CS 342 Lecture -*- Outline -*- * introduction advert: functional programming good for abstraction but how efficient are functional programs? lazy evaluation aims to make functional programming efficient *automatically*! functions can represent infinite objects, lazy evaluation helps make it practical sets loos the full power of lambda (function abstraction) ** History and Goals 1940s church and others studying lambda calculus discover normal order evaluation 1976 Lazy evaluation invented by Friedman and Wise, also Henderson and Morris 1979, Turner invents SASL = St. Andrew's Static Language to explore lazy evaluation for functional programming ** the language like scheme, but without print, begin, while, and only top-level set only other change is the semantics... ** lazy evaluation don't evaluate an expression until it's value is needed demand-driven, normal order, delayed evaluation in Scheme and most languages (not Algol 60), arguments to functions evaluated before call is made cons evaluates its arguments (eager, applicative-order evaluation) ---------- (set ones (cons 1 ones)) ---------- gives error in scheme infinite list of ones in SASL -> ones (... ...) -> (car ones) 1 -> ones (1 ...) -> (car (cdr ones)) 1 -> ones (1 1 1 1 1 1 1 1 1 1 ^C *** examples **** searching for number at which a predicate holds page 153: e.g., (set pred (lambda (n) (> (* n (+ n n)) 100))) (set our-pred pred) what is (interval 6 8)? what is (find-val pred (interval 6 8) 'oops)? how to code sqr? why is code more reusable in fuctional style? how big a list does fun-srch-for construct? what to do about fun-srch-while? e.g., (set pred fermats-last-theorem-counterexample) if pred is unknown, how to construct list to search can we make an infinite list? **** fun-srch-while on page 156 (be lazy) consider (fun-srch-for 100) with our-pred consider fun-srch-while what is (car (ints-from 1))? what is cadr? what does find-val do? don't we get into infinite loop constructing (ints-from 1)? ** thunk = expression + environment sometimes called a suspension way to compute a value, should it be needed analogy: name + phonebook is a thunk for Don's telephone number analogy: like a parameterless closure but language distinguishes thunks from closures! *** application of a closure makes thunks for the argument expressions (set K (lambda (x) (lambda (y) x))) -> ((K 1) (oh-no)) 1 creates thunk for 1: <| 1, {} |>, where {} is global env applies K by binding x to the thunk <| 1, {} |> and evaluating (lambda (y) x) creates closure, k1: <<(lambda (y) x), {x |-> <| 1, {} |>}>> creates thunk for (oh-no): <| (oh-no), {} |> applies the closure k1, extending the environment {x |-> <| 1, {} |>} by binding y to the thunk <| (oh-no), {} |> and evaluating the expression x which demands the value of x, which forces the thunk <| 1, {} |>, yielding 1 so 1 is replaces <| 1, {} |> as the binding for x and 1 is returned *** application of cons makes thunks for its arguments -> (cdr (cons (oh-no) '())) '() go through this... *** thunks are not closures -> (car (cons (lambda (x) x) '())) closure = lambda expression + environment i.e., parameters, body + environment thunk = expression + environment closure protects its body from evaluation, until it is applied thunk is evaluated whenever it is referenced ** side-effects *** definition either output or changing the value of a variable some state that changes, divides time into before and after the change *** problems no way to tell when an expression will be evaluated by looking at it, have to look at where it's used hence print, begin, while, all but top-level set left out of SASL *** memoization (call by need) without side effects, each evaluation of a thunk gives same result so only need to evaluate it once analogy: remember each phone number you look up... when get value of (force) a thunk, replace thunk by value! can do this because value of thunk cannot change (set ones (cons 1 ones)) -> ones (... ...) -> (car ones) 1 -> ones (1 ...) -> (car (cdr ones)) 1 -> ones (1 1 1 1 1 1 1 1 1 1 ^C this optimizations of thunks can be used to omptimize programs ------------ ; naive Fibonacci code, slow even in SASL (set fib ; Nat -> Nat (lambda (n) (if (< n 2) n (+ (fib (- n 1)) (fib (- n 2)))))) ; taking advantage of SASL's memoization, ; using a list to return multiple values, fast (set fib-memoized (lambda (n) (add-pair (fib-below n)))) (set add-pair (lambda (ls) (+ (car ls) (car (cdr ls))))) (set list2 (lambda (n m) (cons n (cons m '())))) (set fib-below ; effect: returns list of (fib (- n 1)) and (fib (- n 2)) (lambda (n) (if (= n 0) (list2 0 0) (if (= n 1) (list2 0 1) ((lambda (fl) (list2 (add-pair fl) (car fl))) (fib-below (- n 1))))))) ; same as above, but using continuations to return multiple values ; fast as above (set fib-with-continuations (lambda (n) (fib-helper n +))) (set fib-helper (lambda (n cont) ; effect: calls cont with argments (fib (- n 1)) and (fib (- n 2)) ; thus returning (fib (- n 1)) and (fib (- n 2)) (if (= n 0) (cont 0 0) (if (= n 1) (cont 0 1) (fib-helper (- n 1) (lambda (fib-n-2 fib-n-3) (cont (+ fib-n-2 fib-n-3) fib-n-2))))))) ; tail recursive version, which computes from 0, fast even in Scheme (set fib-tail-recursive (lambda (n) (if (< n 2) n (fib-aux n 0 1)))) (set fib-aux (lambda (n fib-n-1 fib-n-2) (if (= n 1) fib-n-2 (fib-aux (- n 1) fib-n-2 (+ fib-n-1 fib-n-2))))) ; using the recurrence to build an infinite list of Fibonacci numbers ; saves results even across different calls! (set fib-next (lambda (fib-n-1 fib-n-2) (cons (+ fib-n-1 fib-n-2) (fib-next fib-n-2 (+ fib-n-1 fib-n-2))))) (set fibs (cons 0 (cons 1 (fib-next 0 1)))) (set nth (lambda (ls n) (if (= 0 n) (car ls) (nth (cdr ls) (- n 1))))) (set fib-from-fibs-list (lambda (n) (nth fibs n))) ; more elegant way to do the above, based on ; adding a shifted version of the list of Fibonacci numbers to itself ; 0 1 1 2 3 5 8 13 21 34 ... ;+ 1 1 2 3 5 8 13 21 34 ... ;= 1 2 3 5 8 13 21 34 ... (set infinite-list-add (lambda (l1 l2) (cons (+ (car l1) (car l2)) (infinite-list-add (cdr l1) (cdr l2))))) (set fibonacci-numbers (cons 0 (cons 1 (infinite-list-add fibonacci-numbers (cdr fibonacci-numbers))))) (set fib-from-list (lambda (n) (nth fibonacci-numbers n))) ------------ ** semantics *** strict functions a strict function evaluates (uses, demands) all its arguments + is strict, as are all value-ops except cons if is strict only in its first argument examples: and and or (short-curcuit evaluation) what about?: ---------- (set f (lambda (x y) x)) ---------- *** evaluation rules evaluate operator, make thunks out of operands see page 159 **** demand points use of a variable! e.g., return from function read-eval print loop e0 in (e0 e1 ...) e1 in (if e1 e2 e3) arguments to a value-op other than cons result of car or cdr nothing else! **** application of closures bind arguments to thunks evaluate expression can a thunk contain a thunk? what happens in ((lambda (x) ((lambda (y) y) x)) 3)? ** examples primes p. 163 draw picture of streams why it can't be done in Scheme, p. 164 explain same-fringe problem problem is that one can't always generate next item from the previous (need more context) what context is saved up by the SASL function? recursion schemes p. 166 (uncurry ((curry3 mapcar2) +)) plays role of infinite-list-add ------------- (set fibs (cons 0 (cons 1 (mapcar2 + fibs (cdr fibs))))) -------------