2008-02-05 Introducing `lambda', Functions as Objects, Currying ======================================================================== >>> Introducing Scheme's `lambda' fun & lambda difference between lambda and simple values scheme puzzle -- (+ ((?)) 3) not being able to do recursive functions with `let' let* as a derived form let with lambda in scheme --> can be a derived form how `if' can be used to implement `and' `or' as derived forms ======================================================================== >>> More Lambdas: Functions as Objects, Currying Get back quickly over the concept of a function in Scheme. Newtonian syntax vs. a lambda expression. Don't be fooled into making a bogus connection between Scheme's syntax, and its `unique' powers... The fact is that it is not the only language that has this capability. For example, this: (define (f g) (g 2 3)) (f +) ==> 5 (f *) ==> 6 (f (lambda (x y) (+ (square x) (square y)))) ==> 13 Can be written in JavaScript like this: function f(g) { return g(2,3); } function square(x) { return x*x; } window.alert(f(function (x,y) { return square(x) + square(y); })) ======================================================================== >>> Using Functions as Objects A very important aspect of Scheme -- using "higher order" functions -- functions that get and return functions. Here is a very simple example: (define (f x) (lambda () x)) (define a (f 2)) (a) --> 2 (define b (f 3)) (b) --> 3 Note - what we get is actually an object that remembers (by the substitution we're doing) a number. How about: (define aa (f a)) (aa) --> # (this is a) ((aa)) --> 2 Take this idea to the next level: (define (kons x y) (lambda (b) (if b x y))) (define (kar x) (x #t)) (define (kdr x) (x #f)) (define a (kons 1 2)) (define b (kons 3 4)) (list (kar a) (kdr a)) (list (kar b) (kdr b)) Or, with types: (: kons : (All (A B) (A B -> (Boolean -> (U A B))))) (define (kons x y) (lambda (b) (if b x y))) (: kar : (All (A B) ((Boolean -> A) -> A))) (define (kar x) (x #t)) (: kdr : (All (A B) ((Boolean -> B) -> B))) (define (kdr x) (x #f)) (define a (kons 1 2)) (define b (kons 3 4)) (list (kar a) (kdr a)) (list (kar b) (kdr b)) Even more -- why should the internal function expect a boolean and choose what to return? We can simply expect a function that will take the two values and return one: (define (kons x y) (lambda (s) (s x y))) (define (kar x) (x (lambda (x y) x))) (define (kdr x) (x (lambda (x y) y))) (define a (kons 1 2)) (define b (kons 3 4)) (list (kar a) (kdr a)) (list (kar b) (kdr b)) And a typed version, using our own constructor to make it a little less painful: (define-type (Kons A B) = ((A B -> (U A B)) -> (U A B))) (: kons : (All (A B) (A B -> (Kons A B)))) (define (kons x y) (lambda (s) (s x y))) (: kar : (All (A B) ((Kons A B) -> (U A B)))) (define (kar x) (x (lambda: ([x : A] [y : B]) x))) (: kdr : (All (A B) ((Kons A B) -> (U A B)))) (define (kdr x) (x (lambda: ([x : A] [y : B]) y))) (define a (kons 1 2)) (define b (kons 3 4)) (list (kar a) (kdr a)) (list (kar b) (kdr b)) Note that the type definition is the same as: (define-type Kons = (All (A B) ((A B -> (U A B)) -> (U A B)))) so `All' is to polymorphic type definitions what `lambda' is for function definitions. Finally in JavaScript: function kons(x,y) { return function(s) { return s(x, y); } } function kar(x) { return x(function(x,y){ return x; }); } function kdr(x) { return x(function(x,y){ return y; }); } a = kons(1,2); b = kons(3,4); window.alert('a = <' + kar(a) + ',' + kdr(a) + '>' ); window.alert('b = <' + kar(b) + ',' + kdr(b) + '>' ); ======================================================================== >>> Currying How is this done? Functions for translating between normal and curried versions. (define (currify f) (lambda (x) (lambda (y) (f x y)))) Typed, with examples: (: currify : (All (A B C) ((A B -> C) -> (A -> (B -> C))))) ;; convert a double-argument function to a curried one (define (currify f) (lambda (x) (lambda (y) (f x y)))) (: add : (Number Number -> Number)) (define (add x y) (+ x y)) (: plus : (Number -> (Number -> Number))) (define plus (currify add)) (test ((plus 1) 2) => 3) (test (((currify add) 1) 2) => 3) (test (map (plus 1) '(1 2 3)) => '(2 3 4)) Usages -- common with H.O. functions like map, where we want to `fix' one argument. When dealing with such higher-order code, the types are very helpful, since every arrow corresponds to a function: ;; currify : (A B -> C) -> (A -> (B -> C)) It is common to make a function `->' type associate to the right (but not in Typed Scheme), so this type can be written as: ;; currify : (A B -> C) -> (A -> B -> C) (Which is also the same as: ;; currify : (A B -> C) -> A -> B -> C but that's a little confusing...) ========================================================================