About the assignments in ICS 141: This course teaches not only new concepts but also new ways of thinking. Learning to think in new ways usually requires practice-- actually doing the new things a few times (or more). The homeworks are your opportunity to get that practice. Don't just say to yourself, "Let me just get the answer down and turn it in; I'll learn how to do it later." Keep working (by yourself, with the instructor or TAs, or with your classmates) until you can produce the answers on your own, without help.

It's no secret that some of the problems we assign this quarter are the same as we assigned last quarter. It is entirely possible for you to find the answers from someone who took the course before and just copy them down and turn them in. But besides being academically dishonest, it also won't prepare you to do similar problems on the exams, so your course grade will suffer. Give the assignments the time and attention they require, and enjoy stretching your "mental muscles."

Due date: This assignment is due on Monday, January 24, by 3:00 PM. Deposit your assignment in the locking drop box in room CS 189. The due date is closer than it seems; be sure to get started this week so you can ask questions in discussion on Friday.

Summary: For this assignment you will become acquainted with running the Scheme interpreter and the basic patterns of recursive list processing code in Scheme. A few of the problems will seem familiar to those who took ICS 22 here from me; I apologize for the repetition but your work back then will pay off now.

Part I (Readings and review questions): Each chapter of Sebesta ends with two sets of exercises, one called "review questions" and one called "problem set." Each set is numbered starting from 1, so be careful to verify which set we're asking for on each assignment.

