CS 227 Lecture -*- Outline -*- * Currying (7.3) A technique named after Haskell B. Curry, but actually first invented by Schoenfinkel and independently by Curry ** idea, when is it useful *** want to fix one of the parameters of a function that is vary a procedure along one dimension (of parameterization) for example, instead of writing (round-n-places 5 z) (round-n-places 5 y) ... want to capture that once and for all. (see exercise 7.14 for currying round-n-places) *** currying is planning ahead to allow that currying is planning to make it easy to fix one argument later ------------------- CURRYING (round-n-places 5 z) (round-n-places 5 y) (+ 5 7) (+ 5 12) def: currying is planning ahead to make it easy to fix one argument of a procedure, by having nested lambdas. -------------------- ** Currying addition --------------- CURRYING ADDITION +: (-> (number number) number) (define add5 ; TYPE: (-> (number) number) (lambda (n) (+ 5 n))) (define add20 ; TYPE: (-> (number) number) (lambda (n) (+ 20 n))) ---------------- We could do this for any number. We apply our lambda abstraction knowledge to do this and abstract out the common part. --------------- (define curried+ ; TYPE: (-> (number) ; (-> (number) number)) (lambda (m) (lambda (n) (+ m n)))) --------------- What this is is a procedure that takes a number as an argument and returns one of the things that add 5 or add 20. *** Examples Q: can you define add5? add20? (define add5 (curried+ 5)) (define add20 (curried+ 20)) ((curried+ 7) 12) Q: what procdure is (curried+ 8)? *** How to understand curried+ --------------- WHAT curried+ DOES EQUATIONS curried+ = (lambda (m) (lambda (n) (+ m n))) (curried+ m) = (lambda (n) (+ m n)) ((curried+ m) n) = (+ m n) ((curried+ 7) 12) = (((lambda (m) (lambda (n) (+ m n))) 7) 12) = ((let ((m 7)) (lambda (n) (+ m n))) 12) = (let ((m 7)) ((lambda (n) (+ m n)) 12)) = (let ((m 7)) (let ((n 12)) (+ m n))) ---------- Note however that the let is created dynamically. That is, we don't have to write it ourselves This leads to the following characterization of what curried+ returns when applied to an argument. -------------- (curried+ 7) = (let ((m 7)) (lambda (n) (+ m n))) -------------- Or use the environment diagrams, remember that the procedure returned from a lambda, remembers the environment where it was born. -------------- USING THE ENVIRONMENT MODEL Env0: + ~~> [primitive-procedure|args|...] curried+ ~~> [procedure|(m) |(lambda (n) (+ m n)) |Env0] (define add7 (curried+ 7)) Env1: m ~~> [7] (lambda (n) (+ m n)) ==> [procedure|(n)|(+ m n)|_____] Env0: + ~~> [primitive-procedure|args|...] curried+ ~~> [procedure|(m) |(lambda (n) (+ m n)) |Env0] add7 ~~> [procedure|(n)|(+ m n)|_____] -------------- Note that Env1 doesn't go away now, since it's part of add7's closure. may want to apply add7 now ** More examples *** Curried member Suppose we want to see if the symbol clinton is a member of the lists democrats and governors and presidents. We could write ----------- (member? 'clinton democrats) (member? 'clinton governors) (member? 'clinton presidents) ----------- But we have to repeat (member? 'clinton ...) each time. Does not capture the idea of "clinton membership." ------------------ (define member? ; TYPE: (-> (datum list) boolean) (lambda (item ls) (if (null? ls) #f (or (equal? (car ls) item) (member? item (cdr ls)))))) ------------------ We want a procedure of type (-> (datum) (-> (list) boolean)) that is, one that takes a datum and returns a procedure that tests if that datum is in the list. So we want (lambda (item) ...) ------------------ ; - Program 7.16, pg. 212 - (define member?-c ; TYPE: (-> (datum) (-> (list) boolean)) (lambda (item) (letrec ((helper ; TYPE: (-> (list) boolean) (lambda (ls) (if (null? ls) #f (or (equal? (car ls) item) (helper (cdr ls))))))) helper))) (define clinton-in? (member?-c 'clinton)) (clinton-in? governors) ------------------ Notes: The letrec avoids passing item on each recursive call. The procedure helper has type list -> boolean as required. *** Curried map Already saw this. (Recall the slides for map and map-c) We can write it in the same form as member-c, call it apply-to-all ------------------ ; - Program 7.17, pg. 213 - (define apply-to-all ; TYPE: (-> ((-> (S) T)) ; (-> ((list S)) (list T))) (lambda (proc) ; ENSURES: (result ls) is the list of ; results of applying f to items of ls (letrec ((helper ; TYPE: (-> ((list S)) (list T)) (lambda (ls) ; ENSURES: result is list of ; results of proc applied to ; items of ls, in order (if (null? ls) '() (cons (proc (car ls)) (helper (cdr ls))))))) helper))) ------------------ ** Generalized currying Consider the following ------------------ (define swapper ; TYPE: (-> (datum datum list) list) (lambda (x y ls) (cond ((null? ls) '()) ((equal? (car ls) x) (cons y (swapper x y (cdr ls)))) ((equal? (car ls) y) (cons x (swapper x y (cdr ls)))) (else (cons (car ls) (swapper x y (cdr ls))))))) ------------------ Have them write a procedure ------------------ YOU ARE TO WRITE: swapper-m: (-> (datum datum) (-> (list) list)) ((swapper-m 0 1) '(1 0)) ==> (0 1) ((swapper-m 'a 'b) '(a b c b a)) ==> (b a c a b) ------------------ *** Terminology -- The modification of swapper might be called generalized currying: Replace a procedure of m+k parameters with a procedure that takes m and returns one that takes k. i.e., (-> (t1 ... tm tm+1 ... tm+k) result) becomes (-> (t1 ... tm) (-> (tm+1 ... tm+k) result)) -- Currying proper makes every function take one argument. It turns an m+k argument function into m+k functions (nested), so that each takes 1 argument. ** if more time or in recitations (but in Fall 1993 this is part of the homework) Do procedural abstraction of iteration from length and reverse