The key point here is that when we're evaluating (+ 2 a)
the a
is not a thunk, it's just a symbol that will be looked up in the environment, and whose value is a thunk. And after eval
returns the thunk, force-it
will take care of forcing its value. Let's step through the process.
The only argument that gets delayed in your example is the 0
, at the time of invoking add
, but even so that value will get forced eventually by list-of-arg-values
- think of it, at some point all the procedure applications will lead to apply-primitive-procedure
, and that's the point where we force the thunks.
If we perform a trace at the time of calling list-of-arg-values
, the values passed to actual-value
are 2
and a
in that order. The 2
just evaluates to itself, no problem there. Let's see what happens with the a
; this snippet in actual-value
:
(eval exp env)
... will receive the symbol a
as its exp
(a variable), returning the associated thunk after looking it up in the environment (remember: when we extended the environment in apply
, we created bindings between variables and thunks), and after that force-it
will receive a thunk as the result of calling eval
:
(force-it (eval exp env)))
... and force-it
knows how to evaluate the thunk, in the (thunk? obj)
case. And that's it! finally we obtain 0
, the actual value.