(define (range x y)
(if (> x y)
()
(cons x (range (+ x 1) y))))
Then we wrote the Scheme equivalent of map. DrScheme doesn't allow you to
override the built-in definition of map, so we had to call 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 function 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 ML (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 function 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))
We again used a cond for this code and we used the length of the list to test
for the 1-element case:
(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 ML'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)))
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. The problem is that the let
expression doesn't work the way it did in ML. In particular, 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)))
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?
Previous versions of cse341 have encouraged 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:
This was the end of the lecture, but I have included additional material below that was presented in section and that will be useful to refer to for the programming assignment.
Then I pointed out that Scheme has a way to define a structured object with named fields. You call the "define-struct" procedure, as in the following definition of a point structure that has named fields called x and y:
(define-struct point (x y))
The net effect of calling this procedure is that we have a number of useful
procedures defined for us, including:
> (define-struct point (x y))
> (define p (make-point 2 15))
> (point? p)
#t
> (point? '(2 15))
#f
> (point-x p)
2
> (point-y p)
15
We will be using a struct for the first Scheme homework assignment.