Title: Review: Dynamic Scoping
1Lecture 20
- Review Dynamic Scoping
- Today Lazy Evaluation (4.2.1, 4.2.2)
2Last 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
3Lexical Scoping Environment Diagram
(define (foo x y) (lambda (z) ( x y z)))
Will always evaluate ( x y z) in a new
environment inside the surrounding lexical
environment.
(define bar (foo 1 2))
(bar 3)
4Alternative 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) gt 29
5Dynamic Scoping Environment Diagram
(define x 11)
(define (p x) (bear 20))
Will evaluate ( x y) in an environment that
extendsthe caller's environment.
(define (bear y) ( x y))
(p 9)
x11
( x y) E2gt 29
6A "Dynamic" Version of Scheme
- (define (d-eval exp env)
- (cond
- ((self-evaluating? exp) 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)
- env)) CHANGE add env
- (else (error "Unknown expression -- M-EVAL"
exp))))
7A "Dynamic" Scheme d-apply
- (define (d-apply procedure arguments calling-env)
- (cond ((primitive-procedure? procedure)
- (apply-primitive-procedure 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))))
8Implement lazy evaluation
- Beyond Scheme designing language variants
- Lazy evaluation
- Complete conversion normal order evaluator
- Upward compatible extension lazy, lazy-memo
9Normal 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
10Limitations 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 Lazy Evaluation!
11Strict vs. Non-Strict Arguments
- If the body of a procedure is entered 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.
12How to Implement Lazy Evaluation
(define (foo x y)( x y))
Eval will not evaluate the parameters passed to
apply!
(foo ( 1 2) ( 3 4))
How are these delayed parameters passed? Via the
binding in the extended environment E1!
13How can we implement lazy evaluation?
- (define (l-apply procedure arguments env)
changed - (cond ((primitive-procedure? procedure)
- (apply-primitive-procedure
- 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))))
14Lazy 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) exp)
- ...
- ((application? exp
- (l-apply (actual-value (operator exp)
env) - (operands exp)
- env))
- (else (error "Unknown expression" exp))))
15Actual 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))))
16Representing Thunks
- Abstractly a thunk is a "promise" to return a
value when later needed ("forced")
17Thunks delay-it and force-it
- (define (delay-it exp env) (list 'thunk exp env))
- (define (thunk? obj) (tagged-list? obj 'thunk))
- (define (thunk-exp thunk) (cadr thunk))
- (define (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)))
18Lazy 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)
- env)
- 'ok)
19(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 )) GE) (l-eval ( x x) E1)
(l-apply (actual-value E1) (x x) E1) (l-apply
(primitive add) (x x) E1) (apply-primitive-pr
ocedure (primitive add)
(actual-value x E1)(actual-value x E1)
) (force-it (l-eval x E1)) (force-it (thunk
( 1 1) GE)) (actual-value ( 1 1)
GE) (force-it (l-eval ( 1 1) GE)) gt 2
20(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) GE)
21(l-eval (f 1 1) GE) (l-apply (actual-value f
GE) (1 1) GE) (force-it (l-eval f GE)) gt
(procedure (x y) (x) GE) (l-apply (procedure
(x y) (x) GE) (1 1) GE) (l-eval x E1)
gt (thunk 1 GE)
22(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) GE) (l-eval x E1)
gt (thunk (f 1 1) GE)
Lets force this thunk ..
23(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)) gt 1
24Memo-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
25Thunks 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)))
26Laziness 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!
27Variable 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.
28Controllably Memo-izing Thunks
- thunk never gets memoized
- thunk-memo first eval is remembered
- evaluated-thunk memoized-thunk that has
already been evaluated
Thunk Memo
env
exp
29A 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))))
30Change 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)))
31Changes 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))))
32Deciding 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))))
33Syntax 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) 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))))
34Summary
- Lazy evaluation control over evaluation models
- Convert entire language to normal order
- Upward compatible extension
- lazy lazy-memo parameter declarations
35How 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))