510 likes | 626 Views
Closures and Streams. More on Evaluations. Scheme Expressions. Constants: numbers, booleans . Variables: names for values. Created using the special form define Special forms have special rules for evaluation. Cannot be redefined
E N D
Closures and Streams More on Evaluations
Scheme Expressions • Constants: numbers, booleans. • Variables: names for values. Created using the special form define • Special forms have special rules for evaluation. • Cannot be redefined • 15 “magic words”: and, begin, case, cond, define, do, if, lambda, let, let*, letrec, or, quasiquote, quote, set! • a special form is not a first-class object like a procedure • Combination: (operator operands) • "function calls" or • "procedure applications."
Mantras of Scheme • Every expression has a value • except for • errors, • infinite loops, and • define special form • Computing the value of a combination(e0 e1 e2 …) • Compute all sub-expressions (in any order) • Apply the value of the first (e0) to the values of the rest • Applicative Order • The value of a lambda expression is a procedure
Order Of Evaluation • The order of evaluation turns out to be of central importance for the strength of the functional paradigm. • In this lecture we will study the subject of evaluation order in the functional programming paradigm. This includes an introduction to infinite lists (streams) in Scheme.
Referential Transparency • (3*F(a, b) + b) *(3*F(a, b) - c) • let t be 3*F(a, b) in (t + b)*(t - c) • Are these two equivalent? Depends on the Details of F(x, y).
Referential Transparency • “Equals can be replaced by equals” Two equal expressions can substitute each other without affecting the meaning of a functional program • Makes it is possible to do natural program transformations without concern for side effects • Does not rely on a particular notion of equality • Reference equality, shallow equality and deep equality cannot be distinguished by functional means • Is a major contrast to imperative programming
Distance Between Points (define square (lambda (x) (* x x))) (define sum-of-squares (lambda (x y) (+ (square x) (square y)))) (define dist-between-points (lambda (x1 y1 x2 y2) (sqrt (sum-of-squares (- x1 x2) (- y1 y2)))))
Substitution Model of Evaluation • No assignments; i.e., no set! Or its equivalent. • To apply a compound procedure to arguments, evaluate the body of the procedure with each formal parameter replaced by the corresponding argument ((lambda (xy) (+ (square x) (square y))) (- 5 3)5) • (+ (square 2) (square 5)) • (+ (* 2 2) (* 5 5)) • (+ 4 25) • 29
Substitution Model Example • the evaluated expressions have "**" before and after “**” them. (dist-between-pts 1 1 4 5) (dist-between-pts 1 **1** 4 5) (dist-between-pts 1 **1** **4** 5) (dist-between-pts **1** **1** **4** 5) (**proc (x1 y1 x2 y2) (sqrt (sum-of-squares (- x1 x2) (- y1 y2)))** **1** **1** **4** 5) (**proc (x1 y1 x2 y2) (sqrt (sum-of-squares (- x1 x2) (- y1 y2)))** **1** **1** **4** **5**) (sqrt (sum-of-squares (- **1** **4**) (- **1** **5**))) (sqrt (sum-of-squares (**sub** **1** **4**) (- **1** **5**))) (sqrt (sum-of-squares **-3** (- **1** **5**))) (sqrt (sum-of-squares **-3** (**sub** **1** **5**))) (sqrt (sum-of-squares **-3** **-4**)) (sqrt (**proc (x y) (+ (square x) (square y))** **-3** **-4**)) (sqrt (+ (square **-3**) (square **-4**))) (sqrt (+ (square **-3**) (**proc (x) (* x x)** **-4**))) (sqrt (+ (square **-3**) (* **-4** **-4**))) (sqrt (+ (square **-3**) (**mul** **-4** **-4**))) (sqrt (+ (square **-3**) **16**)) (sqrt (**add** (square **-3**) **16**)) (sqrt (**add** (**proc (x) (* x x)** **-3**) **16**)) (sqrt (**add** (* **-3** **-3**) **16**)) (sqrt (**add** (**mul** **-3** **-3**) **16**)) (sqrt (**add** **9** **16**)) (sqrt **25**) (**sqrt** **25**) **5**
Applicative Order Evaluation • (rator rand1 rand2 rand3 ...) • evaluate all • rator rand1 rand2 rand3 ... • in any order • apply the first value rator to the rest rand1 … . • Unless it is a special form. • Scheme uses Applicative Order • This is the reason for having special forms. • call by value
Normal Order Evaluation • (rator rand1 rand2 rand3 ...) • Fully expand, then reduce. • apply the leftmost sub-expression to the rest and expand. • Argument sub-expressions get evaluated when necessary. • Don't evaluate operands until they are needed • call by name
Normal Order Example • Fully Expand: • (sum-of-squares (+ 5 1) (* 6 2)) • (**proc (x y) (+ (square x) (square y))** (+ 5 1) (* 6 2)) • (+ (square (+ 5 1)) (square (* 6 2))) • (+ (**proc (x) (* x x)** (+ 5 1)) (square (* 6 2))) • (+ (* (+ 5 1) (+ 5 1)) (square (* 6 2))) • (+ (* (+ 5 1) (+ 5 1)) (**proc (x) (* x x)** (* 6 2))) • (+ (* (+ 5 1) (+ 5 1)) (* (* 6 2) (* 6 2))) Notation: ** expansion **; also, ** reduction **
Normal Order Example • Fully Expanded, now reduce • (+ (* (+ 5 1) (+ 5 1)) (* (* 6 2) (* 6 2))) • (+ (* (**add** 5 1) (+ 5 1)) (* (* 6 2) (* 6 2))) • (+ (* (**add** **5** 1) (+ 5 1)) (* (* 6 2) (* 6 2))) • (+ (* (**add** **5** **1**) (+ 5 1)) (* (* 6 2) (* 6 2))) • (+ (* **6** (+ 5 1)) (* (* 6 2) (* 6 2))) • (+ (* **6** (+ 5 1)) (* (* **6** 2) (* 6 2))) • (+ (* **6** (+ 5 1)) (* (**mul** **6** 2) (* 6 2))) • (+ (* **6** (+ 5 1)) (* (**mul** **6** **2**) (* 6 2))) • (+ (* **6** (+ 5 1)) (* **12** (* 6 2))) • (+ (* **6** (+ 5 1)) (* **12** (* 6 **2**))) • (+ (* **6** (+ 5 1)) (* **12** (* **6** **2**))) • (+ (* **6** (+ 5 1)) (* **12** (**mul** **6** **2**))) • (+ (* **6** (+ 5 1)) (* **12** **12**)) • (+ (* **6** (+ 5 1)) (**mul** **12** **12**)) • (+ (* **6** (+ 5 1)) **144**) • (+ (* **6** (+ **5** 1)) **144**) • (+ (* **6** (+ **5** **1**)) **144**) • (+ (* **6** (**add** **5** **1**)) **144**) • (+ (* **6** **6**) **144**) • (+ (**mul** **6** **6**) **144**) • (+ **36** **144**) • (**add** **36** **144**) • **180**
Comments on Normal Order • Normal is not as efficient as applicative. • We needed to evaluate (+ 5 1) and (* 6 2) twice, instead of once each with applicative order. • Normal is more likely to terminate than applicative • (define p (lambda () (p))) • (define test (lambda (x y) (if (= x 0) 0 y))) • (test 0 (p)) • applicative order: infinite loop • normal order: returns 0.
Arbitrary Evaluation Order • Sub expressions can be evaluated in an arbitrary order provided that • no errors occur in sub expressions • the evaluation of every sub expression terminates • Sub expressions can be evaluated in parallel
Strict v. Non-Strict (define (inf-calc)(inf-calc)) ((lambda (x) 1) (inf-calc)) • Different evaluation orders give different 'results' • The number 1 • A non-terminating calculation • Two different semantics of function application: • Strict: A function call is well-defined if and only if all actual parameters are well-defined • Non-strict: A function call can be well-defined even if one or more actual parameters cause an error or an infinite calculation
Normal- v. Applicative-Order • Scheme and ML use applicative-order reduction • Haskell uses normal-order reduction • Normal-order reduction is more powerful than the applicative-order reduction • If an expression e somehow can be reduced to f in one or more steps, • e can be reduced to f by normal order reduction, but • not necessarily by applicative order reduction
Special Forms • Forms for which applicative order reduction would not make sense • (if b x y) • Depending on the value of b, either x or y are evaluated • It would often be harmful to evaluate both x and y before the selection • (define (fak n) (if (= n 0) 1 (* n (fak (- n 1))))) • (and x y z) • and evaluates its parameter from left to right • In case x is false, there is no need to evaluate y and z • Often, it would be harmful to evaluate y and z • (and (not (= y 0)) (even? (quotient x y)))
Substitution model specifies what to compute. (lambda (y) (+ 4 y)) Substitution model is inadequate for mutable data structures. Environment model computes it efficiently by only remembering those things that change. <(lambda (y) (+ x y)), [x := 4]> We need to distinguish location and contents of the location. Models
Environments in Evaluation ((lambda (xy) (+ (square x) (square y))) (- 5 3)5) = (+ (square x)(square y)) = (+ (* x x)(* x x)) = (+ 4 25) = 29 x=2,y=5 x=2,y=5 x=5,y=5
Closure • A closure is an implementation technique for representing procedures with free variables. • A closure is a data structure containing the argument expression and the environment
An 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))))
Higher-order Functions And Lists • Use of lists and generic higher-order functions (HOF) enable abstraction and reuse • Can replace customized recursive definitions with more readable definitions built using “library” functions • The HOF approach may be less efficient. • HOF Ex: map, filter, accumulate
enumerate integers i from 0 to n compute the i-thFibonacci number for each integer i filter them, selecting even ones accumulate the results using cons, starting with () Even Fibonacci Numbers
Even Fibonacci Numbers (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)))
Filter (define (filter predseq) (cond ((null? seq) ()) ((pred (car seq)) (cons (car seq) (filter pred (cdrseq)))) (else (filter pred (cdrseq)))))
Accumulate (define (accumulate op init seq) (if (null? seq) init (op (car seq) (accumulate op init (cdrseq))))) • Typically, (op init x) = (op x init) = x
Even Fibonacci Numbers (define (enum-interval low high) (if (> low high) () (cons low (enum-interval (+ low 1) high)))) (define (even-fibs n) (accumulate cons nil (filter even? (map fib (enum-interval 0 n)))))
Lazy Evaluation • Delaying evaluation of expressions until they are needed • Opposite: eager evaluation. • If the argument is evaluated before the body of the procedure is entered we say that the procedure is strict in that argument. • In a purely applicative-order language, all procedures are strict in each argument. • In a purely normal-order language, all procedures are non-strict in each argument, and primitive procedures may be either strict or non-strict. • Lazy evaluation is one evaluation strategy used to implement non-strict functions. • http://foldoc.org/lazy+evaluation
Delayed evaluation in Scheme • Scheme does not support normal-order reduction nor lazy evaluation. • Scheme has an explicit primitive which delays an evaluation • (delay expr) => promise R5RS: delay • (force promise) => value R5RS: force
Use of Non-Strictness • Non-strictness is useful in almost any constructor for data structures. • cons, in particular • E.g., compute the length of a list without knowing the values of the individual elements in the list. • A language can control the strictness of each argument. • (define (f a (b lazy) c (d lazy-memo)) ...) • Memoizing means that if an expression's value is needed more than once (i.e., it is shared), the result of the first evaluation is remembered and subsequent requests for it will return the remembered value.
Interpreter with Lazy Evaluation • Thunk • The delayed arguments/ operands are not evaluated; instead, they are transformed into objects called thunks. • Represented as a closure - a data structure containing • the argument expression and • the environment • An operand is frozen when it is passed unevaluated to the procedure • It is thawed when the procedure evaluates it. • The process of thawing is also called forcing.
Streams • Def: Streams Are Lazy Lists/ Sequences • As a data abstraction, streams are the same as lists. • Lazy Evaluation • Lists: cdr is evaluated at construction time. • Streams: cdr is evaluated at selection time • construct a stream only partially, and • pass the partial construction to the program that consumes the stream. • http://docs.racket-lang.org/srfi-std/srfi-41/srfi-41.html
Uses of Streams • 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.
Potentially Infinite Structures • (define s (cons 0 s)) • Solution: infinite sequence of zeros. (0 . (0. (0. (0. … )))) • Illegal in ordinary Scheme. • cf. Ada, Pascal,… type s = record car: integer; cdr: s end;
Recursive Winding And Unwinding • (0.(0.(0. … ))) • (0.Function which when executed generates next item of an infinite structure) • (0. ) • (0.(0. )) • (0.(0.(0. . . . )))
Stream Primitives • Constructor: stream-cons • Selectors • stream-car • stream-cdr • (stream-car (stream-cons x y)) = x • (stream-cdr (stream-cons x y)) = y • empty-stream • stream-null?
Stream Primitives Implemented (define the-empty-stream ()) (define stream-null? null?) (define stream-car car) (define (stream-cdr s) ((cdr s))) (define (stream-cons x s) (cons x (lambda () s))) (require srfi/41) SRFI = Scheme Requests For Implimentation http://docs.racket-lang.org/srfi-std/srfi-41/srfi-41.html
Stream Examples (define stream-zeros (cons 0 (lambda() stream-zeros))) (define (numbers-from n) (cons n (lambda ()(numbers-from (+ 1 n))))) (define stream-numbers (numbers-from 0))
stream-enum-interval (define (stream-enum-interval low high) (if (> low high) the-empty-stream (stream-cons low (stream-enum-interval (+ 1 low) high)))) • (stream-enumerate-interval 10000 1000000) • returns a stream represented as a pair • whose car is 10000 and • whose cdr is a promise to • enumerate the next number and • a promise to …
Stream Primitives -- More (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))))) Example (stream-car (stream-cdr (stream-filter prime? (stream-enum-interval 100 1000))))
Generate Fibonacci Numbers Evaluate only as much as is necessary. Efficient. (define (fibgen a b) (cons a (lambda () (fibgen b (+ a b))))) (define fibs (fibgen 0 1))
Generate Fibonacci Numbers 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)) ))
Factorial Revisited Tail recursion that can be automatically translated into an iteration. (define (trfac n) (letrec ((iter (lambda (i a) (if (zero? i) a (iter (- i 1) (* a i)))))) (iter n 1)))
Factorial using Assignments (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)))))) (iter))))
Factorial Stream (define (str n r) (cons r (lambda () (str (+ n 1) (* n r))))) (define sfac (str 1 1)) • (car ((cdr ((cdr ((cdrsfac))))))) • Demand driven generation of list elements. Caching/ Memoing (of r) necessary for efficiency. Avoids assignment.
The Sieve of Eratosthenes (define (sieve stream) (stream-cons (stream-car stream) (sieve (stream-filter (lambda (x) (not (divisible? x (stream-car stream)))) (stream-cdr stream)))))
Sieve Of Eratosthenes (define (divisible? x y) (= (remainder x y) 0)) (define (integers-starting-from n) (cons-stream n (integers-starting-from (+ n 1)))) (define primes (sieve (integers-starting-from 2)))