(define (range x y) (if (> x y) '() (cons x (range (+ x 1) y))))Then we wrote the Scheme equivalent of map. I used a slightly different name to distinguish it from the built-in definition of map, so I called ours "mapp":
(define (mapp f lst) (if (null? lst) '() (cons (f (car lst)) (mapp f (cdr lst)))))To write this, we needed a predicate to test for an empty list. We used the "null?" predicate (pronounced by Scheme programmers as "null p" or "null huh?") which tells you whether or not something is an empty list.
Then we wrote an equivalent for filter that we called filterr. This procedure involves a 3-way branch (empty list, first value passes the test, first value fails the test). For a 3-way branch, it is better to use a construct known as cond. With cond you can pick between any number of alternatives. It is like a nested if/else construct in Java or OCaml (if, else if, else if, else if, else). It has this general form:
Using cond, we wrote our version of filter as follows:
(define (filterr f lst) (cond ((null? lst) '()) ((f (car lst)) (cons (car lst) (filterr f (cdr lst)))) (else (filterr f (cdr lst)))))Then we wrote a procedure that would convert a list into a list of lists that are 2-long. It pairs up values to make sublists, as in:
> (pair-off (range 1 10)) ((1 2) (3 4) (5 6) (7 8) (9 10))If the list has an odd number of elements, it makes a sublist out of the final element:
> (pair-off (range 1 11)) ((1 2) (3 4) (5 6) (7 8) (9 10) (11))To write this we need to be able to make a list. Scheme has a procedure called list that does exactly that:
> (list 1 3 4) '(1 3 4)We again used a cond for this code and we used the length of the list to test for the 1-element case and the list procedure to make lists:
(define (pair-off lst) (cond ((null? lst) '()) ((= (length lst) 1) (list lst)) (else (cons (list (car lst) (cadr lst)) (pair-off (cddr lst))))))Then we talked about defining a procedure that will compute (x + y)2. We can do this directly, as in:
(define (f x y) (* (+ x y) (+ x y)))We didn't like the idea of computing the sum twice, so we decided to include a let expression to define a local variable called sum. The let construct is similar to OCaml's let/in/end construct. It takes a list of symbol/expression pairs and an expression to evaluate:
When I tried to use this in our procedure f, I purposely made a parenthesis mistake with the let:
(define (f x y) (let (sum (+ x y)) (* sum sum)))This didn't work because the first argument of a let should be a list of bindings. So even if there is just one variable being defined, we have to put that binding inside a list:
(define (f x y) (let ((sum (+ x y))) (* sum sum)))DrRacket allows you to use square brackets for the bindings to try to avoid this mistake:
(define (f x y) (let ([sum (+ x y)]) (* sum sum)))In fact, if you type the keys for brackets as you do this, DrRacket will correctly use parentheses when appropriate and brackets when appropriate. This can be a useful habit to prevent making errors.
I then asked how we could define a second procedure that would compute (x + y)2 times (x + y + 2)2. We could again define this directly:
(define (f x y) (let ([sum (+ x y)]) (* sum sum (+ sum 2) (+ sum 2))))But we again decided to use a local variable to avoid computing the second sum twice:
(define (f x y) (let ([sum (+ x y)] [sum2 (+ sum 2)]) (* sum sum sum2 sum2)))Unfortunately, this didn't work. It generated a runtime error in the binding of sum2, saying that sum is not defined. As in OCaml, you can't refer to other definitions included in the let expression. The way to get around this is to use a variation known as let* which allows you to refer to other bindings:
(define (f x y) (let* ([sum (+ x y)] [sum2 (+ sum 2)]) (* sum sum sum2 sum2)))Order matters in this case, so it is important to define sum before using it.
You can think of let and let* as being two points on a spectrum. The let construct is the simplest. It says to compute various local variables without paying attention to the other local variables being defined. The let* construct says to define them sequentially so that a later local variable definition can make reference to an earlier one. There is a third variation of let that is known as letrec that is even more powerful than let*, but we won't need it for what we'll be doing.
Someone asked why Scheme has variations like let, let* and letrec. I said that I've heard many answers from Scheme programmers. The most common answer is that Scheme programmers want their code to be efficient so they don't want to be forced to use a more expensive operation if they don't need to. Why should you have to pay the price of a let* if all you need is a let?
Some instructors encourage students to use letrec for defining local helper procedures, but I think this syntax is unnecessarily confusing. Following the advice that Abelson and Sussman give in Structure and Interpretation of Computer Programs, we will define local helper procedures as an "internal definition" (embedding a define inside a define).
For example, the procedure f below computes (x2 - 2) + (y2 - 2) + (z2 - 2) by calling a local procedure g that computes (n2 - 2):
(define (f x y z) (define (g n) (- (sqr n) 2)) (+ (g x) (g y) (g z)))I pointed out that we can define helper procedures using internal definitions because the syntax of define is really this: