A thunk delays work and remembers its value.
A tagged mutable object can hold a suspended computation, then replace that representation with the value produced by its first force.
Which representation changes make call-by-need visible inside an evaluator?
- Represent delayed work as a tagged mutable vector
- Distinguish an unevaluated thunk from an evaluated thunk
- Force a suspended computation only when its value is demanded
- Memoize the first value for later demands
make-thunk stores a thunk tag, a zero-argument computation, and an empty result slot. Creating that vector does not call the computation, so even a body that would fail can remain harmless when nobody demands it.
force-it inspects the tag. On the first demand it calls the stored computation, changes the tag to evaluated-thunk, discards the computation, and stores the value. Later demands select the cached slot and perform no computation again. This exposes the representation change that a lazy evaluator normally hides behind argument handling.
(begin
(define (make-thunk computation)
(vector 'thunk computation #f))
(define (force-it object)
(if (eq? (vector-ref object 0) 'evaluated-thunk)
(vector-ref object 2)
(let ((value ((vector-ref object 1))))
(vector-set! object 0 'evaluated-thunk)
(vector-set! object 1 #f)
(vector-set! object 2 value)
value)))
(define calls 0)
(define delayed
(make-thunk
(lambda ()
(set! calls (+ calls 1))
(* calls 10))))
(list (force-it delayed)
(force-it delayed)
calls
(vector-ref delayed 0)))- Output
- —
- Value
- —
- Diagnostic
- —
The first program returns (10 10 1 evaluated-thunk). The second returns (thunk 0) without evaluating the division by zero.
In the first run, find one application of the stored computation and the vector mutations that replace the thunk representation. The second force reads the cached value. In the second run, confirm that no division application or calls assignment occurs.
Change the program before you read the hint.
Force delayed a third time in the first program. Predict the three values, calls count, and final tag before running it.
Show one hint
Once the tag is evaluated-thunk, force-it reads slot 2 without calling slot 1.