灯下 登录
计算机科学 / SICP / subsection 中英对照

3.1.1 Local State Variables

To illustrate what we mean by having a computational object with time-varying

state, let us model the situation of withdrawing money from a bank account. We

will do this using a procedure withdraw, which takes as argument an

amount to be withdrawn. If there is enough money in the account to

accommodate the withdrawal, then withdraw should return the balance

remaining after the withdrawal. Otherwise, withdraw should return the

message Insufficient funds. For example, if we begin with $100 in the

account, we should obtain the following sequence of responses using

withdraw:

为了说明什么是具有随时间变化的状态的计算对象,我们来模拟从银行账户取款的情形。为此,我们将使用一个名为 withdraw 的过程,它接受要取出的金额作为参数。如果账户中有足够的钱满足取款请求,withdraw 应返回取款后的余额;否则,withdraw 应返回消息 Insufficient funds。例如,若账户中最初有 $100,则使用 withdraw 应得到如下一系列响应:

Observe that the expression (withdraw 25), evaluated twice, yields

different values. This is a new kind of behavior for a procedure. Until now,

all our procedures could be viewed as specifications for computing mathematical

functions. A call to a procedure computed the value of the function applied to

the given arguments, and two calls to the same procedure with the same

arguments always produced the same result.

注意,表达式 `(withdraw 25)` 被求值两次,却产生了不同的值。这是过程的一种新行为。到目前为止,我们的所有过程都可以看作是计算数学函数的规范:对过程的调用会计算出将该函数应用于给定参数所得到的值,对同一过程以相同参数的两次调用总会产生相同的结果。

To implement withdraw, we can use a variable balance to indicate

the balance of money in the account and define withdraw as a procedure

that accesses balance. The withdraw procedure checks to see if

balance is at least as large as the requested amount. If so,

withdraw decrements balance by amount and returns the new

value of balance. Otherwise, withdraw returns the

Insufficient funds message. Here are the definitions of balance

and withdraw:

为了实现 withdraw,我们可以使用变量 balance 来表示账户中的余额,并将 withdraw 定义为一个访问 balance 的过程。withdraw 过程首先检查 balance 是否至少与请求金额一样大:若是,withdraw 就将 balance 减去 amount 并返回 balance 的新值;否则,withdraw 返回 Insufficient funds 消息。下面是 balance 和 withdraw 的定义:

Decrementing balance is accomplished by the expression

对 balance 的递减由如下表达式完成:

This uses the set! special form, whose syntax is

这里用到了特殊形式 set!,其语法为:

Here ⟨name⟩ is a symbol and ⟨new-value⟩ is any expression.

Set! changes ⟨name⟩ so that its value is the result obtained by

evaluating ⟨new-value⟩. In the case at hand, we are changing

balance so that its new value will be the result of subtracting

amount from the previous value of balance.

其中 ⟨name⟩ 是一个符号,⟨new-value⟩ 是任意表达式。set! 改变 ⟨name⟩,使其值成为对 ⟨new-value⟩ 求值所得的结果。在当前情形中,我们改变 balance,使其新值成为从 balance 的原有值中减去 amount 所得的结果。

Withdraw also uses the begin special form to cause two

expressions to be evaluated in the case where the if test is true: first

decrementing balance and then returning the value of balance. In

general, evaluating the expression

withdraw 还使用了特殊形式 begin,以便在 if 测试为真时依次对两个表达式求值:先递减 balance,然后返回 balance 的值。一般而言,对表达式

causes the expressions ⟨

e

x

求值会使表达式 ⟨

e

x

p
1

⟩ through ⟨

e

x

⟩ 到 ⟨

e

x

p
k

⟩ to be evaluated

in sequence and the value of the final expression ⟨

e

x

⟩ 依次被求值,并将最后一个表达式 ⟨

e

x

p
k

⟩ to be

returned as the value of the entire begin form.

⟩ 的值作为整个 begin 形式的值返回。

Although withdraw works as desired, the variable balance presents

