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?
(define W2 (make-withdraw 100))