# 4.1.6内部声明

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

我们的评估环境模型和元循环评估器按顺序执行声明，从而一次扩展了一个声明的环境框架。 这对于交互式程序开发特别方便，在这种情况下，程序员需要自由地将函数的应用程序与新函数的声明混合在一起。 但是，如果我们仔细考虑用于实现块结构的内部声明（在 [1.1.8](10) 部分中介绍），我们会发现环境的按名称扩展可能会 并不是声明本地名称的最佳方法。

考虑具有内部声明的函数，例如

```js
function f(x) {
    function is_even(n) {
        return n === 0
               ? true
               : is_odd(n - 1);
    }
    function is_odd(n) {
        return n === 0
               ? false
               : is_even(n - 1);
    }
    // rest of body of f
}
```

我们的意图是，函数`is_even`主体中的名称`is_odd`应该引用在`is_even`之后声明的函数`is_odd`。 名称`is_odd`的范围是`f`的整个主体，而不仅仅是`f`主体的一部分，从声明`is_odd`的位置开始。 确实，当我们认为`is_odd`本身是根据`is_even`定义的，因此`is_even`和`is_odd`是相互递归的函数时，我们看到对这两个声明的唯一令人满意的解释是将它们视为 名称`is_even`和`is_odd`同时添加到环境中。 更一般而言，在块结构中，本地名称的范围是在其中评估声明的整个函数体。

