Lecture 19 Dynamic Scoping Lazy Evaluation 4 2

  • Slides: 55
Download presentation
Lecture 19 Dynamic Scoping Lazy Evaluation (4. 2. 1, 4. 2. 2) 1

Lecture 19 Dynamic Scoping Lazy Evaluation (4. 2. 1, 4. 2. 2) 1

Last lecture: Lexical Scoping Free variables in an application of procedure f get their

Last lecture: Lexical Scoping Free variables in an application of procedure f get their values from the procedure where f was defined (by the appropriate lambda special form). Also called static binding (define (foo x y) (lambda (z) (+ x y z))) (define bar (foo 1 2)) bar : (lambda (z) (+ 1 2 z)) (bar 3) 2

Lexical Scoping Environment Diagram (define (foo x y) (lambda (z) (+ x y z)))

Lexical Scoping Environment Diagram (define (foo x y) (lambda (z) (+ x y z))) (define bar (foo 1 2)) (bar 3) GE foo: p: x y body: (l (z) (+ x y z)) Will always evaluate (+ x y z) in a new environment inside the surrounding lexical environment. bar: E 1 x: 1 y: 2 p: z body: (+ x y z) E 2 z: 3 (+ x y z) | E 2 => 6 3

Alternative Model: Dynamic Scoping • Dynamic scope: – Look up free variables in the

Alternative Model: Dynamic Scoping • Dynamic scope: – Look up free variables in the caller's environment rather than the surrounding lexical environment • Example: (define x 11) (define bear (lambda (y) (+ x y))) (define (p x) (bear 20)) (p 9) => 29 4

Dynamic Scoping Environment Diagram (define x 11) (define (p x) (bear 20)) Will evaluate

Dynamic Scoping Environment Diagram (define x 11) (define (p x) (bear 20)) Will evaluate (+ x y) in an environment that extends the caller's environment. (define (bear y) (+ x y)) (p 9) GE x: 11 p: bear: E 1 p: x body: (bear 20) E 2 x: 9 y: 20 (+ x y) | E 2 => 29 p: y body: (+ x y) 5