a problem. As specified above, balance is a name defined in the global

environment and is freely accessible to be examined or modified by any

procedure. It would be much better if we could somehow make balance

internal to withdraw, so that withdraw would be the only

procedure that could access balance directly and any other procedure

could access balance only indirectly (through calls to withdraw).

This would more accurately model the notion that balance is a local

state variable used by withdraw to keep track of the state of the

account.

尽管 withdraw 能按预期工作,但变量 balance 带来了一个问题。如上所述,balance 是定义在全局环境中的名字,任何过程都可以自由地查看或修改它。更理想的做法是,能够将 balance 设为 withdraw 的内部变量,使得 withdraw 成为唯一能直接访问 balance 的过程,而其他任何过程都只能间接访问 balance(通过调用 withdraw)。这样才能更准确地体现 balance 是 withdraw 用来追踪账户状态的局部状态变量这一概念。

We can make balance internal to withdraw by rewriting the

definition as follows:

我们可以通过如下方式重写定义,将 balance 设为 withdraw 的内部变量:

What we have done here is use let to establish an environment with a

local variable balance, bound to the initial value 100. Within this

local environment, we use lambda to create a procedure that takes

amount as an argument and behaves like our previous withdraw

procedure. This procedure—returned as the result of evaluating the

let expression—is new-withdraw, which behaves in precisely the

same way as withdraw but whose variable balance is not accessible

by any other procedure.

我们在这里所做的是:用 let 建立一个带有局部变量 balance 的环境,并将其绑定到初始值 100。在这个局部环境中,我们用 lambda 创建一个过程,它以 amount 为参数,行为与之前的 withdraw 过程相同。这个过程——作为对 let 表达式求值的结果返回——就是 new-withdraw。它的行为与 withdraw 完全相同,但其变量 balance 不能被任何其他过程访问。

Combining set! with local variables is the general programming technique

we will use for constructing computational objects with local state.

Unfortunately, using this technique raises a serious problem: When we first

introduced procedures, we also introduced the substitution model of evaluation

(1.1.5) to provide an interpretation of what procedure

application means. We said that applying a procedure should be interpreted as

evaluating the body of the procedure with the formal parameters replaced by

their values. The trouble is that, as soon as we introduce assignment into our

language, substitution is no longer an adequate model of procedure application.

(We will see why this is so in 3.1.3.) As a consequence, we

technically have at this point no way to understand why the new-withdraw

procedure behaves as claimed above. In order to really understand a procedure

such as new-withdraw, we will need to develop a new model of procedure

application. In 3.2 we will introduce such a model, together

with an explanation of set! and local variables. First, however, we

examine some variations on the theme established by new-withdraw.

将 set! 与局部变量结合使用,是我们构造具有局部状态的计算对象所采用的一般编程技术。不幸的是,使用这一技术引发了一个严重问题:当我们最初引入过程时,也引入了代换模型(1.1.5节)来解释过程应用的含义——我们说,应用一个过程应被解释为:用实际参数值替换形式参数后,对过程体进行求值。问题在于,一旦我们在语言中引入赋值,代换模型就不再是过程应用的充分模型了(我们将在 3.1.3节 看到原因)。因此,严格说来,我们此时还没有办法理解 new-withdraw 过程为何能按上述方式运行。为了真正理解像 new-withdraw 这样的过程,我们需要建立一个过程应用的新模型。在 3.2节,我们将引入这样一个模型,并对 set! 与局部变量加以解释。但在此之前,我们先考察围绕 new-withdraw 所建立的主题的若干变体。

The following procedure, make-withdraw, creates “withdrawal

processors.” The formal parameter balance in make-withdraw

specifies the initial amount of money in the account.

下面的过程 make-withdraw 用于创建"取款处理器"。make-withdraw 的形式参数 balance 指定了账户的初始金额。

Make-withdraw can be used as follows to create two objects W1 and

W2:

make-withdraw 可以按如下方式使用,以创建两个对象 W1 和 W2:

Observe that W1 and W2 are completely independent objects, each