碰巧的是，我们的解释器将正确评估对`f`的调用，但是由于&lt;quote&gt;意外&lt;/quote&gt;的原因：由于内部函数的声明排在最前面，因此对这些函数的调用将不被评估，直到它们全部具有 被宣布。 因此，`is_odd`将在执行`is_even`时声明。 实际上，我们的顺序求值机制将产生与直接对任何函数执行同时声明的机制相同的结果，在该函数中，内部声明首先出现在主体中，而对声明名称的值表达式的评估实际上并未使用任何 声明的名称。 （有关不遵守这些限制的函数的示例，因此顺序声明不等同于同时声明，请参见练习 [4.11](79#ex_4.11) 。） [[1]](79#footnote-1)

但是，有一种处理声明的简单方法，以使内部声明的名称具有真正的同时作用域-只需在评估任何值表达式之前创建将在当前环境中使用的所有本地名称即可。 一种实现方法是对函数定义表达式进行语法转换。 [[2]](79#footnote-2) 在评估函数定义表达式的主体之前，我们&lt;quote&gt;扫描出&lt;/quote&gt;并消除主体中的所有内部声明。 内部声明的名称将使用函数定义创建，然后通过赋值设置为其值。 在下文中，我们将集中于使用`let`的变量声明； 使用`const`和`function`的常量声明可以类似地处理。 例如，功能定义`(![%5Ctextit%7Bvars%7D](img/af5419828e2dea729cd2551a42471db7.jpg)) =&gt; { let u = ![e_1](img/654004cd3d06f92ac3a84b7843bbf681.jpg); let v = ![e_2](img/4fad213e27dd89bc4ddae55754746125.jpg); ![%5Ctextit%7Bstatement%7D](img/d8424c180976b9cd82355b24b3b0bb03.jpg) }`将转换为`( ![%5Ctextit%7Bvars%7D](img/af5419828e2dea729cd2551a42471db7.jpg) ) =&gt; { return ( (u, v) =&gt; { u = ![e_1](img/654004cd3d06f92ac3a84b7843bbf681.jpg); v = ![e_2](img/4fad213e27dd89bc4ddae55754746125.jpg); ![%5Ctextit%7Bstatement%7D](img/d8424c180976b9cd82355b24b3b0bb03.jpg) })("*unassigned*", "*unassigned*"); }`，其中`"*unassigned*"`是特殊符号，如果尝试使用尚未分配的值，则会导致查找名称以表示错误 名称。

练习 [4.10](79#ex_4.10) 中显示了一种扫描内部声明的替代策略。 与上面显示的转换不同，这强制执行了以下限制：可以在不使用任何名称值的情况下评估声明的名称值。

<exercise>In this exercise we implement the method just described for interpreting internal definitions.

1.  如果找到的值是字符串`"*unassigned*"`，则更改`lookup_name_value`（ [4.1.3](76) 节）以发出错误信号。
2.  通过执行上述转换，编写一个接受函数体并返回没有内部定义的等效函数的函数`scan_out_let`。
3.  在解释器中的`make_function` 或`function_body`中安装`scan_out_let`（请参见 [4.1.3](76) 一节）。 哪个地方更好？ 为什么？

<button class="btn btn-secondary solution_btn" data-toggle="collapse" href="#no_solution_79_1_div">Add solution</button>There is currently no solution available for this exercise. This textbook adaptation is a community effort. Do consider contributing by providing a solution for this exercise, using a Pull Request in [Github](https://github.com/source-academy/sicp).</exercise><exercise>Draw diagrams of the environment in effect when evaluating the ![%5Ctextit%7Bstatement%7D](img/d8424c180976b9cd82355b24b3b0bb03.jpg) in the function in the text, comparing how this will be structured when declarations are interpreted sequentially with how it will be structured if declarations are scanned out as described. Why is there an extra frame in the transformed program? Explain why this difference in environment structure can never make a difference in the behavior of a correct program. Design a way to make the interpreter implement the <quote>simultaneous</quote> scope rule for internal declarations without constructing the extra frame.<button class="btn btn-secondary solution_btn" data-toggle="collapse" href="#no_solution_79_1_div">Add solution</button>There is currently no solution available for this exercise. This textbook adaptation is a community effort. Do consider contributing by providing a solution for this exercise, using a Pull Request in [Github](https://github.com/source-academy/sicp).</exercise><exercise>Consider an alternative strategy for scanning out declarations that translates the example in the text to `( ![%5Ctextit%7Bvars%7D](img/af5419828e2dea729cd2551a42471db7.jpg) ) => { return ( (u, v) => { return ( (a, b) => { u = a; v = b; ![%5Ctextit%7Bstatement%7D](img/d8424c180976b9cd82355b24b3b0bb03.jpg) })(![e_1](img/654004cd3d06f92ac3a84b7843bbf681.jpg), ![e_2](img/4fad213e27dd89bc4ddae55754746125.jpg)); })("*unassigned*", "*unassigned*"); }` Here `a` and `b` are meant to represent new variable names, created by the interpreter, that do not appear in the user's program. Consider the `solve` function from section <ref name="sec:streams-and-delayed-evaluation">[3.5.4](70)</ref>:

```js
function solve(f, y0, dt) {
    const y = integral( () => dy, y0, dt);
    const dy = stream_map(f, y);
    return y;
}
```

Will this function work if internal definitions are scanned out as shown in this exercise? What if they are scanned out as shown in the text? Explain.<button class="btn btn-secondary solution_btn" data-toggle="collapse" href="#no_solution_79_1_div">Add solution</button>There is currently no solution available for this exercise. This textbook adaptation is a community effort. Do consider contributing by providing a solution for this exercise, using a Pull Request in [Github](https://github.com/source-academy/sicp).</exercise><exercise>Ben Bitdiddle, Alyssa P. Hacker, and Eva Lu Ator are arguing about the desired result of evaluating the expression

```js
let a = 1;
function f(x) {      
    let b = a + x;
    let a = 5;
    return a + b;
}
f(10);
```

Ben asserts that the result should be obtained using the sequential rule for `let`: `b` is declared to be 11, then `a` is declared to be 5, so the result is 16\. Alyssa objects that mutual recursion requires the simultaneous scope rule for internal function declarations, and that it is unreasonable to treat function names differently from other names. Thus, she argues for the mechanism implemented in exercise <ref name="ex:internal-defs">[4.8](79#ex_4.8)</ref>. This would lead to `a` being unassigned at the time that the value for `b` is to be computed. Hence, in Alyssa's view the function should produce an error. Eva has a third opinion. She says that if the declarations of `a` and `b` are truly meant to be simultaneous, then the value 5 for `a` should be used in evaluating `b`. Hence, in Eva's view `a` should be 5, `b` should be 15, and the result should be 20\. Which (if any) of these viewpoints do you support? Can you devise a way to implement internal declarations so that they behave as Eva prefers?[[3]](79#footnote-3)<button class="btn btn-secondary solution_btn" data-toggle="collapse" href="#no_solution_79_1_div">Add solution</button>There is currently no solution available for this exercise. This textbook adaptation is a community effort. Do consider contributing by providing a solution for this exercise, using a Pull Request in [Github](https://github.com/source-academy/sicp).</exercise><exercise>For recursion, we currently make use of the fact that the scope of a constant declaration is the surrounding block. An occurrence of the function name in its body can refer to the function, because it lies in the scope of the `const` that declares the name. Louis Reasoner thinks that there ought to be a way to specify recursive functions without using `const`, `let` or `function`. Amazingly, Louis's intuition is correct. It is indeed possible to specify recursive functions without using `const` or `let`, `function`, although the method for accomplishing this is much more subtle than Louis imagined. The following expression computes 10 factorial by applying a recursive factorial function:[[4]](79#footnote-4)

```js
(n => (fact => fact(fact, n))
      ( (ft, k) => k === 1
                   ? 1
                   : k * ft(ft, k - 1) 
      )
)
(10);
```

1.  检查（通过计算表达式）它确实可以计算阶乘。 设计一个类似的表达式来计算斐波那契数。
2.  考虑以下函数，其中包括相互递归的内部定义：

    ```js
    function f(x) {
        function is_even(n) {
            return n === 0
                   ? true
                   : is_odd(n - 1);
        }
        function is_odd(n) {
            return n === 0
                   ? false
                   : is_even(n - 1);
        }
        return is_even(x);
    }
    ```

    填写缺少的表达式以完成`f`的替代定义，该定义既不使用`const`，也不使用`let`或内部函数声明：

    ```js
    function f(x) {
        return (
            (is_even, is_odd) =&gt; 
               is_even(is_even, is_odd, x)
          )
          ( (ev, od, n) =&gt;
               n === 0 ? true : od(??, ??, ??),
            (ev, od, n) =&gt;
               n === 0 ? false : ev(??, ??, ??) 
          );
    }
    ```

<button class="btn btn-secondary solution_btn" data-toggle="collapse" href="#no_solution_79_1_div">Add solution</button>There is currently no solution available for this exercise. This textbook adaptation is a community effort. Do consider contributing by providing a solution for this exercise, using a Pull Request in [Github](https://github.com/source-academy/sicp).</exercise>

* * *

[[1]](79#footnote-link-1) Wanting programs to not depend on this evaluation mechanism is the reason for the <quote>management is not responsible</quote> remark in footnote <ref name="foot:management">**Cound not find label for foot:management**</ref> of chapter 1. The designers of JavaScript chose to resolve this issue by moving all internal function declarations to the beginning of the function body, and thus the discussion might seem moot. However, this mechanism is only applied to function declarations and not to `const` declarations.

[[2]](79#footnote-link-2) We can view function declaration statements as a combination of constant declaration statements and function definition expressions, as explained in section <ref name="sec:lambda">[1.3.2](20)</ref>, and thus the same technique applies to function declaration statements.

[[3]](79#footnote-link-3) The designers of JavaScript support Alyssa on the following grounds: Eva is in principle correct—the definitions should be regarded as simultaneous. But it seems difficult to implement a general, efficient mechanism that does what Eva requires. In the absence of such a mechanism, it is better to generate an error in the difficult cases of simultaneous definitions (Alyssa's notion) than to produce an incorrect answer (as Ben would have it).

[[4]](79#footnote-link-4) This example illustrates a programming trick for formulating recursive functions without using `const`, `let` or `function`. The most general trick of this sort is the ![Y](img/fc057c91da229543772de0dd6f8326f5.jpg) _operator_, which can be used to give a <quote>pure ![%5Clambda](img/5e8df2ba7e47a784c714d176ed8bbb7a.jpg)-calculus</quote> implementation of recursion. (See <citation>Stoy 1977</citation> for details on the lambda calculus, and <citation>Gabriel 1988</citation> for an exposition of the ![Y](img/fc057c91da229543772de0dd6f8326f5.jpg) operator in Scheme.)

