We can turn to the environment model to see how procedures and
assignment can be used to represent objects with local state. As an
example, consider the ``withdrawal processor'' from
section
created by calling the
procedure
(define (make-withdraw balance)
(lambda (amount)
(if (>= balance amount)
(begin (set! balance (- balance amount))
balance)
"Insufficient funds")))
Let us describe the evaluation of
(define W1 (make-withdraw 100))followed by
(W1 50) 50Figure
shows the result of defining the
make-withdraw procedure in the global environment. This produces a
procedure object that contains a pointer to the global 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 interesting part of the computation happens when we apply the procedure make-withdraw to an argument:
(define 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, namely the lambda expression. This constructs a new procedure object, whose code is as specified by the lambda and whose environment is E1, the environment in which the lambda was evaluated to produce the procedure. The resulting procedure object is the value returned by the call to make-withdraw. This is bound to W1 in the global environment, since the define itself is being evaluated in the global environment. Figure
shows the
resulting environment structure.
Now we can analyze what happens when W1 is applied to an argument:
(W1 50) 50We 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 global environment, but rather the environment E1, because this is the environment that is specified by the W1 procedure object. Within this new environment, we evaluate the body of the procedure:
(if (>= balance amount)
(begin (set! balance (- balance amount))
balance)
"Insufficient funds")
The resulting environment structure is shown in
figure
. The expression being evaluated references
both amount and balance. Amount will be found in
the first frame in the environment, while balance will be found
by following the enclosing-environment pointer to E1.
When the set! 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 procedure object W1. The frame
that binds amount
(in which we executed the code that changed balance) is no longer
relevant, since the procedure 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 procedure object W1. Figure
shows the situation after the call to W1.
Observe what happens when we create a second ``withdraw'' object by making another call to make-withdraw:
(define W2 (make-withdraw 100))This produces the environment structure of figure
, which shows
that W2 is a procedure object, that is, a pair with some code
and an environment. The environment E2 for W2 was created by
the call to 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 lambda
expression in the body of make-withdraw.
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.
Exercise. In the make-withdraw procedure, the local variable balance is created as a parameter of make-withdraw. We could also create the local state variable explicitly, using let, as follows:
(define (make-withdraw initial-amount)
(let ((balance initial-amount))
(lambda (amount)
(if (>= balance amount)
(begin (set! balance (- balance amount))
balance)
"Insufficient funds"))))
Recall from section
that let is simply
syntactic sugar for a procedure call:
(let ((var exp)) body)is interpreted as an alternate syntax for
((lambda (var) body) exp)Use the environment model to analyze this alternate version of make-withdraw, drawing figures like the ones above to illustrate the interactions
(define W1 (make-withdraw 100))Show that the two versions of make-withdraw create objects with the same behavior. How do the environment structures differ for the two versions?(W1 50)
(define W2 (make-withdraw 100))