A "Dynamic" Version of Scheme (define (d-eval exp env) (cond ((self-evaluating? exp) ((variable? exp)

A "Dynamic" Version of Scheme (define (d-eval exp env) (cond ((self-evaluating? exp) ((variable? exp) (lookup-variable-value exp env)). . . ((lambda? exp) (make-procedure (lambda-parameters exp) (lambda-body exp) '*no-environment*)) ; CHANGE: no env. . . ((application? exp) (d-apply (d-eval (operator exp) env) (list-of-values (operands exp) env)) ; CHANGE: add env (else (error "Unknown expression -- M-EVAL" exp)))) 6

A "Dynamic" Scheme – d-apply (define (d-apply procedure arguments calling-env) (cond ((primitive-procedure? procedure) (apply-primitive-procedure

A "Dynamic" Scheme – d-apply (define (d-apply procedure arguments calling-env) (cond ((primitive-procedure? procedure) (apply-primitive-procedure arguments)) ((compound-procedure? procedure) (eval-sequence (procedure-body procedure) (extend-environment (procedure-parameters procedure) arguments calling-env))) ; CHANGE: use calling env (else (error "Unknown procedure" procedure)))) 7

Implement lazy evaluation Beyond Scheme – designing language variants: Lazy evaluation • Complete conversion

Implement lazy evaluation Beyond Scheme – designing language variants: Lazy evaluation • Complete conversion – normal order evaluator • Upward compatible extension – lazy, lazy-memo 8

Normal Order (Lazy) Evaluation Alternative models for computation: • Applicative Order: • evaluate all

Normal Order (Lazy) Evaluation Alternative models for computation: • Applicative Order: • evaluate all arguments, then apply operator • Normal Order: • go ahead and apply operator with unevaluated argument subexpressions • evaluate a subexpression only when value is needed • to print • by primitive procedure 9

Limitations of Applicative Order Evaluation Suppose we wanted to create: (define (unless condition usual-value

Limitations of Applicative Order Evaluation Suppose we wanted to create: (define (unless condition usual-value exceptional-value) (if condition exceptional-value usual-value)) Which can be used in expressions such as: (unless (= b 0) (/ a b) (begin (display "exception: returning 0") 0)) This won't work in an applicative-order language! But will work with normal –order. 10

Strict vs. Non-Strict Arguments If the body of a procedure is evaluated before an

Strict vs. Non-Strict Arguments If the body of a procedure is evaluated before an argument has been evaluated we say that the procedure is non-strict in that argument. If the argument is evaluated before the body of the procedure is entered we say that the procedure is strict in that argument. Applicative order: strict in all arguments. Normal order: strict in arguments only in primitive procedures. 11

How to Implement Lazy Evaluation (define (foo x y)(+ x y)) (foo (+ 1

How to Implement Lazy Evaluation (define (foo x y)(+ x y)) (foo (+ 1 2) (+ 3 4)) Eval will not evaluate the parameters passed to apply! GE foo: E 1 p: x y body: (+ x y) x: “(+ 1 2)” y: “(+ 3 4)” How are these delayed parameters passed? Via the binding in the extended environment E 1! 12

How can we implement lazy evaluation? (define (l-apply procedure arguments env) ; changed (cond

How can we implement lazy evaluation? (define (l-apply procedure arguments env) ; changed (cond ((primitive-procedure? procedure) (apply-primitive-procedure (list-of-arg-values arguments env))) ((compound-procedure? procedure) (l-eval-sequence (procedure-body procedure) (extend-environment (procedure-parameters procedure) (list-of-delayed-args arguments env) (procedure-environment procedure)))) (else (error "Unknown proc" procedure)))) 13

Lazy Evaluation – l-eval • Most of the work is in l-apply; need to

Lazy Evaluation – l-eval • Most of the work is in l-apply; need to call it with: • actual value for the operator • just expressions for the operands • the environment. . . (define (l-eval exp env) (cond ((self-evaluating? exp). . . ((application? exp (l-apply (actual-value (operator exp) env) (operands exp) env)) (else (error "Unknown expression" exp)))) 14

Actual vs. Delayed Values (define (actual-value exp env) (force-it (l-eval exp env))) (define (list-of-arg-values

Actual vs. Delayed Values (define (actual-value exp env) (force-it (l-eval exp env))) (define (list-of-arg-values exps env) (if (no-operands? exps) '() (cons (actual-value (first-operand exps) env) (list-of-arg-values (rest-operands exps) env)))) (define (list-of-delayed-args exps env) (if (no-operands? exps) '() (cons (delay-it (first-operand exps) env) (list-of-delayed-args (rest-operands exps) env)))) 15

Representing Thunks • Abstractly – a thunk is a "promise" to return a value

Representing Thunks • Abstractly – a thunk is a "promise" to return a value when later needed ("forced") • Concretely – our representation: thunk exp env 16

Thunks – delay-it and force-it (define (delay-it exp env) (list 'thunk exp env)) (thunk?

Thunks – delay-it and force-it (define (delay-it exp env) (list 'thunk exp env)) (thunk? obj) (tagged-list? obj 'thunk)) (thunk-exp thunk) (cadr thunk)) (thunk-env thunk) (caddr thunk)) (define (force-it obj) (cond ((thunk? obj) (actual-value (thunk-exp obj) (thunk-env obj))) (else obj))) (define (actual-value exp env) (force-it (l-eval exp env))) 17

Lazy Evaluation – other changes needed • Example – need actual predicate value in

Lazy Evaluation – other changes needed • Example – need actual predicate value in conditional if. . . (define (l-eval-if exp env) (if (true? (actual-value (if-predicate exp) env)) (l-eval (if-consequent exp) env) (l-eval (if-alternative exp) env))) • Example – don't need actual value in assignment. . . (define (l-eval-assignment exp env) (set-variable-value! (assignment-variable exp) (l-eval (assignment-value exp) env) 'ok) 18

(l-eval ‘((lambda (x) (+ x x) ) (+ 1 1)) GE) (l-apply (actual-value ‘(lambda

(l-eval ‘((lambda (x) (+ x x) ) (+ 1 1)) GE) (l-apply (actual-value ‘(lambda (x) (+ x x)) GE) ‘((+1 1 )) GE) (force-it (l-eval ‘(lambda (x) (+ x x)) GE)) ‘(procedure ‘(x) ‘((+ x x)) GE) (l-apply ‘(procedure ‘(x) ‘(+ x x) GE) ‘((+1 1 )) (l-eval ‘(+ x x) E 1 GE) GE x : (‘thunk ‘(+1 1) GE) (l-apply (actual-value ‘+ E 1) ‘(x x) E 1) (l-apply ‘(primitive #[add]) ‘(x x) E 1) (apply-primitive-procedure ‘(primitive #[add]) (actual-value x E 1) ) (force-it (l-eval (force-it (‘thunk (actual-value ‘(+ (force-it (l-eval x E 1)) ‘(+ 1 1) GE)) 1 1) GE) ‘(+ 1 1) GE)) ==> 2 19

(l-eval ‘(define f (lambda (x y) x)) GE) (eval-definition ‘(define f (lambda (x y)

(l-eval ‘(define f (lambda (x y) x)) GE) (eval-definition ‘(define f (lambda (x y) x)) GE) (define-variable! ‘f (l-eval ‘(lambda (x y) x) GE) f: ‘(procedure ‘(x y) ‘(x) GE 20

(l-eval ‘(f 1 1) GE) (l-apply (actual-value ‘f GE) ‘(1 1) GE) (force-it (l-eval

(l-eval ‘(f 1 1) GE) (l-apply (actual-value ‘f GE) ‘(1 1) GE) (force-it (l-eval ‘f GE)) ==> ‘(procedure ‘(x y) ‘(x) GE (l-apply ‘(procedure ‘(x y) ‘(x) GE) ‘(1 1) (l-eval ‘x E 1) E 1 GE) x : (‘thunk 1 GE) y: (‘thunk 1 GE) ==> (‘thunk 1 GE) 21

(l-eval ‘(f (f 1 1) 1) GE) (l-apply (actual-value ‘f GE) ‘((f 1 1)

(l-eval ‘(f (f 1 1) 1) GE) (l-apply (actual-value ‘f GE) ‘((f 1 1) 1) GE (force-it (l-eval ‘f GE)) ‘(procedure ‘(x y) ‘(x) GE) (l-apply ‘(procedure ‘(x y) ‘(x) GE) ‘((f 1 1) 1) (l-eval ‘x E 1) E 1 GE) x : (‘thunk ‘(f 1 1) GE) y: (‘thunk 1 GE) ==> (‘thunk ‘(f 1 1) GE) Lets force this thunk. . 22

(force-it (‘thunk ‘(f 1 1) GE)) (actual-value ‘(f 1 1) GE) (force-it (l-eval ‘(f

(force-it (‘thunk ‘(f 1 1) GE)) (actual-value ‘(f 1 1) GE) (force-it (l-eval ‘(f 1 1) GE)) (force-it (l-apply (actual-value ‘f GE) ‘(1 1) GE)) (force-it (‘thunk 1 GE)) (actual-value 1 GE) (force-it (l-eval 1 GE)) ==> 1 23

Memo-izing Thunks • Idea: once thunk exp has been evaluated, remember it • If

Memo-izing Thunks • Idea: once thunk exp has been evaluated, remember it • If value is needed again, just return it rather than recompute • Concretely – mutate a thunk into an evaluated-thunk exp env evaluated- result thunk 24

Thunks – Memoizing Implementation (define (evaluated-thunk? obj) (tagged-list? obj 'evaluated-thunk)) (define (thunk-value evaluated-thunk) (cadr

Thunks – Memoizing Implementation (define (evaluated-thunk? obj) (tagged-list? obj 'evaluated-thunk)) (define (thunk-value evaluated-thunk) (cadr evaluated-thunk)) (define (force-it obj) (cond ((thunk? obj) (let ((result (actual-value (thunk-exp obj) (thunk-env obj)))) (set-car! obj 'evaluated-thunk) (set-car! (cdr obj) result) (set-cdr! (cdr obj) '()) result)) ((evaluated-thunk? obj) (thunk-value obj)) (else obj))) 25

Laziness and Language Design • We have a dilemma with lazy evaluation • Advantage:

Laziness and Language Design • We have a dilemma with lazy evaluation • Advantage: only do work when value actually needed • Disadvantages – not sure when expression will be evaluated; can be very big issue in a language with side effects – may evaluate same expression more than once • Memoization doesn't fully resolve our dilemma • Advantage: Evaluate expression at most once • Disadvantage: What if we want evaluation on each use? • Alternative approach: give programmer control! 26

Variable Declarations: lazy and lazy-memo • We want to extend the language as follows:

Variable Declarations: lazy and lazy-memo • We want to extend the language as follows: (lambda (a (b lazy) c (d lazy-memo)). . . ) • "a", "c" are strict variables (evaluated before procedure application • "b" is lazy; it gets (re)-evaluated each time its value is actually needed • "d" is lazy-memo; it gets evaluated the first time its value is needed, and then that value is returned again any other time it is needed again. 27

Controllably Memo-izing Thunks • thunk-memo • evaluated-thunk when forced – never gets memoized –

Controllably Memo-izing Thunks • thunk-memo • evaluated-thunk when forced – never gets memoized – first eval is remembered – memoized-thunk that has already been evaluated Thunk Memo exp env evaluated- result thunk 28

A new version of delay-it • Look at the variable declaration to do the

A new version of delay-it • Look at the variable declaration to do the right thing. . . (define (delay-it decl exp env) (cond ((not (declaration? decl)) (l-eval exp env)) ((lazy? decl) (list 'thunk exp env)) ((memo? decl) (list 'thunk-memo exp env)) (else (error "unknown declaration: " decl)))) 29

Changes to force-it (define (force-it obj) (cond ((thunk? obj) ; eval, but don't remember

Changes to force-it (define (force-it obj) (cond ((thunk? obj) ; eval, but don't remember it (actual-value (thunk-exp obj) (thunk-env obj))) ((memoized-thunk? obj) ; eval and remember (let ((result (actual-value (thunk-exp obj) (thunk-env obj)))) (set-car! obj 'evaluated-thunk) (set-car! (cdr obj) result) (set-cdr! (cdr obj) '()) result)) ((evaluated-thunk? obj) (thunk-value obj)) (else obj))) 30

Changes to l-apply • Key: in l-apply, only delay "lazy" or "lazy-memo" params •

Changes to l-apply • Key: in l-apply, only delay "lazy" or "lazy-memo" params • make thunks for "lazy" parameters • make memoized-thunks for "lazy-memo" parameters (define (l-apply procedure arguments env) (cond ((primitive-procedure? procedure). . . ) ; as before; apply on list-of-arg-values ((compound-procedure? procedure) (l-eval-sequence (procedure-body procedure) (let ((params (procedure-parameters procedure))) (extend-environment (map parameter-name params) (list-of-delayed-args params arguments env) (procedure-environment procedure))))) (else (error "Unknown proc" procedure)))) 31

Deciding when to evaluate an argument. . . • Process each variable declaration together

Deciding when to evaluate an argument. . . • Process each variable declaration together with application subexpressions – delay as necessary: (define (list-of-delayed-args var-decls exps env) (if (no-operands? exps) '() (cons (delay-it (first-variable var-decls) (first-operand exps) env) (list-of-delayed-args (rest-variables var-decls) (rest-operands exps) env)))) 32

Syntax Extensions – Parameter Declarations (define (first-variable var-decls) (car var-decls)) (define (rest-variables var-decls) (cdr

Syntax Extensions – Parameter Declarations (define (first-variable var-decls) (car var-decls)) (define (rest-variables var-decls) (cdr var-decls)) (define declaration? pair? ) (define (parameter-name var-decl) (if (pair? var-decl) (car var-decl)) (define (lazy? var-decl) (and (pair? var-decl) (eq? 'lazy (cadr var-decl)))) (define (memo? var-decl) (and (pair? var-decl) (eq? 'lazy-memo (cadr var-decl)))) 33

Summary • Lazy evaluation – control over evaluation models • Convert entire language to

Summary • Lazy evaluation – control over evaluation models • Convert entire language to normal order • Upward compatible extension – lazy & lazy-memo parameter declarations 34

How do we use this new lazy evaluation? • Our users could implement a

How do we use this new lazy evaluation? • Our users could implement a stream abstraction: (define (cons-stream x (y lazy-memo)) (lambda (msg) (cond ((eq? msg 'stream-car) x) ((eq? msg 'stream-cdr) y) (else (error "unknown stream msg" msg))))) (define (stream-car s) (s 'stream-car)) (define (stream-cdr s) (s 'stream-cdr)) 35

Go over this example at home: infinite streams We want to work with non-strict

Go over this example at home: infinite streams We want to work with non-strict cons, car, cdr. We work with our lazy evaluator Running over the non-lazy Scheme. We define: (define (cons x y) (lambda (m) (m x y))) (define (car z) (z (lambda (p q) p))) (define (cdr z) (z (lambda (p q) q))) 36

Our example; the ones stream. (define ones (cons 1 ones)) (car ones) We will

Our example; the ones stream. (define ones (cons 1 ones)) (car ones) We will do it in detail. We will be so tired after that, that we won’t need more examples… And we will also be running out of time… 37

So we start… The outer loop gets the input: exp= “(define ones (cons 1

So we start… The outer loop gets the input: exp= “(define ones (cons 1 ones))” It calls: (actual-value ‘(define ones (cons 1 ones)) GE) The actual-value from the outer loop, is the one sending the force, which wakes up all the sleeping values that are needed. It calls: (force-it (l-eval ‘(define ones (cons 1 ones)) GE)) 38

It’s a definition, so… (force-it (eval-definition ‘(define ones (cons 1 ones)) GE)) (force-it (define-variable!

It’s a definition, so… (force-it (eval-definition ‘(define ones (cons 1 ones)) GE)) (force-it (define-variable! (definition-variable ‘(define. . )) (l-eval (definition-value ‘(define. . )) GE)) (force-it (define-variable! ‘ones (l-eval ‘(cons 1 ones) GE)) 39

Eval again, and it’s … a procedure (force-it (define-variable! ‘ones (l-apply (actual-value ‘cons GE)

Eval again, and it’s … a procedure (force-it (define-variable! ‘ones (l-apply (actual-value ‘cons GE) ‘(1 ones) GE)) (actual-value ‘cons GE) (force-it (l-eval ‘cons GE)) (force-it #proc-. . ) #proc-. . (force-it (define-variable! ‘ones (l-apply #proc-cons ‘(1 ones) GE)) 40

L-apply gets into action… (force-it (define-variable! ‘ones (l-eval-sequence ((lambda (m) (m x y))) (extend-environment

L-apply gets into action… (force-it (define-variable! ‘ones (l-eval-sequence ((lambda (m) (m x y))) (extend-environment ‘(x y) (list-of-delayed-args ‘(1 ones) GE)) 41

The whole point: the arguments are delayed. (force-it (define-variable! ‘ones (l-eval-sequence ((lambda (m) (m

The whole point: the arguments are delayed. (force-it (define-variable! ‘ones (l-eval-sequence ((lambda (m) (m x y))) (extend-environment ‘(x y) (list (delay-it ‘ 1 GE) (delay-it ‘ones GE)) 42

I. e. , the arguments are thunks. (force-it (define-variable! ‘ones (l-eval (lambda (m) (m

I. e. , the arguments are thunks. (force-it (define-variable! ‘ones (l-eval (lambda (m) (m x y)) (extend-environment ‘(x y) (list 'thunk ‘ 1 GE) (list ‘thunk ‘ones GE)) 43

L-eval again. This time. . A procedure (force-it (define-variable! ‘ones (make-procedure (lambda-parameters ‘(lambda (m)

L-eval again. This time. . A procedure (force-it (define-variable! ‘ones (make-procedure (lambda-parameters ‘(lambda (m) (m x y))) (lambda-body ‘(lambda (m) m x y))) (extend-environment ‘(x y) (list 'thunk ‘ 1 GE) (list ‘thunk ‘ones GE)) 44

So. . • We create the new environment • We make the procedure •

So. . • We create the new environment • We make the procedure • We assign the procedure value to the variable ‘ones in GE • define-variable! Returns ‘ok • It’s not a thunk so force-it returns it as well. GE ones: E 1 x: y: (list 'thunk ‘ 1 GE) (list ‘thunk ‘ones GE)) p: m body: (m x y) 45

Now we try ‘(car ones) (actual-value ‘(car ones) GE) (force-it (l-eval ‘(car ones) GE))

Now we try ‘(car ones) (actual-value ‘(car ones) GE) (force-it (l-eval ‘(car ones) GE)) (force-it (l-apply #proc-car ‘ones GE)) (force-it (l-eval-sequence ‘((z (lambda (p q) p))) (extend-environment ‘(z) (list-of-delayed-args ‘ones GE))) 46

L-eval gets ready. . (force-it (l-eval ‘(z (lambda (p q) p)) (extend-environment ‘(z) (list

L-eval gets ready. . (force-it (l-eval ‘(z (lambda (p q) p)) (extend-environment ‘(z) (list 'thunk ‘ones GE))) l-eval looks at the expression, and it’s a compound procedure. 47

L-eval moves (force-it (l-eval (l-apply (actual-value ‘z (extend-env. . )) ‘(lambda (p q) p)

L-eval moves (force-it (l-eval (l-apply (actual-value ‘z (extend-env. . )) ‘(lambda (p q) p) (extend-environment ‘(z) (list 'thunk ‘ones GE))) 48

(actual-value ‘z (extend-env. . )) Actual-value opens to: (force-it (l-eval ‘z (extend-environment ‘(z) (list

(actual-value ‘z (extend-env. . )) Actual-value opens to: (force-it (l-eval ‘z (extend-environment ‘(z) (list 'thunk ‘ones GE))) (force-it (list ‘thunk ‘ones GE)) (actual-value ‘ones GE) It is NOT a thunk, so we get: The procedure ones the address of 49

Back to L-eval (force-it (l-eval (l-apply ones ‘(lambda (p q) p) (extend-environment …))) We

Back to L-eval (force-it (l-eval (l-apply ones ‘(lambda (p q) p) (extend-environment …))) We move the ball to l-apply… 50

L-eval again. . (force-it (l-eval ‘(m x y) (extend-environment ‘(m) (list-of-delayed-args ‘(lambda (p q)

L-eval again. . (force-it (l-eval ‘(m x y) (extend-environment ‘(m) (list-of-delayed-args ‘(lambda (p q) p) (extend-env…)) (extend-environment ‘(x y) E 2 (list 'thunk ‘ 1 GE) (list ‘thunk ‘ones GE)))))) 51

And l-eval again… L-eval gets into action. It calls: (l-apply (actual-value (operator exp) E

And l-eval again… L-eval gets into action. It calls: (l-apply (actual-value (operator exp) E 2) … This forces m, and assigns m the procedure (lambda (p q) p) At this stage x, y are still delayed. We now get: (l-apply (lambda (p q) p) ‘(x y) E 2) 52

And l-apply again… (l-eval ‘p (extend-environment ‘(p q) (list-of-delayed-args ‘(x y) E 2)) In

And l-apply again… (l-eval ‘p (extend-environment ‘(p q) (list-of-delayed-args ‘(x y) E 2)) In the extended environment E 2, p gets bounded to (list ‘thunk x E 1) And so this is what l-eval returns. (list ‘thunk x E 1) 53

We know force it. Obj = (list ‘thunk x E 1) (actual-value (thunk-exp obj)

We know force it. Obj = (list ‘thunk x E 1) (actual-value (thunk-exp obj) (thunk-env obj))) (actual-value x E 1) (force-it (l-eval x E 1)) (force-it (list ‘thunk ‘ 1 GE)) ‘ 1 We had to force the object twice! 54

Add-lists and the integers Now consider: (define (add-lists list 1 list 2) (cond ((null?

Add-lists and the integers Now consider: (define (add-lists list 1 list 2) (cond ((null? List 1) list 2) ((null? List 2) list 1) (else (cons (+ (car list 1) (car list 2)) (add-lists (cdr list 1) (cdr list 2)))))) (define ones (cons 1 ones)) (define integers (cons 1 (add-lists one integers)) 55