with its own local state variable balance. Withdrawals from one do not

affect the other.

注意,W1 和 W2 是完全独立的对象,各自拥有自己的局部状态变量 balance。从其中一个取款不会影响另一个。

We can also create objects that handle deposits as well as withdrawals, and

thus we can represent simple bank accounts. Here is a procedure that returns a

“bank-account object” with a specified initial balance:

我们还可以创建既能处理存款又能处理取款的对象,从而表示简单的银行账户。下面的过程返回一个具有指定初始余额的"银行账户对象":

Each call to make-account sets up an environment with a local state

variable balance. Within this environment, make-account defines

procedures deposit and withdraw that access balance and an

additional procedure dispatch that takes a “message” as input and

returns one of the two local procedures. The dispatch procedure itself

is returned as the value that represents the bank-account object. This is

precisely the

message-passing style of programming that we saw in

2.4.3, although here we are using it in conjunction with the

ability to modify local variables.

Make-account can be used as follows:

每次调用 make-account 都会建立一个带有局部状态变量 balance 的环境。在这个环境中,make-account 定义了访问 balance 的过程 deposit 和 withdraw,以及一个额外的过程 dispatch,它接受一条"消息"作为输入并返回两个局部过程之一。dispatch 过程本身作为代表银行账户对象的值被返回。这正是我们在 2.4.3节 中见到的消息传递风格的程序设计,只不过这里我们将它与修改局部变量的能力结合使用。make-account 可以按如下方式使用:

Each call to acc returns the locally defined deposit or

withdraw procedure, which is then applied to the specified

amount. As was the case with make-withdraw, another call to

make-account

每次对 acc 的调用都返回局部定义的 deposit 或 withdraw 过程,随后该过程被应用于指定的金额。与 make-withdraw 的情形一样,再次调用 make-account

will produce a completely separate account object, which maintains its own

local balance.

将产生一个完全独立的账户对象,它维护着自己的局部 balance。

Racket #lang sicp
(withdraw 25)
75

(withdraw 25)
50

(withdraw 60)
"Insufficient funds"

(withdraw 15)
35
Racket #lang sicp
(define balance 100)

(define (withdraw amount)
 (if (>= balance amount)
 (begin (set! balance (- balance amount))
 balance)
 "Insufficient funds"))
Racket #lang sicp
(set! balance (- balance amount))
Racket #lang sicp
(set! ⟨name⟩ ⟨new-value⟩)
Racket #lang sicp
(begin ⟨exp₁⟩ ⟨exp₂⟩ … ⟨expₖ⟩)
Racket #lang sicp
(define new-withdraw
 (let ((balance 100))
 (lambda (amount)
 (if (>= balance amount)
 (begin (set! balance
 (- balance amount))
 balance)
 "Insufficient funds"))))
Racket #lang sicp
(define (make-withdraw balance)
 (lambda (amount)
 (if (>= balance amount)
 (begin (set! balance
 (- balance amount))
 balance)
 "Insufficient funds")))
Racket #lang sicp
(define W1 (make-withdraw 100))
(define W2 (make-withdraw 100))

(W1 50)
50

(W2 70)
30

(W2 40)
"Insufficient funds"

(W1 40)
10
Racket #lang sicp
(define (make-account balance)
 (define (withdraw amount)
 (if (>= balance amount)
 (begin (set! balance
 (- balance amount))
 balance)
 "Insufficient funds"))
 (define (deposit amount)
 (set! balance (+ balance amount))
 balance)
 (define (dispatch m)
 (cond ((eq? m 'withdraw) withdraw)
 ((eq? m 'deposit) deposit)
 (else (error "Unknown request:
 MAKE-ACCOUNT" m))))
 dispatch)
Racket #lang sicp
(define acc (make-account 100))

((acc 'withdraw) 50)
50

((acc 'withdraw) 60)
"Insufficient funds"

((acc 'deposit) 40)
90

((acc 'withdraw) 60)
30
Racket #lang sicp
(define acc2 (make-account 100))