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 3.1.1 created by calling the
procedure
我们可以借助环境模型来了解,过程与赋值如何用来表示带有局部状态 (local state) 的对象。以 3.1.1 节中的“取款处理器”为例,它是通过调用过程
Let us describe the evaluation of
让我们描述下面这个表达式的求值过程:
followed by
随后再求值:
Figure 3.6 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 λ-expression.
Figure 3.6: Result of defining make-withdraw in the global environment.
图 3.6 展示了在全局环境中定义 `make-withdraw` 过程的结果。这产生了一个包含指向全局环境之指针的过程对象。到目前为止,这与我们已经见过的例子没有什么不同,只是该过程的过程体本身是一个 λ 表达式。
图 3.6:在全局环境中定义 `make-withdraw` 的结果。
The interesting part of the computation happens when we apply the procedure
make-withdraw to an argument:
计算中有趣的部分发生在将过程 `make-withdraw` 应用于一个实参时:
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
λ-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 3.7 shows the resulting environment structure.
Figure 3.7: Result of evaluating (define W1 (make-withdraw 100)).
Now we can analyze what happens when W1 is applied to an argument:
我们照例先建立环境 E1,其中形参 `balance` 被绑定到实参 100。在这个环境中,我们对 `make-withdraw` 的过程体——即那个 λ 表达式——求值。这将构造出一个新的过程对象,其代码由 lambda 指定,其环境则是 E1,即对 lambda 求值以产生该过程时所在的环境。所得的过程对象就是调用 `make-withdraw` 的返回值。由于 `define` 本身是在全局环境中求值的,这个返回值被绑定到全局环境中的 `W1`。图 3.7 展示了由此产生的环境结构。
图 3.7:对 `(define W1 (make-withdraw 100))` 求值的结果。
现在我们可以分析将 `W1` 应用于一个实参时发生了什么:
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 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:
我们先构造一个框架,将 `W1` 的形参 `amount` 绑定到实参 50。这里至关重要的一点是:该框架的外围环境不是全局环境,而是 E1,因为 `W1` 过程对象所指定的正是这个环境。在这个新环境中,我们对过程体求值:
The resulting environment structure is shown in Figure 3.8. 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.
Figure 3.8: Environments created by applying the procedure object W1.
由此产生的环境结构如图 3.8 所示。被求值的表达式同时引用了 `amount` 和 `balance`:`amount` 将在当前环境的第一个框架中找到,而 `balance` 则需沿着外围环境指针追溯到 E1 才能找到。
图 3.8:应用过程对象 `W1` 时所创建的各个环境。
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 3.9 shows the
situation after the call to W1.
Figure 3.9: Environments after the call to W1.
执行 `set!` 时,E1 中 `balance` 的绑定被修改。调用 `W1` 结束时,`balance` 变为 50,而包含 `balance` 的框架仍然被过程对象 `W1` 所指向。绑定了 `amount` 的那个框架(我们在其中执行了修改 `balance` 的代码)已不再有意义,因为构造该框架的过程调用已经终止,环境的其他部分也没有指向该框架的指针。下次调用 `W1` 时,将建立一个新框架来绑定 `amount`,其外围环境为 E1。可以看到,E1 充当了过程对象 `W1` 局部状态变量的“存放处”。图 3.9 展示了调用 `W1` 之后的状态。
图 3.9:调用 `W1` 之后的各个环境。
Observe what happens when we create a second “withdraw” object by making
another call to make-withdraw:
观察一下再次调用 `make-withdraw` 创建第二个“取款”对象时会发生什么:
This produces the environment structure of Figure 3.10, 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 λ-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.
Figure 3.10: Using (define W2 (make-withdraw 100)) to create a second object.
这产生了图 3.10 所示的环境结构,其中 `W2` 是一个过程对象,即由若干代码和一个环境构成的序对。`W2` 的环境 E2 是在调用 `make-withdraw` 时创建的,它包含一个拥有自己的 `balance` 局部绑定的框架。另一方面,`W1` 和 `W2` 具有相同的代码:即 `make-withdraw` 过程体中 λ 表达式所指定的代码。由此可以看出 `W1` 和 `W2` 为何表现为相互独立的对象。对 `W1` 的调用引用的是存储在 E1 中的状态变量 `balance`,而对 `W2` 的调用引用的是存储在 E2 中的 `balance`。因此,对一个对象局部状态的修改不会影响另一个对象。
图 3.10:使用 `(define W2 (make-withdraw 100))` 创建第二个对象。
(define (make-withdraw balance)
(lambda (amount)
(if (>= balance amount)
(begin (set! balance
(- balance amount))
balance)
"Insufficient funds"))) (define W1 (make-withdraw 100)) (W1 50)
50 (define W1 (make-withdraw 100)) (W1 50)
50 (if (>= balance amount)
(begin (set! balance (- balance amount))
balance)
"Insufficient funds") (define W2 (make-withdraw 100))