Closures and Streams cs7100(Prasad) L11Clos Great Reference: Abelson and Sussman : SICP Lecture introduces standard terms well-known in Functional Programming (languages) cs7100(Prasad) L11Clos
Contemporary Interest in Closures The concept of closures was developed in the 1960s and was first fully implemented in 1975 as a language feature in the Scheme programming language to support lexically scoped first-class functions. Project Lambda makes it easier to write code for multi-core processors by adding closures to the Java language and extending the Java API to support parallelizable operations upon streamed data. Rick Hickey’s Clojure (a dialect of LISP for Java platform) is a pure functional language with support for rich set of data structures, and constructs for concurrent programming. cs7100(Prasad) L11Clos
Models of Evaluation Substitution-based (define (square x) (* x x)) ((lambda (x y) (+ (square x) (square y))) (- 5 3) 5) (+ (square 2) (square 5)) (+ (* 2 2) (* 5 5)) (+ 4 25) 29 Equational reasoning by symbol manipulation; Arithmetic computation summarized without explicit rule invocation cs7100(Prasad) L11Clos
Expression Evaluation Options To evaluate: (operator operand1 operand2 operand3 ...) Applicative-Order Evaluation (call by value) evaluate each of the sub-expressions. apply the leftmost result to the rest. Normal-Order Evaluation (call by name) apply the leftmost (lambda) sub-expression to the rest and expand. (Argument sub- expressions get evaluated when necessary.) These two mechanisms differ substantially for the following case: ( (lambda (x) 5) ( (lambda (x) (x x)) (lambda (x) (x x)) ) ) When computation terminates, both approaches have give the same value. However, normal order evaluation has more propensity to terminate than application order evaluation. (E.g., an argument evaluation, whose value is actually not needed, may not terminate.) Work of Alonzo Church in the 30s cs7100(Prasad) L11Clos
Models of Evaluation Environment-based ((lambda (x y) (+ (square x) (square y))) (- 5 3) 5) (+ (square x) (square y)) x=2,y=5 (+ (* x x) x=2,y=5 (* x x) ) x=5,y=5 (+ 4 25) 29 Supports code reuse. Substitution model specifies what to compute. Environment model computes it efficiently by only remembering those things that change. APPLICATIVE ORDER EVALUATION -- recursion no problem in substitution model cs7100(Prasad) L11Clos
An extended example (define square (lambda (x) (* x x))) (define sum-of-squares (lambda (x y) (+ (square x) (square y)))) (define f (lambda (a) (sum-of-squares (+ a 1) (* a 2)))) (define (g) (let ((z 25)) z)) (g) 25 cs7100(Prasad) L11Clos
Initial Global Environment Heap storage + Garbage collection vs Stack-based storage cs7100(Prasad) L11Clos
Executing (f 5) and (sum-of-squares 6 10) Ref: SICP Static scoping: tree of environments Dynamic scoping: linear stack of environments Need for dynamic allocation and garbage collection => going beyond stack-based storage allocation-deallocation ------------------ Refer to Queue-as-an-object code to see an example of closure : local variables in create() outlive its invocation as the variables are present in the other methods (closures) Only when all the methods are destroyed is the GC allowed to clean them … cs7100(Prasad) L11Clos
Delayed Evaluation : THUNKS (define x (* 5 5)) x 25 (define y (lambda () (y) Partial Evaluation : CURRYING (define add (lambda (x) (lambda (y) (+ x y))) (define ad4 (add 4)) (ad4 8) 12 (define x 5) (define y (* x x)) (define x 6) y = 25 (define (y) (* x x)) (y) = 36 ML functions are inherently curried. Can consume arguments lazily. ------------------ Ad4 retains the binding to x as 4. In stack-based implementation, Activation record x-4 would have been released when add-execution terminates. Here, it is allocated on heap for eventual descrution by the garbage collector. ----------------- cs7100(Prasad) L11Clos
Closure and Models < (lambda (y) Substitution (lambda (y) (+ 4 y) ) Substitution model is inadequate for mutable data structures. Environment < (lambda (y) (+ x y)) , [x <- 4] > Need to distinguish location and contents of the location. cs7100(Prasad) L11Clos
Modular Designs with Lists cs7100(Prasad) L11Clos
Higher-order functions and lists Use of lists and generic higher-order functions enable abstraction and reuse Can replace customized recursive definitions with more readable definitions built using “library” functions The HOF approach may be less efficient. Promotes MODULAR DESIGNS – improves programmer productivity Modular Designs using Lists cs7100(Prasad) L11Clos
(define (even-fibs n) (define (next k) (if (> k n) ’() (let ((f (fib k))) (if (even? f) (cons f (next (+ k 1))) (next (+ k 1)) )) )) (next 0)) Take a number n and construct a list of first n even Fibonacci numbers. (define (fib n) (cond ((zero? n) 0) ((= n 1) 1) (else (+ (fib (- n 1)) (fib (- n 2)))) ) cs7100(Prasad) L11Clos
Abstract Description enumerate integers from 0 to n compute the Fibonacci number for each integer filter them, selecting even ones accumulate the results using cons, starting with () Instead of using dissimilar customized recursive definitions for the problems, understand their similarities, and exploit it by defining and reusing common primitives. cs7100(Prasad) L11Clos
(define (filter pred seq) (cond ((null? seq) ’()) ((pred (car seq)) (cons (car seq) (filter pred (cdr seq)))) (else (filter pred (cdr seq))) )) (define (accumulate op init seq) (if (null? seq) init (op (car seq) (accumulate op init (cdr seq))) Typically, init and op in accumulate are connected. init op x = x op init = x cs7100(Prasad) L11Clos
(enum-interval 0 n))))) (define (enum-interval low high) (if (> low high) ’() (cons low (enum-interval (+ low 1) high)) )) (define (even-fibs n) (accumulate cons ’() (filter even? (map fib (enum-interval 0 n))))) Accumulate part is redundant in this specific case. cs7100(Prasad) L11Clos
Streams: Motivation Streams simulated in Scheme and ML using closures. Streams are naturally supported through lazy evaluation in Haskell. For a classic account of streams refer to SICP (Abelson and Sussman) that clearly explains the following points: 1. Sequences and higher-order functions such as accumulate, map, filter, etc contribute to abstraction. 2. The need to support objects with states and assignments to have modular designs. (Problems with equality and sequencing.) 3. Streams as an alternative means to represent states through histories of values but without assignment. cs7100(Prasad) L11Clos
Modeling real-world objects (with state) and real-world phenomena Use computational objects with local variables and implement time variation of states using assignments Alternatively, use sequences to model time histories of the states of the objects. Possible Implementations of Sequences Using Lists Using Streams Delayed evaluation (demand-based evaluation) useful (necessary) when large (infinite) sequences are considered. Electrical engineering signals : VHDL cs7100(Prasad) L11Clos
Streams : Equational Reasoning (define s (cons 0 s)) Illegal. (Solution: infinite sequence of 0’s.) (0 . (0. (0. (0. … )))) (cf. Ada, Pascal,…) type s = record car : integer; cdr : s end; How do we represent potentially infinite structures? cs7100(Prasad) L11Clos
(0.(0.(0. … ))) (0. Function which when (0. ) (0. ) (0. . . . ) executed generates an infinite structure ) Recursive winding and unwinding (0. ) (0. ) (0. . . . ) cs7100(Prasad) L11Clos
>(define stream-car car) >(define (stream-cdr s) ( (cdr s) ) ) Unwrap by executing the second. >(define stream-zeros (cons 0 (lambda() stream-zeros) ) ) Wrap by forming closure (thunk). cs7100(Prasad) L11Clos
(stream-cdr stream-zeros) ) >(define (numbers-from n) (cons n >(stream-car (stream-cdr stream-zeros) ) >(define (numbers-from n) (cons n (lambda () (numbers-from (+ 1 n)) ))) >(define stream-numbers (numbers-from 0) ) cs7100(Prasad) L11Clos
Recapitulating Stream Primitives (define stream-car car) (define (stream-cdr s) ( (cdr s) ) ) (define (stream-cons x s) (cons x ( lambda ( ) s) ) ) (define the-empty-stream ’() ) (define stream-null? null?) These functions enable creation of streams with finite number of elements. For infinite streams, we need to use the technique given earlier for the seed-stream. cs7100(Prasad) L11Clos
(define (stream-filter p s) (cond ((stream-null? s) the-empty-stream) ((p (stream-car s)) (stream-cons (stream-car s) (stream-filter p (stream-cdr s)))) (else (stream-filter p (stream-cdr s))) )) (define (stream-enum-interval low high) (if (> low high) the-empty-stream (stream-cons low (stream-enum-interval (+ 1 low) high)))) cs7100(Prasad) L11Clos
(stream-enum-interval 100 1000)))) (stream-car (stream-cdr (stream-filter prime? (stream-enum-interval 100 1000)))) (define (fibgen f1 f2) (cons f1 (lambda () (fibgen f2 (+ f1 f2))) )) (define fibs (fibgen 0 1)) Evaluate only as much as is necessary. Efficient. The following implementation is incorrect because of call by value evaluation. It would be okay in a lazy language. (define (fibgen f1 f2) (stream-cons f1 (fibgen f2 (+ f1 f2)) )) cs7100(Prasad) L11Clos
Factorial Revisited (define (trfac n) (letrec ( (iter (lambda (i a) (if (zero? i) a (iter (- i 1) (* a i))))) ) (iter n 1) tail recursive factorial that can be automatically translated into an iteration. cs7100(Prasad) L11Clos
(define (ifac n) (let (( i n ) ( a 1 )) (letrec ( (iter (lambda () (if (zero? i) a (begin (set! a (* a i)) (set! i (- i 1)) (iter) )) ) (define (fac n) (let (( i n ) ( a 1 )) (letrec ( (iter (lambda () (if (zero? i) a (begin (set! i (- i 1)) (set! a (* a i)) (iter) )) ) (fac 4) = 0 // ERROR The parallel assignment to formals is now made sequential. Because one of the formal effects the other, it is important that old values of a and i be used. Hence the ordering is constrained. There is also potential for bugs in rewriting tail-recursion as the corresponding iteration if ordering is messed-up. cs7100(Prasad) L11Clos
Factorial Stream (define (str n r) (cons r (lambda () (str (+ n 1) (* n r)) ) (define sfac (str 1 1)) (car ((cdr ((cdr ((cdr sfac)) )) )) ) … (stream-cdr … ) Demand driven generation of list elements. Caching/Memoing necessary for efficiency. Avoids assignment. str (“n” , “factorial n-1” ) We remember factorial n-1 to minimize computation. 1,1,2,6,24,… To compute the i-th factorial, this seems only constant factor inefficient than the iterative loop for the amount of computation. cs7100(Prasad) L11Clos