Let's take a look at the natural numbers defined inductively.
N ::= 0 | succ N (define even? (lambda (n) (cond ((zero? n) #t) (else (odd? (- n 1)))))) (define odd ...)Repeated patterns in programming are bad because each repetition offers another opportunity to introduce a bug. Use procedures to abstract common patterns. Consider the problem of adding one to each element of a list.
(define add-each (lambda (l) (cond ((null? l) '()) (else (cons (add1 (car l)) (add-each (cdr l)))))))Suppose we also need to subtract one from every element.
(define sub-each (lambda (l) (cond ((null? l) '()) (else (cons (sub1 (car l)) (add-each (cdr l)))))))These procedures are nearly the same. We can write one procedure, abstracting over the operation:
(define each (lambda (f l) (cond ((null? l) '()) (else (cons (f (car l)) (each (cdl l)))))))This procedure is map.
Now consider multiplying all the numbers of a list together.
(define *-overlist (lambda (l) (if (null? l) 1 (* (car l) (*-overlist (cdr l))))))If we also wanted to define
+-overlist
we should use procedure
abstraction. To do this we write a folding function. Folding can occur
either to the left or to the right. The procedure above folds to the
right.
(define foldr (lambda (f i l) (if (null? l) i (f (car l) (foldr f i (cdr l)))))) (define *-overlist (lambda (l) (foldr * 1 l)))
foldr
multiplies elements from right to left. We can also build
a fold function that works left to right.
(define foldl (lambda (f i l) (if (null? l) i (foldl f (f i (car l)) (cdr l)))))The direction of the fold does not matter in the case of a communicative operation. But if the function is not communicative or has side effects the direction of the fold is quite important. Note how we can now define
map
using foldr
.
(map f l) = (foldr (lambda (a b) (cons (f a) b)) '() l)Much of this we can also do in C, but suppose we can define a function that adds n to its parameter in the following manner.
(define add-n (lambda (n) (lambda (m) (+ m n)))) (map (add-n 3) '(1 2 3))This example shows that we can construct open procedures that capture the value of bound variables. You can not do this in C, hence procedures like
map
and fold
are less useful in C.
Convince yourself that the following are equivalent.
(lambda (x y) (let ((x y) (y x)) x))) (lambda (x y) ((lambda (x y) x) y x))This example shows that
let
is a syntactic abstraction. In general
(let ((x[1] e[1]) ... (x[n] e[n])) e') = ((lambda(x[1] .. x[n]) e') e[1] ... e[n])The reason that
let
is not a function is that it manipulates
bindings. Why else might we want to have syntactic abstractions? Consider
the following.
(cond ((test then[1] ... then[n]) clause*)) = (if test (begin then[1] ... then[n]) (cond clause*))) (cond (else e[1] ... e[n])) = (begin e[1] ... e[n]) (cond) = undefHere we see the second motivation for syntactic abstraction: to change argument evaluation order. Now lets look at
and
and or
.
(and e[0]) = e[0] (and e[0] ... e[n]) = (if (e[0]) (and (e[1] ... e[n]) #f)) (or e[0]) = e[0] (or e[0] ... e[n]) = (let ((x e[0])) (if x x (or e[1] ... e[n]))) x not in FV(e[1] ... e[n])Scheme has short-circuit evaluation like C. Suppose that we forget the condition that x not be in FV(e[1] ... e[n]).
(define x 1) (or #f x)This should be 1, but instead it is expanded into
(let ((x #f)) (if x x x))What happened? variable
x
was captured by the binding introduced
by the macro. We must have the hygiene condition that x not in
FV(e[1] ... e[n]) to prevent this. We usually
want hygiene for all variable bindings introduced by a macro. Hygienic macro
systems do this automatically. The following shows that we have the
same problem in C.
#define swap(x,y) { int temp = x; x = y; y = temp; } ... swap(temp,x);
You can define macros in our Scheme system using
extend-syntax
. There you will find
some examples of how to use this feature. Although the extend-syntax system
we are using is not hygienic, we can get a little bit
of help from gensym
. It generates a unique symbol within
the context of an extend-syntax
expression. The following
is an outline for the definition of or
.
(extend-syntax (or) ((or e) e) ((or e1 e2 ... ) (with ((x (gensym))) (let ((x e1) (if ... ))))))