# 3.1.1局部状态变量

> 译者： [https://sicp.comp.nus.edu.sg/chapters/49](https://sicp.comp.nus.edu.sg/chapters/49)

为了说明具有时变状态的计算对象的含义，让我们对从银行帐户提取资金的情况进行建模。 我们将使用函数`withdraw`进行此操作，该函数将要提取的`amount`作为参数。 如果帐户中有足够的钱来容纳提款，则`withdraw`应退还提款后的余额。 否则，`withdraw`应返回消息_资金不足。_ 例如，如果我们以帐户中的 &lt;dollar&gt;&lt;/dollar&gt; 100开头，则应该使用`withdraw`获得以下响应序列：

```js
withdraw(25); // output: 75
```

```js
withdraw(25); // output: 50
```

```js
withdraw(60); // output: "Insufficient funds"
```

```js
withdraw(15); // output: 35
```

观察到两次评估的表达式`withdraw(25)`产生不同的值。 这是一种新的功能行为。 到目前为止，我们的所有功能都可以视为计算数学功能的规范。 调用函数会计算应用于给定参数的函数值，而两次调用具有相同参数的相同函数始终会产生相同的结果。 [[1]](49#footnote-1)

&lt;split&gt;到目前为止，我们所有的名称都是_常量_，由关键字`const`声明。 声明后，它们就不会更改其值（适合于常量）。 为了实现类似`withdraw`的功能，我们引入了一种新的声明-_变量声明_，它使用关键字`let`而不是`const`。 在声明变量`balance`来指示帐户中的余额后，我们可以将`withdraw`定义为访问`balance`的函数。&lt;/split&gt; 函数检查`balance`是否至少与请求的`amount`一样大。 如果是这样，则`withdraw`将`balance`递减`amount`并返回`balance`的新值。 否则，`withdraw`返回_资金不足_消息。 以下是`balance`和`withdraw`的声明：

```js
let balance = 100;

function withdraw(amount) {
   if (balance >= amount) {
      balance = balance - amount;
      return balance;
   } else {
      return "Insufficient funds";
   }
}
```

通过以下语句完成减量`balance`

```js
balance = balance - amount;
```

<split>The syntax of such _assignment statements_ is </split>`![%5Ctextit%7Bname%7D](img/abe58dbcc6883742e3e0d539cb238bc1.jpg) = ![%5Ctextit%7Bnew-value%7D](img/c8389d11d1b92ceaac24e0c4afa4ac7d.jpg);`

![%5Ctextit%7Bname%7D](img/abe58dbcc6883742e3e0d539cb238bc1.jpg)是符号，![%5Ctextit%7Bnew-value%7D](img/c8389d11d1b92ceaac24e0c4afa4ac7d.jpg)是任何表达式。 分配更改了![%5Ctextit%7Bname%7D](img/abe58dbcc6883742e3e0d539cb238bc1.jpg)，因此其值是通过评估![%5Ctextit%7Bnew-value%7D](img/c8389d11d1b92ceaac24e0c4afa4ac7d.jpg)获得的结果。 在当前情况下，我们正在更改`balance`，以便其新值将是从`balance`的先前值中减去`amount`的结果。 &lt;split&gt;[[2]](49#footnote-2)&lt;/split&gt; 

&lt;split&gt;函数`withdraw`也使用_顺序组成_来在`if`测试为true的情况下评估两个表达式：首先递减`balance`，然后返回[ `balance`。 通常，执行语句`![%5Ctextit%7Bstmt%7D_%7B1%7D](img/ef9df5a0289005cce813a5b09b959d6e.jpg) ![%5Ctextit%7Bstmt%7D_%7B2%7D](img/e3431ffb862a80f6485783d9ecf75bc1.jpg)`会使语句![%5Ctextit%7Bstmt%7D_%7B1%7D](img/ef9df5a0289005cce813a5b09b959d6e.jpg)和![%5Ctextit%7Bstmt%7D_%7B2%7D](img/e3431ffb862a80f6485783d9ecf75bc1.jpg)依次评估。 [[3]](49#footnote-3)&lt;/split&gt; 

尽管`withdraw`可以按需工作，但是变量`balance`却带来了问题。 如上所述，`balance`是在程序环境中定义的名称，可以自由访问以通过任何功能进行检查或修改。 如果我们能以某种方式将`balance`内置于`withdraw`内，那就更好了，这样`withdraw`将是唯一可以直接访问`balance`的函数，而其他任何函数只能（通过调用）间接访问`balance` 至`withdraw`）。 这将更准确地为以下概念建模：`balance`是`withdraw`用于跟踪帐户状态的本地状态变量。

我们可以通过如下定义将`balance`内置到`withdraw`中：

```js
function make_withdraw() {
    let balance = 100;
    return amount => {
        if (balance >= amount) {
            balance = balance - amount;
            return balance;
        } else {
            return "insufficient funds";
        }
    };
}
const new_withdraw = make_withdraw();
```

&lt;split&gt;我们在这里所做的是使用`let`建立一个环境，该环境的局部变量`balance`绑定到初始值100。在此局部环境中，我们使用函数定义 [[4]](49#footnote-4) 创建一个函数，该函数以`amount`作为参数，其行为类似于之前的`withdraw`函数。 该函数是`make_withdraw`函数主体的评估结果，其返回结果与`withdraw`完全相同，但其变量`balance`无法由任何其他函数访问。 [[5]](49#footnote-5)&lt;/split&gt;

将赋值语句与变量语句组合在一起是我们将用于构造具有局部状态的计算对象的通用编程技术。 不幸的是，使用这种技术提出了一个严重的问题：当我们首次引入函数时，我们还引入了评估的替换模型（ [1.1.5](7)[3.1.3](51)[3.2](52) 部分中，我们将介绍这种模型，以及赋值语句和变量语句的说明。 但是，首先，我们研究`make_withdraw`建立的主题的一些变化。

&lt;split&gt;以下函数`make_withdraw_with_balance`将初始余额提取为参数。&lt;/split&gt; `make_withdraw_with_balance`中的形式参数`balance`指定帐户中的初始金额。 [[6]](49#footnote-6)

```js
function make_withdraw_with_balance(balance) {
    return amount => {
        if (balance >= amount) {
            balance = balance - amount;
            return balance;
        } else {
            return "insufficient funds";
        }
    };
}
```

函数`make_withdraw_with_balance`可以按如下方式使用，以创建两个对象`w1`和`w2`：

```js
const w1 = make_withdraw_with_balance(100);
const w2 = make_withdraw_with_balance(100);
```

```js
w1(50); // output: 50
```

```js
w2(70); // output: 30
```

```js
w2(40); // output: "Insufficient funds"
```

```js
w1(40); // output: 10
```

观察到`w1`和`w2`是完全独立的对象，每个对象都有其自己的局部状态变量`balance`。 从一个撤出不会影响另一个。

我们还可以创建处理存款和取款的对象，因此可以表示简单的银行帐户。 这是一个返回具有指定初始余额的&lt;quote&gt;银行帐户对象&lt;/quote&gt;的函数：

```js
function make_account(balance) {
    function withdraw(amount) {
        if (balance >= amount) {
            balance = balance - amount;
            return balance;
        } else {
            return "Insufficient funds";
        }
    }
    function deposit(amount) {
        balance = balance + amount;
        return balance;
    }
    function dispatch(m) {
        if (m === "withdraw") {
            return withdraw;
        } else if (m === "deposit") {
            return deposit;
        } else {
            return "Unknown request - - MAKE-ACCOUNT";
        }
    }
    return dispatch;
}
```

每次对`make_account`的调用都会建立一个带有局部状态变量`balance`的环境。 在此环境中，`make_account`定义访问`balance`的函数`deposit`和`withdraw`以及将&lt;quote&gt;消息&lt;/quote&gt;作为输入并返回两个本地函数之一的附加函数`dispatch`。 `dispatch`函数本身作为代表银行帐户对象的值返回。 这正是我们在 [2.4.3](42) 部分中看到的_消息传递_编程风格，尽管在这里我们将其与修改功能结合使用 局部变量。

`make_account`可以如下使用：

```js
const acc = make_account(100);
```

```js
(acc("withdraw"))(50);
```

```js
(acc("withdraw"))(60);
```

```js
(acc("deposit"))(40);
```

```js
(acc("withdraw"))(60);
```

每次调用`acc`都会返回本地定义的`deposit`或`withdraw`函数，然后将其应用于指定的`amount`。 与`make_withdraw`一样，另一个对`make_account`的调用

```js
const acc2 = make_account(100);
```

will produce a completely separate account object, which maintains its own local `balance`.<exercise>An _accumulator_ is a function that is called repeatedly with a single numeric argument and accumulates its arguments into a sum. Each time it is called, it returns the currently accumulated sum. Write a function `make_accumulator` that generates accumulators, each maintaining an independent sum. The input to `make_accumulator` should specify the initial value of the sum; for example

```js
// make_accumulator to be written by students
const a = make_accumulator(5);
```

```js
a(10);  // output: 15
```

```js
a(10);  // output: 25
```

<button class="btn btn-secondary solution_btn" data-toggle="collapse" href="#solution_49_1_div">Solution</button> <solution>```js
function make_accumulator(current) {
    function add(arg) {
        current = current + arg;
        return current;
    }
    return add;
}
```</solution></exercise> <exercise>In software-testing applications, it is useful to be able to count the number of times a given function is called during the course of a computation. Write a function `make_monitored` that takes as input a function, `f`, that itself takes one input. The result returned by `make_monitored` is a third function, say `mf`, that keeps track of the number of times it has been called by maintaining an internal counter. If the input to `mf` is the string `"how many calls?"`, then `mf` returns the value of the counter. If the input is the string `"reset count"`, then `mf` resets the counter to zero. For any other input, `mf` returns the result of calling `f` on that input and increments the counter. For instance, we could make a monitored version of the `sqrt` function:

```js
// make_monitored function to be written by students
const s = make_monitored(math_sqrt);
```

```js
s(100);
```

```js
s("how many calls?"); // returns 1
```

<button class="btn btn-secondary solution_btn" data-toggle="collapse" href="#solution_49_2_div">Solution</button> <solution>```js
const s = make_monitored(math_sqrt);
s(100);
display(s("how many calls"));
s(5);
display(s("how many calls"));
```

```js
function make_monitored(f) {
    let counter = 0; //initialized to 0
    function mf(cmd) {
        if (cmd === "how many calls") {
            return counter;
        } else if (cmd === "reset count") {
            counter = 0;
            return counter;
        } else {
            counter = counter + 1;
            return f(cmd);
        }
    }
    return mf;
}
```</solution></exercise> <exercise>Modify the `make_account` function so that it creates password-protected accounts. That is, `make_account` should take a symbol as an additional argument, as in

```js
// make_account function to be written by students
const acc = make_account(100, "secret password");
```

The resulting account object should process a request only if it is accompanied by the password with which the account was created, and should otherwise return a complaint:

```js
(acc("secret password", "withdraw"))(40); // result: 60
```

```js
(acc("some other password", "deposit"))(40);  
// result: incorrect password
```

<button class="btn btn-secondary solution_btn" data-toggle="collapse" href="#solution_49_3_div">Solution</button> <solution>```js
function make_account(balance, p) {
    function withdraw(amount) {
        if (balance >= amount) {
            balance = balance - amount;
            return balance;
        } else {
            return "Insufficient funds";
        }
    }
    function deposit(amount) {
        balance = balance + amount;
        return balance;
    }
    function dispatch(m, q) {
        if (p === q) {
            if (m === "withdraw") {
                return withdraw;
            } else if (m === "deposit") {
                return deposit;
            } else {
                return "Unknown request - make_account";
            }
    	} else {
            return q => "Incorrect Password";
        }
    }
    return dispatch;
}

const a = make_account(100, "eva");
(a("withdraw", "eva"))(50); //withdraws 50
(a("withdraw", "ben"))(40); //incorrect password
```</solution></exercise> <exercise>Modify the `make_account` function of exercise <ref name="ex:password-protection">[3.3](49#ex_3.3)</ref> by adding another local state variable so that, if an account is accessed more than seven consecutive times with an incorrect password, it invokes the function `call_the_cops`.<button class="btn btn-secondary solution_btn" data-toggle="collapse" href="#solution_49_4_div">Solution</button> <solution>```js
function make_account(balance, p) {

    let invalid_attempts = 0; //initializes to 0

    function withdraw(amount) {
        if (balance >= amount) {
            balance = balance - amount;
            return balance;
        } else {
            return "Insufficient funds";
        }
    }

    function deposit(amount) {
        balance = balance + amount;
        return balance;
    }

    function call_the_cops() {
        return "calling the cops as you have exceeded " +
               "the max no of failed attempts";
    }

    function dispatch(m, q) {
        if (invalid_attempts <= 7) {
            if (p === q) {
                if (m === "withdraw") {
                    return withdraw;
                } else if (m === "deposit") {
                    return deposit;
                } else {
                    return "Unknown request - make_account";
                }
            } else {
                invalid_attempts = invalid_attempts + 1;
    	        return "Incorrect Password";
            }
        } else {
            return call_the_cops();
        }
    }

    return dispatch;

}
```</solution></exercise> 

* * *

[[1]](49#footnote-link-1) Actually, this is not quite true. One exception was the random-number generator in section <ref name="sec:primality">[1.2.6](17)</ref>. Another exception involved the operation/type tables we introduced in section <ref name="sec:data-directed">[2.4.3](42)</ref>, where the values of two calls to `get` with the same arguments depended on intervening calls to `put`. On the other hand, until we introduce assignment, we have no way to create such functions ourselves.

[[2]](49#footnote-link-2) Note that assignment statements look similar to and should not be confused with constant and variable declarations of the form `const ![%5Ctextit%7Bname%7D](img/abe58dbcc6883742e3e0d539cb238bc1.jpg) = ![%5Ctextit%7Bvalue%7D](img/8941e10ab9faad8a97352258c597168e.jpg);` and `let ![%5Ctextit%7Bname%7D](img/abe58dbcc6883742e3e0d539cb238bc1.jpg) = ![%5Ctextit%7Bvalue%7D](img/8941e10ab9faad8a97352258c597168e.jpg);` in which a newly declared ![%5Ctextit%7Bname%7D](img/abe58dbcc6883742e3e0d539cb238bc1.jpg) is associated with a ![%5Ctextit%7Bvalue%7D](img/8941e10ab9faad8a97352258c597168e.jpg). Also similar in looks but not in meaning are expressions of the form `![%5Ctextit%7Bexpression%7D_1](img/af41029c31183e68ec01b08c614e1295.jpg) === ![%5Ctextit%7Bexpression%7D_2](img/65aa6628b728916b19487b6648d5257c.jpg)` which evaluate to `true` if ![%5Ctextit%7Bexpression%7D_1](img/af41029c31183e68ec01b08c614e1295.jpg) evaluates to the same value as ![%5Ctextit%7Bexpression%7D_2](img/65aa6628b728916b19487b6648d5257c.jpg) and to `false`, otherwise.

 [[3]](49#footnote-link-3) We have already used sequential composition implicitly in our programs, because in JavaScript the body of a function can be a sequence of statements, not just a single `return` statement, as discussed in section <ref name="sec:block-structure">[1.1.8](10)</ref>.

[[4]](49#footnote-link-4) Blocks as bodies of function definition expressions were introduced in section <ref name="sec:graphics">[2.2.4](33)</ref>.

[[5]](49#footnote-link-5) In programming-language jargon, the variable `balance` is said to be _encapsulated_ within the `new_withdraw` function. Encapsulation reflects the general system-design principle known as the _hiding principle_: One can make a system more modular and robust by protecting parts of the system from each other; that is, by providing information access only to those parts of the system that have a <quote>need to know.</quote>

[[6]](49#footnote-link-6) In contrast with `make_withdraw` above, we do not have to use `let` to make `balance` a local variable, since formal parameters are already local. This will be clearer after the discussion of the environment model of evaluation in section <ref name="sec:environment-model">[3.2](52)</ref>. (See also exercise <ref name="ex:local-state-variable">[3.10](55#ex_3.10)</ref>.)

