# 5.4.2序列评估和尾递归

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

`ev-sequence`处的显式控制评估器的部分类似于元圆评估器的`eval_sequence`函数。 它处理函数体或显式`begin`表达式中的表达式序列。

通过将要评估的表达式序列放在`unev`中，将`continue`保存到堆栈中并跳转到`ev_sequence`来评估显式`begin`表达式。

通过从`compound_apply`跳转到`ev_sequence`来处理功能体中的隐式序列，此时`continue`已在堆栈上，并已保存在`ev_application`中。

`ev_sequence`和`ev_sequence_continue`处的条目形成一个循环，该循环依次评估序列中的每个表达式。 未评估的表达式列表保存在`unev`中。 在评估每个表达式之前，我们检查序列中是否还有其他表达式要评估。 如果是这样，我们将保存其余未评估的表达式（保存在`unev`中）和必须对其进行评估的环境（保存在`env`中），然后调用`eval_dispatch`来评估该表达式。 从此评估返回后，将在`ev_sequence_continue`恢复两个保存的寄存器。

在入口点`ev_sequence_last_exp`处以不同的方式处理序列中的最终表达式。 由于在此之后没有更多要评估的表达式，因此在进入`eval_dispatch`之前无需保存`unev`或`env`。 整个序列的值是最后一个表达式的值，因此，在计算完最后一个表达式之后，除了在当前保存在堆栈上的入口点处继续（已由`ev_application`或[HTG5保存] ]）。我们没有设置`continue`来安排`eval_dispatch`返回此处，然后从堆栈中恢复`continue`并继续该入口点，而是在进入`eval_dispatch`之前从堆栈中恢复了`continue`， 因此`eval_dispatch`将在评估表达式后在该入口点继续。

```js
"ev_sequence",
          assign(exp(op("first_exp"), reg("unev"))),
          test(op("is_last_exp"), reg("unev")),
          branch(label("ev_sequence_last_exp")),
          save("unev"),
          save("env"),
          assign("continue", label("ev_sequence_continue")),
          go_to(label("eval_dispatch")),
      "ev_sequence_continue",
          restore("env"),
          restore("unev"),
          assign("unev", op("rest_exps"), reg("unev")),
          go_to(label("ev_sequence")),
      "ev_sequence_last_exp",
          restore("continue"),
          go_to(label("eval_dispatch")),
```

## 尾递归

在第1章中，我们说过由诸如

```js
function sqrt_iter(guess, x) {
          return is_good_enough(guess, x)
              ? guess
              : sqrt_iter(improve(guess, x), x);
      }
```

is an iterative process. Even though the function is syntactically recursive (defined in terms of itself), it is not logically necessary for an evaluator to save information in passing from one call to `sqrt_iter` to the next.[[1]](111#footnote-1) An evaluator that can execute a function such as `sqrt_iter` without requiring increasing storage as the function continues to call itself is called a _tail-recursive_ evaluator. The metacircular implementation of the evaluator in chapter 4 does not specify whether the evaluator is tail-recursive, because that evaluator inherits its mechanism for saving state from the underlying Scheme. With the explicit-control evaluator, however, we can trace through the evaluation process to see when function calls cause a net accumulation of information on the stack.

我们的评估程序是尾递归的，因为为了评估序列的最终表达，我们直接将其传输到`eval_dispatch`，而无需在堆栈上保存任何信息。 因此，评估序列中的最终表达式（即使它是函数调用（如`sqrt_iter`一样，其中条件表达式是函数主体中的最后一个表达式，也将简化为对`sqrt_iter`的调用）） 不会导致任何信息堆积在堆栈上。 [[2]](111#footnote-2)

如果我们不认为在这种情况下不必保存信息这一事实，我们可能通过以相同的方式处理所有表达式来实现`eval_sequence`，即保存寄存器，评估表达式， 返回以恢复寄存器，并重复此操作，直到对所有表达式求值： [[3]](111#footnote-3)

```js
"ev_sequence",
          test(op("has_no_more_exps"), reg("unev")),
          branch(label("ev_sequence_end")),
          assign(exp(op("first_exp"), reg("unev")),
          save("unev"),
          save("env"),
          assign(continue(label("ev_sequence_continue"))),
          go_to(label("eval_dispatch")),
      "ev_sequence_continue",
          restore("env"),
          restore("unev"),
          assign("unev", op("rest_exps"), reg("unev")),
          go_to(label("ev_sequence")),
      "ev_sequence_end",
          restore("continue"),
          go_to(reg("continue")),
```

这似乎是对我们先前用于评估序列的代码的微小更改：唯一的区别是，我们经历了序列中最后一个表达式以及其他序列的保存-恢复周期。 对于任何表达式，解释器仍将提供相同的值。 但是这种改变对尾递归实现是致命的，因为我们现在必须在对序列的最终表达式求值后返回，以撤消（无用的）寄存器保存。 这些额外的节省将在嵌套函数调用期间累积。 因此，诸如`sqrt_iter`之类的过程将需要与迭代次数成比例的空间，而不是恒定的空间。 这种差异可能很大。 例如，使用尾部递归，可以仅使用函数调用机制来表示无限循环：

```js
function count(n) {
          display(n, "\n");
          count(n + 1);
      }
```

Without tail recursion, such a function would eventually run out of stack space, and expressing a true iteration would require some control mechanism other than function call.

* * *

[[1]](111#footnote-link-1) We saw in section <ref name="sec:designing-register-machines">[5.1](95)</ref> how to implement such a process with a register machine that had no stack; the state of the process was stored in a fixed set of registers.

[[2]](111#footnote-link-2) This implementation of tail recursion in `ev_sequence` is one variety of a well-known optimization technique used by many compilers. In compiling a function that ends with a function call, one can replace the call by a jump to the called function's entry point. Building this strategy into the interpreter, as we have done in this section, provides the optimization uniformly throughout the language.

[[3]](111#footnote-link-3) We can define `has_no_more_exps` as follows:

```js
function has_no_more_exps(seq) {
            return is_null(seq);
        }
```

