CS 227 Lecture -*- Outline -*- focus: how to use these procedures; more generally, how to look at a definition of such a proc and see how to use it. * Procedures as Arguments and Values (7.2) ** Abstraction of patterns --- what lambda does Back in Chapter 2, we saw that we could abstract the pattern, ----------------- LAMBDA ABSTRACTION (cons 19 '()) (cons 'bit '()) (cons 'x '()) (cons (+ 2 3) '()) (cons (f (g x) 3) '()) (cons (cons 'x '()) '()) ----------------- so that instead of writing out the cons and '() each time, we could capture that idea (abstract it) once and for all, in a procedure. ----------------- (define list1 ; TYPE: (-> (T) (list T)) (lambda (x) (cons x '()))) (list1 19) (list1 (+ 2 3)) (list1 (list1 'x)) ----------------- ** Composition Now, let us look at patterns where the common parts are procedures. How would you write the procedure cadr? ----------------- LAMBDA ABSTRACTION, FOR PROCEDURES (define cadr ; TYPE: (-> ((list T)) T) (lambda (x) (car (cdr x)))) ----------------- How about cddr? ----------------- (define cddr ; TYPE: (-> ((list T)) (list T)) (lambda (x) (cdr (cdr x)))) ----------------- and a not-null procedure? ----------------- (define not-null? ; TYPE: (-> ((list T)) boolean) (lambda (x) (not (null? x)))) ----------------- These are all very similar. We would like to just say, put these two procedures together in this order. ----------------- ; - Program 7.8, page 201 - (define compose ; TYPE: (-> ((-> (T) U) (-> (S) T)) ; (-> (S) U)) (lambda (f g) ; ENSURES: (result x) = (f (g x)) (lambda (x) (f (g x))))) ----------------- Show how to recover the others, also add2 .... *** use --------------- (define cadr (compose car cdr)) (define cddr (compose cdr cdr)) (define not-null? __________________) (define add2 ______________________) --------------- Q: can you fill in the blanks? *** ways of thinking about it -------------- WAYS OF THINKING ABOUT COMPOSE --------------- **** type is an abstract view of how it works Derive the type of compose (-> ((-> (T) U) (-> (S) T)) (-> (S) U)) Like connecting lines, or implications --------------- TYPE ( f ( g x)) U <--[ f ]--- T <--[ g ]--- S --------------- **** equations thinking of it as a combinator ***** from examples --------------- EQUATIONS, FROM EXAMPLES ((compose add1 add1) 3) = (add1 (add1 3)) ((compose add1 car) ls) = (add1 (car ls)) ((compose f g) x) = (f (g x)) --------------- ***** from the definition this is very general and quick --------------- EQUATIONS, FROM THE DEFINITION compose = (lambda (f g) (lambda (x) (f (g x)))) (compose f g) = ((compose f g) x) = ---------------- fill in the above with (compose f g) = (lambda (x) (f (g x))) ((compose f g) x) = (f (g x)) **** using equational semantics of Scheme fill in reasons below ---------------- USING LET ((compose add1 car) '(3 0)) = (((lambda (f g) (lambda (x) (f (g x)))) add1 car) '(3 0)) = ((let ((f add1) (g car)) (lambda (x) (f (g x)))) '(3 0)) = (let ((f add1) (g car)) ((lambda (x) (f (g x))) '(3 0))) = (let ((f add1) (g car)) (let ((x '(3 0))) (f (g x)))) ==> 4 ------------ **** using environments ------------ USING THE ENVIRONMENT MODEL Env0: car ~~> [primitive-procedure|(x l)|...] add1 ~~> [primitive-procedure|(x)|...] compose ~~> [procedure|(f g) |(lambda (x) (f (g x))) |Env0] ((compose add1 car) '(3 0)) -------------- to evaluate this application we eval the operator -------------- (compose add1 car) ------------- to evaluate this, we evaluate compose, add1, car, then apply by forming: (connect up f and g, fill in the blanks in the closure) ------------- Env1 (parent Env0): f g (lambda (x) (f (g x))) ==> [procedure|(x)| ] ------------ then we evaluate '(3 0), getting (3 0), then apply the operator, the closure for (compose add1 car) by forming the environment (so fill in below for the binding) ------------- Env2 (parent ): x ~~> [ ] ------------ The point is to see the environment made by compose remembers f and g ** Mapping Let us look at a procedure to add 1 to all items of a list of numbers, returning list, ---------------- (define add1-to-each ; (-> (list) list) (lambda (ls) ; ENSURES: result is list of adding 1 ; to each item of ls (if (null? ls) '() (cons (add1 (car ls)) (add1-to-each (cdr ls)))))) ---------------- and a procedure to subtract 3 from each element. ---------------- (define sub3 (lambda (x) (- x 3))) (define sub3-from-each ; (-> (list) list) (lambda (ls) ; ENSURES: result is list of ; subtracting 3 from each item of ls (if (null? ls) '() (cons (sub3 (car ls)) (sub3-from-each (cdr ls)))) )) ---------------- See the pattern? ---------------- (define map-c ; TYPE: (-> ((-> (S) T)) ; (-> ((list S)) (list T))) (lambda (f) ; ENSURES: (result ls) is the list of ; results of applying f to items of ls (lambda (ls) (if (null? ls) '() (cons (f (car ls)) ((map-c f) (cdr ls))))))) ---------------- Note: don't be tempted to use a LET as in (define map-c (lambda (f) (let ((recur (map-c f))) ;;; no! loops forever! (lambda (ls) (if (null? ls) '() (cons (f (car ls)) (recur (cdr ls)))))))) This is an infinite loop!!! But the following does work... (define map-c (lambda (f) (letrec ((recur (lambda (ls) (if (null? ls) '() (cons (f (car ls)) (recur (cdr ls))))))) recur))) Show how to recover add1-to-each and sub3-from-each from map-c. (define add1-to-each (map-c add1)) Usage: (add1-to-each (list 3 4)) ((map-c sub1) (list 2 3 5)) Derive the type of map-c How to understand this? ------------- map-c = (lambda (f) (lambda (ls) (if (null? ls) '() (cons (f (car ls)) ((map-c f) (cdr ls)))))) (map-c f) = (lambda (ls) (if (null? ls) '() (cons (f (car ls)) ((map-c f) (cdr ls))))) ((map-c f) ls) = (if (null? ls) '() (cons (f (car ls)) ((map-c f) (cdr ls)))) Recursively: ((map-c f) '()) = '() ((map-c f) (cons x l)) = (cons (f x) ((map-c f) l)) Iteratively ((map-c f) (list a b c ...)) = (list (f a) (f b) (f c) ...) ------------- If the equations are too subtle, show them for add1-to-each also... There is a procedure built in to Scheme that is like map-c, but easier to use. ---------------- ; - Program 7.1, pg. 196 - (define map ; TYPE: (-> ((-> (S) T) (list S)) ; (list T)) (lambda (proc ls) ; ENSURES: result is a list of the ; results of applying proc to the ; items of ls, in the same order (if (null? ls) '() (cons (proc (car ls)) (map proc (cdr ls)))))) ---------------- Q: can you define add1-to-each and sub3-from-each using map? (define add1-to-each (lambda (ls) (map add1 ls))) Q: Given the list '(1 3 5 7 9) and map, how would you make the list (-3 -1 1 3 5); that is, subtracting 4 from each? You don't always have to give a name to the argument procedure. (map (lambda (x) (- x 4)) '(1 3 5 7 9)) Q: can you define map using map-c? (define map (lambda (proc ls) ((map-c proc) ls))) Q: What are the equations for map? ----------------- CAN YOU FILL IN THE RIGHT-HAND SIDES? (map f l) = ((map-c ___) ___) (map f '()) = (map f (cons x l)) = (map f (list a b c ...)) = (list ________________...) --------------- ** add, times, exponent hierarchy addition can be thought of as repeated adding of 1 (successor) ---------------- ; - Program 7.9, pg. 203 - (define plus ; TYPE:(-> (integer integer) integer) (lambda (x y) (if (zero? y) x (add1 (plus x (sub1 y)))))) ---------------- multiplication is repeated addition ---------------- ; - Program 7.10, pg. 203 - (define times ; TYPE: (-> (integer integer) integer) (lambda (x y) (if (zero? y) 0 (plus x (times x (sub1 y)))))) ---------------- exponentiation is repeated multiplication ---------------- ; - Program 7.11, pg. 203 - (define exponent ; TYPE: (-> (integer integer) integer) (lambda (x y) (if (zero? y) 1 (times x (exponent x (sub1 y)))))) ---------------- What if we go further? (Computer Scientists and Mathematicians love to do this!) ---------------- ; - Program 7.12, pg. 203 - (define super ; TYPE: (-> (integer integer) integer) (lambda (x y) (if (zero? y) 1 (exponent x (super x (sub1 y)))))) ---------------- here, (super 2 3) is (2^2)^2 and (super 2 4) is ((2^2)^2)^2 and yet further ..... ---------------- ; - Program 7.13, pg. 204 - (define superduper ; TYPE: (-> (integer integer) integer) (lambda (x y) (if (zero? y) 1 (super x (superduper x (sub1 y)))) )) ---------------- See the pattern? We can define this hierarchy inductively, with addition and multiplication as base cases. ---------------- ; - Program 7.14, pg. 204 - (define super-order ; TYPE: (-> (integer) ; (-> (integer integer) ; integer)) (lambda (n) (cond ((= n 1) plus) ((= n 2) times) (else (lambda (x y) (if (zero? y) 1 ((super-order (sub1 n)) x ((super-order n) x (sub1 y))))))))) ---------------- (Change the last cond to if to show pattern better) (Use + and * to get speed) If all the arguments in super-order are the same, this is the Ackermann function. ---------------- ; - Program 7.15, pg. 205 - (define ackermann ; TYPE: (-> (integer) integer) (lambda (n) ((super-order n) n n))) ---------------- This grows faster than any polynomial. e.g., (Ackermann 4) is (((4^4)^4)^4) (4^4)^4 is 4^{256} and log (4^{256}) = 256 * log 4 = 154.13, so ((4^4)^4) is about 10^{153}, so (Ackermann 4) has about 10^{153} digits!