We can turn to the environment model to see how
proceduresfunctions
and assignment can be used to represent objects with local state. As an
example, consider the
withdrawal processor from
section 3.1.1 created by calling the
procedurefunction
Figure 3.11
Figure 3.12
shows the result of
defining the
make-withdrawdeclaring the
make_withdrawprocedurefunction
in the
globalprogram
environment. This produces a
procedurefunction
object that contains a pointer to the
globalprogram
environment. So far, this is no different from the examples we have already
seen, except that
the body of the
procedure is itself a
lambda
expression.
the return expression in the body of the function is itself
a lambda expression.
Original
JavaScript
The interesting part of the computation happens when we apply the
procedurefunctionmake-withdrawmake_withdraw
to an argument:
Original
JavaScript
(define W1 (make-withdraw 100))
const W1 = make_withdraw(100);
We begin, as usual, by setting up an environment E1 in which the
formal parameter
balance is bound to the argument 100. Within
this environment, we evaluate the body of
make-withdraw,make_withdraw,
namely the
lambda expression. Thisreturn statement whose return expression is
a lambda expression. The evaluation of this lambda expression
constructs a new
procedurefunction
object, whose code is as specified by the
lambdalambda expression
and whose environment is E1, the environment in which the
lambdalambda expression
was evaluated to produce the
procedure.function.
The resulting
procedurefunction
object is the value returned by the call to
make-withdraw.make_withdraw.
This is bound to W1 in the
globalprogram
environment, since the
defineconstant declaration
itself is being evaluated in the
globalprogram
environment.
Figure 3.13
Figure 3.14
shows the resulting environment structure.
Original
JavaScript
Now we can analyze what happens when W1
is applied to an argument:
Original
JavaScript
(W1 50)
50
W1(50);
50
We begin by constructing a frame in which
amount, the
formal parameter of
W1, is bound to the argument 50. The crucial
point to observe is that this frame has as its enclosing environment not the
globalprogram
environment, but rather the environment E1, because this is the
environment that is specified by the W1procedurefunction
object. Within this new environment, we evaluate the body of the
procedure:function:
The resulting environment structure is shown in
figure 3.15.
figure 3.16.
The expression being evaluated references
both amount and
balance.
AmountThe variable amount
will be found in the first frame in the environment, and
balance will be found by following the
enclosing-environment pointer to E1.
Original
JavaScript
When the
set!assignment
is executed, the binding of balance in E1 is
changed. At the completion of the call to
W1, balance is 50,
and the frame that contains balance is still
pointed to by the
procedurefunction
object W1. The frame that binds
amount (in which we executed the code that
changed balance) is no longer relevant, since
the
procedurefunction
call that constructed it has terminated, and there are no pointers to that
frame from other parts of the environment. The next time
W1 is called, this will build a new frame that
binds amount and whose enclosing environment is
E1. We see that E1 serves as the place that holds the local
state variable for the
procedurefunction
object W1.
Figure 3.17
Figure 3.18
shows the situation after the call to W1.
Original
JavaScript
Observe what happens when we create a second withdraw object
by making another call to
make_withdraw:make_withdraw:
Original
JavaScript
(define W2 (make-withdraw 100))
const W2 = make_withdraw(100);
This produces the environment structure of
figure 3.19,
figure 3.20,
which shows
that W2 is a
procedurefunction
object, that is, a pair with some code and an environment. The environment
E2 for W2 was created by the call to
make-withdraw.make_withdraw.
It contains a frame with its own local binding for
balance. On the other hand,
W1 and W2 have the
same code: the code specified by the
lambdalambda
expression in the body of
make-withdrawmake_withdraw.[1] We see
here why W1 and W2
behave as independent objects. Calls to
W1 reference the state variable
balance stored in E1, whereas calls to
W2 reference the
balance stored in E2. Thus, changes to the
local state of one object do not affect the other object.
Original
JavaScript
Exercise 3.10
In the
make-withdrawmake_withdrawprocedure,function
the local variable balance is created as a
parameter of
make-withdraw.make_withdraw.
We could also create the local state variable
explicitly,
separately,
using
let,
what we might call an
immediately invoked lambda
expression
as follows:
Recall from section 1.3.2 that
let is simply syntactic sugar for a
procedurefunction
call:
(let ((var exp)) body)
is interpreted as an alternate syntax for
((lambda (var) body) exp)
The outer lambda expression is invoked immediately after it
is evaluated. Its only purpose is to create a local variable
balance and
initialize it to initial_amount.
Use the environment model to analyze this alternate version of
make_withdraw, drawing figures like the ones
above to illustrate the interactions
Show that the two versions of
make-withdrawmake_withdraw
create objects with the same behavior. How do the environment structures
differ for the two versions?
Black shows the environment structure of function in exercise 3.10
Green shows differences in environment structure of original
(where make_withdraw is replaced with the figure 3.9 version)
[1]
Whether
W1 and W2 share
the same physical code stored in the computer, or whether they each keep a
copy of the code, is a detail of the implementation. For the interpreter we
implement in chapter 4, the code is in fact shared.