You should know the answers to the review questions listed below, but you do not have to turn them in. This list should help you identify some of the more important concepts in the text. (On the other hand, don't infer that topics we skip or omit are entirely unimportant.)

Chapter 1: 1-11, 13, 15-17, 19-25, 27-32. Come back to these questions at the end of the course, as you review for the final.

Chapter 2: 8, 14-17, 21, 26-30, 35-38, 42, 43, 46.

Chapter 14: 1, 4, 6-9, 14.

Part II: In Chapter 2 of Sebesta, answer in one or two brief sentences each of the following questions in the problem set on page 104: 6, 7, 8, 9, and 14. (Note that these are questions from the "problem set," not from the "review questions.")

Feel free to try some of the exercises we don't assign, but if you get stuck, ask us before spending too much time on any problem; not all of them address issues that will be important to us.

Part III: We cover Scheme in ICS 141 for two main reasons: To illustrate concepts of functional programming, including higher-order functions, and to give you experience learning a new language that's significantly different from what you already know. Learning to think in a new way isn't easy; some frustration is inevitable as you stretch your mind in new directions. But middle school and high school students learn this material (see www.schemers.com and www.cs.rice.edu/CS/PLT/Teaching/), so you can, too.

Scheme language details: Writing textbooks is hard work, so maybe we can forgive Sebesta missing a few details about the current status of the Scheme language standard:
-- The two boolean constants are
#t and #f. NIL is no longer used, and while the empty list counts as false, it's bad programming practice to use it that way.
-- Many Scheme implementations allow
first and rest in place of the historically interesting but hopelessly non-mnemonic car and cdr. If yours doesn't, you can just say (define first car) and (define rest cdr).
-- Some Scheme implementations allow curly braces or square brackets instead of parentheses, as a visual aid to matching pairs up. That's not standard; you should just use parentheses and let your environment do the matching for you.
-- There are two equivalent styles for defining functions, which I'll call the prototype form and the lambda form. Sebesta uses the prototype form, which is shorter and which illustrates what a call might look like:
(define (cube x) (* x x x)). I prefer the lambda form, which doesn't hide the underlying lambda expression and which is consistent in form with other uses of define: (define cube (lambda (x) (* x x x))).

Scheme implementations: The NT machines in the labs have an implementation of Scheme called EdScheme.

For use at home, you can download a limited-time version of EdScheme for free from www.schemers.com; you can also purchase a copy for about 50% off (see us for details). We also recommend DrScheme, another implementation from Rice University that's available entirely for free on most every platform (www.cs.rice.edu/CS/PLT/packages/drscheme/).

Problems: For most of these problems, we'll ask you to print out the transcript window showing your interaction with the Scheme interpreter. You'll probably want to produce a separate transcript for turning in, rather than printing out pages and pages showing all your experimentation. But don't worry if your transcript contains a few typos.

(a) Get used to the Scheme environment. Try some expressions like (* 123 456) and (expt 2 100) and (/ 3.14159265 2).

Type in some definitions of symbols in the global environment, like (define pi 3.14159265), and then try (/ pi 2).

Type in a function definition like this one:

(define fact ; Compute n! (n factorial).
   (lambda (n) ; 0! is 1 by definition
     (cond ; The extra horizontal space
       ((<= n 0) 1 ) ; isn't needed; it just lines
       (else (* n (fact (- n 1))))))) ; up the cond clause parts.

Notice how the environment indents and highlights blocks of code so you don't get the parentheses confused.

Make sure you know how to save your code in a file and load that file into Scheme for evaluation. EdScheme doesn't automatically re-evaluate changes you make in your code, any more than Visual C++ does (you have to recompile there, too).
Try some compound expressions, like
(gcd (fact 100) (expt 2 1000)) and (fact (fact 5)) and (first (rest '(Huey Dewey Louie))).

What is the value produced by (/ (fact 5) (expt 7 2))? This result is called "exact representation"--it looks unusual to us, but it's useful in further calculations because nothing is lost by rounding off to a decimal representation. On the other hand, evaluate (output-fixed-point (/ (fact 5) (expt 7 2)) 15 10). (The 15 is the total size in characters of the result; the 10 is the number of digits to the right of the decimal point.) The code for output-fixed-point is available on Masterhit, the NT lab server.

What happens when you evaluate (fact (fact 500))?

Play around more with EdScheme, trying other expressions. Experiment with the list operators--cons, first, rest, list, append, null?--until you're comfortable with how they work. You can look at the online help (available under the Help menu or question-mark button) for some more information. To understand what a function does, be sure you understand what kinds of data it expects as its arguments (atoms? lists? numbers?) and what kind of data it returns. The function cons, for example, takes any expression as its first argument and a list as its second argument, and it returns a list.

You don't have to turn anything in for this part (III (a)) of the lab. But of course if you short-change the time you spend building familiarity, you'll have much more trouble later on.

(b) This function, called Ackerman's function, grows really fast:
(define A
  (lambda (x y)
  (cond ((= x 0) (+ 1 y))
  ((= y 0) (A (- x 1) 1))
  (else (A (- x 1)
  (A x (- y 1)))))))
(b.1) Type it in and try it out (with very small arguments). Then print out the Transcript window showing what you did. (But you don't have to print out results that show pages and pages of solid digits.)

(b.2) Rewrite Ackerman's function on paper using standard mathematical notation.

(c) What's the longest number you can generate in the Scheme you're using, without running out of memory and taking no more than 60 seconds of processor time? Generating the big numbers is one part of the question; counting the digits is another.

(c.1) Try using (string-length (number->string your-big-number)).

(c.2) Try to approximate it using the log base 10.

(c.3) Try to do it using some tool(s) other than Scheme (or any programming language).

(c.4) Using your wristwatch (or slow, measured counting), time how long it takes for Scheme to calculate and display your big number. Now, time how long it takes to calculate the big number and then its length (by nesting the expression to generate the big number inside the length-calculating expression from part (c.1) or (c.2)). You'd expect the second to take longer, but on some Scheme systems it doesn't. Does it on your system? Why might the generate-and-calculate-length task take less time?

(c.5) Type up your answers to these questions and print a transcript showing what you did. Again, don't print more than a page or two of solid digits.

(d) Write each of the following functions in Scheme. For each, pay attention to the type of value that's returned: Is it a list, a single item, a number, a boolean? If you're new to recursive thinking, it will take you a while to start seeing the patterns; that's why there are so many exercises (and even this many may not be enough).

Go back and read what the course reference sheet says about collaboration. It's good to work with your classmates, but remember that the goal is that you be able to write routines like this independently.

(member? A B) returns #t (true) if A occurs in the list B, and #f (false) if it doesn't. (Sebesta solves this in the text, but try it yourself first.)
(member? 'a '()) returns #f;
(member? 'a '(b a t t y)) returns #t;
(member? '(b (c)) '(a b (b (c)) d (b (c)))) returns #t;
(member? '(b (c)) '(a b (c))) returns #f.

(find-all-evens A) takes a list of numbers and returns a list containing all the numbers from the original list that are even. The predefined predicate even? is useful here.
(find-all-evens '()) returns ();
(find-all-evens '(3 9 7)) returns ();
(find-all-evens '(1 2 3 4 5)) returns (2 4);
(find-all-evens '(3 2 7 2 6)) returns (2 2 6).

(all-even? A) takes a list of numbers and returns #t if they're all even, and #f otherwise.
(all-even? '()) returns #t;
(all-even? '(3 5 7 2 6)) returns #f;
(all-even? '(2 8 0 4 88)) returns #t.

(count-all-matches A B) returns the number of times A occurs in the list B.
(count-all-matches 'a '()) returns 0;
(count-all-matches 'a '(a b a c a d)) returns 3;
(count-all-matches 'a '(a b (a) c (a d))) returns 1;
(count-all-matches '(a (b)) '(a b (a (b)) a (b) (a (b)) ((a (b))))) returns 2.

(subst A B C) returns C with all occurrences of A changed to B.
(subst 'x 'y '()) returns ();
(subst 'a 'b '(a c e)) returns (b c e);
(subst 'a 'b '(b c d)) returns (b c d);
(subst 'a '(a b) '(a b r a)) returns ((a b) b r (a b));
(subst '(a b c) 'abc '(w x (a b c) y (a b c) z)) returns (w x abc y abc z).

(first-atom A) returns the first atom in the list A, no matter how deeply nested. Use the predefined predicates pair? and null? to test whether something is an atom or not.
(first-atom '()) returns ();
(first-atom '(a b c)) returns a;
(first-atom '(((a b) c))) returns a;
(first-atom '( () a b c)) returns (), which is easy because (atom? '()) is #t.

(atomize A) returns a list of all the atoms in A, no matter how deeply nested. (Hint: Use the predefined function (append L1 L2) to join the atoms in (first A) with the atoms in (rest A).)
(atomize '()) returns ();
(atomize '(a b c)) returns (a b c);
(atomize '((a b) c)) returns (a b c);
(atomize '(((a) () (b c)) (d e))) returns (a () b c d e).

Again, print out a transcript showing your definitions and some tests indicating that they're right.