** 5. First-Class Functions ** 5.1. Functions are values! Lots can be done by treating functions as values which can e.g. parameterize other functions. * What does "first-class" mean? - a value can be used just like any other f-c value :) - can be bound to a variable, or left unnamed - can be passed to a fun, and returned from a fn - can be stored in a data structure * F-C fns are good for *parameterizing* other fns by *code*, not just *values* can define a function that takes another function as an argument - pass in a function at call sites (e.g. by naming it) - can invoke function in body (e.g. by naming formal & calling it) example: parameterize member with a function to use to compare elements (define (member-fn eq-fn elem lst) (if (null? lst) #f (if (eq-fn elem (car lst)) lst (member-fn eq-fn elem (cdr lst))))) (member-fn equal? '(3 4) '(2 (3 4) 5 (6 7))) --> ((3 4) 5 (6 7)) (member-fn eq? '(3 4) '(2 (3 4) 5 (6 7))) --> #f Similar examples: parameterizing assoc with key comparison function ------------------------------------------------------------------------ 5.2. Another category: generic operations on lists, parameterized by one or more fns Ex: map: take a list, and apply some operation to each element, producing a list of the results (define (map fn lst) (if (null? lst) () (cons (fn (car lst)) (map fn (cdr lst))))) (map square '(3 4 5)) --> (9 16 25) (map square ()) --> () (map integer? '(3 "hi" (5 6) 7)) --> (#t #f #f #t) To do at home: map2 over two lists, e.g. to add lists pairwise Another functional: filter, which takes a predicate to include (Do at home) Another functional: reduce, which takes an infix binary function and an identity value, and reduces a list using the infix operator. E.g. to compute sum of a list: (sum '(3 4 5 6)) ==> 3 + 4 + 5 + 6 [+ 0] (sum '()) ==> 0 Think about strategy: 1) what's the base case? 2) assume we're not the base case. then we inductively assume we can reduce the rest of the list. How do we take the reduction of the rest of the list, and compute the reduction of the whole list? (define (reduce fn base lst) (if (null? lst) base (fn (car lst) (reduce fn base (cdr lst))))) (reduce + 0 '(3 4 5 6)) --> (reduce + 0 '()) --> 0 (reduce * 1 '(3 4 5 6)) --> (reduce cons () '(3 4 5 6)) --> a copy! map and filter can be programmed in terms of reduce; reduce is more general. can all list manipulation functions? - if they recur on a single list (not merge, or map2) - not tail-recursive ones, at least This reduce is right-associative reduction. Doesn't matter for associative operators like + and *, but does for e.g. cons and -. If we want left-associative reduction, how do we write it? => naturally leads to a tail-recursive function (define (reduce-left fn base lst) (define (reduce-left-helper fn lst result) (if (null? lst) result (reduce-left-helper fn (cdr lst) (fn result (car lst))))) (reduce-left-helper fn lst base)) (reduce-left - 15 '(1 2 3 4)) --> 5 [Important note: Scheme's built-in map, reduce, filter, etc. have somewhat more general interfaces & more complicated semantics than these.] -------------------------------------------------------------------------- 5.3. ** Anonymous functions with lambda ** Since Scheme supports first-class fns, would expect an expression form to create them, even a literal expression Lambda special form: (lambda (formal1 ... formalN) bodyexpr) --> Can bind it to a name, if desired: (define square (lambda (n) (* n n))) (square 3) --> 9 [In fact, (define (id formal1 ... formalN) body) is just *syntactic sugar* for (define id (lambda (formal1 ... formalN) body))] Can pass it to functions, by name or directly: (map square '(3 4 5)) (map (lambda (n) (* n n)) '(3 4 5)) Use anon functions when they're one-shot, useful only to a particular call. Give them a name if there is a reasonable one, and the function is likely to be used more than once. Ex: find all elements of a list that are >100: (filter (lambda (n) (> n 100)) lst) --------------------------------------------------------------------------- 5.4. ** Static Scoping ** Ex 2: change member-fn to take just a test function, not also a value to compare against: (define (find pred-fn lst) (if (null? lst) #f (if (pred-fn (car lst)) lst (find pred-fn (cdr lst))))) Now define member-fn in terms of find: (define (member-fn eq-fn elem lst) (find (lambda (e) (eq-fn e elem)) lst)) But something interesting is going on here: the elem reference in the body of the lambda isn't one of the lambda's formals, but is defined in the (lexically) enclosing scope! We want this to work right. Some terms: *) free variable, w.r.t. an expression: a variable that is referenced within an expression but not bound within that expression *) a closed expr: one w/ no free vars expr == (lambda (x) (foo (lambda (x y) (+ x y z)) x y)) FV[expr] = {foo, z, y} References to bound variables do the expected thing. What about free variables? Under lexical/static scoping: look for textually/lexically/statically enclosing scopes, until find a binding; error otherwise Under dynamic scoping: look at caller, recursively (i.e., up call stack) to find the binding; error otherwise. Dynamic easy to implement (by accident!), but hard to program with. Static scoping supports programs using free vars in reasonable ways, where it's easy to identify the corresponding definition for each var reference, looking only at "local" code. Better modularity. But more expensive to implement. Bug in dynamic scoping: (define (member-fn fn elem lst) (find (lambda (e) (equal? e elem)) lst)) (define (find fn lst) (if (null? lst) #f (let ((elem (car lst))) (if (fn elem) lst (find fn (cdr lst)))))) member-fn now always returns its argument list! With static/lexical scoping, it doesn't matter how find is implemented, or what variables names it chooses to bind; the reference to elem in member-fn's nested lambda always references the formal of member-fn. Very common, to use a local lambda that references formals &/or locals of enclosing fn. Static scoping is a necessity. Support for nested procedures that can have free variables is called block structure. Algol had it, Pascal too, and some later languages. ---------------------------------------------------------------------------- 5.5. ** Returning Functions from Functions ** Functions can return functions, too. One kind of example: a wrapper function, that takes some args and returns a simpler function that takes the remaining arguments (define (make-member-fn eq-fn) (lambda (elem lst) (member-fn eq-fn elem lst))) (define member-equal? (make-member-fn equal?)) (define member-eq? (make-member-fn eq?)) (member-equal? ...) (member-eq? ...) We need static scoping to make this work: the nested lambda has eq-fn as a free var. But something even stranger is happening: the binding of eq-fn is gone when the lambda is eventually invoked! (The reference to eq-fn outlives its enclosing function call stack.) So the result of evaluating a lambda needs to remember the values of its free variables, to distinguish itself from other executions of the same lambda, and to allow it to have an lifetime independent of its enclosing call stack. The result is called a closure, which can be thought of as a pair of a piece of code and an *environment* which is a binding list. [Draw pictures.] Another fn returning a fn: compose (define (compose f g) (lambda (x) (f (g x)))) (map (compose double square) '(3 4 5)) A function in a data structure: (define (make-window-menu window) (list (cons 'open (lambda () (open-window window))) (cons 'move (lambda () (move-window window))) (cons 'close (lambda () (close-window window))) (cons 'iconify (lambda () (iconify-window window))))) (define (select-menu menu selection) (let* ((entry (assoc menu selection)) (action (cdr entry))) (action))) ... (define w1 (make-window ...)) (define w1-menu (make-window-menu w1)) ... ... (select-menu w1-menu selection) ...