Advanced Methods in Programming Languages (201-2-4411-01)
Notes Week 6 - Michael Elhadad
Building Interpreters (4): Call by Need
When Passing parameters by need we can delay the computation of
parameters until they are actually used inside the procedure. This is
apparently the best of both worlds - between applicative order and
normal order computation.
In practice, call-by-need has 2 problems:
- The overhead of argument passing is very high (construction of thunks)
- The order of computation of actual parameters is not predictable anymore.
This does not combine well that languages which allow side effects
(assignments).
Call by Need: L7
In call-by-value, when computing the value of an application expression,
we proceed as follows:
- Compute the value of the actual parameters
- Extend the closed environment by binding the formal parameters of the closure
to the value of the actual parameters
- Compute the body of the closure in the extended environment
In call-by-need, in contrast, we do not compute the value of
actual parameters when computing an application expression.
Instead, we bind the formal parameter to a special construct - called a thunk
which delays the computation of the actual parameter until it is actually needed -
that is, until the formal parameter is used as a varref expression in a
computed expression.
To allow for this behavior, we extend the target datatype - it can now be:
;; Define targets for call-by-reference
(define-datatype target target?
(direct-target (expval expval?))
(indirect-target (ref ref-to-direct-target?))
(thunk-target (exp exp?)
(env env?)))
;; A thunk is considered a direct target
(define ref-to-direct-target?
(lambda (x)
(and (reference? x)
(cases reference x
(a-ref (pos vec)
(cases target (vector-ref vec pos)
(direct-target (v) #t)
(indirect-target (v) #f)
(thunk-targer (exp env) #t)))))))
;; Deref taking into account indirect targets and thunks
(define deref
(lambda (ref)
(cases target (primitive-deref ref)
(direct-target (expval) expval)
(indirect-target (ref1)
(cases target (primitive-deref ref1)
(direct-target (expval) expval)
(indirect-target (p) (error "Illegal reference"))
(thunk-target (exp env) (eval-thunk ref1))))
(thunk-target (exp env) (eval-thunk ref)))))
;; Implement memoization: a thunk is only computed once
(define eval-thunk
(lambda (ref)
(let ((target (primitive-deref ref)))
(cases target target
(thunk-target (exp env)
(let ((expval (eval-exp exp env)))
(begin
(primitive-setref! ref (direct-target expval))
expval)))
(else (error "Bad thunk"))))))
(define setref!
(lambda (ref expval)
(let ((ref (cases target (primitive-deref ref)
(direct-target (expval1) ref)
(indirect-target (ref1) ref1)
(thunk-targer (exp env) ref))))
(primitive-setref! ref (direct-target expval)))))
Thunk-targets are created in eval-rand according to the following rules:
;; Procedure application: we compute new bindings using eval-rand
;; Special treatment for var-ref parameters - do not create reference chains.
;; Simple computations are performed immediately (lit and proc)
;; Other computations are delayed as thunks
(define eval-rand
(lambda (rand env)
(cases expr rand
(var-exp (var)
(indirect-target
(let ((ref (apply-env-ref env var)))
(cases target (primitive-deref ref)
(direct-target (expval) ref)
(thunk-target (exp env) ref)
(indirect-target (ref1) ref1)))))
(lit (datum) (direct-target datum))
(proc (formals body) (direct-target (closure formals body env)))
(else (thunk-target rand env)))))
Last modified Mar 31, 2003
Michael Elhadad