# 4.1.3评估者数据结构

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

除了定义表达式的外部语法外，评估程序实现还必须定义评估程序内部执行的数据结构，作为程序执行的一部分，例如功能和环境的表示以及对与错的表示。

## 谓词测试

&lt;split&gt;要输入条件的结果，我们希望谓词求值为`true`，因此，我们定义评估函数`is_true`如下：&lt;/split&gt;

```js
function is_true(x) {
    return x === true;
}
```

With the definition of the function `eval_conditional_expression` of section <ref name="sec:core-of-evaluator">[4.1.1](74)</ref>, this means that our evaluator evaluates the alternative statement for any predicate value other than `true`.

## 代表功能

为了处理原语，我们假设我们具有以下功能：

*   `apply_primitive_function(` ![%5Ctextit%7Bfun%7D](img/e08803b94d9e8d59b93bdb3d77bd5e47.jpg)，![%5Ctextit%7Bargs%7D](img/f4e23fc9c58c45377e1f8dbd981e20f3.jpg) `)` &lt;break&gt;&lt;/break&gt; 将给定的原始函数应用于列表![%5Ctextit%7Bargs%7D](img/f4e23fc9c58c45377e1f8dbd981e20f3.jpg)中的参数值，并返回应用程序的结果。
*   `is_primitive_function(` ![%5Ctextit%7Bfun%7D](img/e08803b94d9e8d59b93bdb3d77bd5e47.jpg) `)` &lt;break&gt;&lt;/break&gt; 测试![%5Ctextit%7Bfun%7D](img/e08803b94d9e8d59b93bdb3d77bd5e47.jpg)是否是原始函数。

These mechanisms for handling primitives are further described in section <ref name="sec:running-eval">[4.1.4](77)</ref>.

使用构造函数`make_compound_function`从参数，函数体和环境构造复合函数：

```js
function make_compound_function(parameters, body, env) {
    return list("compound_function",
                parameters, body, env);
}
function is_compound_function(f) {
    return is_tagged_list(f, "compound_function");
}
function function_parameters(f) {
    return list_ref(f, 1);
}
function function_body(f) {
    return list_ref(f, 2);
}
function function_environment(f) {
    return list_ref(f, 3);
}
```

 <split>## 代表返回值

我们在 [4.1.1](74) 部分中看到，序列的求值以遇到的第一个return语句终止，并且如果求值，函数应用程序的求值需要返回值`undefined` 函数体中的“”没有遇到`return`语句。 为了识别`return`语句的评估，我们引入`return`值作为评估器数据结构。

```js
function make_return_value(content) {
    return list("return_value", content);
}
function is_return_value(value) {
    return is_tagged_list(value,"return_value");
}
function return_value_content(value) {
    return head(tail(value));
}
```</split> 

## 环境运营

评估人员需要用于操作环境的操作。 如 [3.2](52) 一节所述，环境是一系列帧，其中每个帧都是将名称与其对应值相关联的绑定表。 我们在操作环境中使用以下操作：

*   `lookup_name_value(` ![%5Ctextit%7Bname%7D](img/abe58dbcc6883742e3e0d539cb238bc1.jpg)，![%5Ctextit%7Benv%7D](img/76ca56831868eee1e619fb18576ad6e4.jpg) `)` &lt;break&gt;&lt;/break&gt; 返回绑定到符号![%5Ctextit%7Bname%7D](img/abe58dbcc6883742e3e0d539cb238bc1.jpg)的值环境env，如果名称未绑定，则发出错误信号。
*   `extend_environment(` ![%5Ctextit%7Bnames%7D](img/b34d1b289cd30ff57c0f10eace3f5ba4.jpg)，![%5Ctextit%7Bvalues%7D](img/3baf5ec2562fedaf6a6b452f0afa6648.jpg)，![%5Ctextit%7Bbase-env%7D](img/8eb23c5c52bf61986e1c118498e41f1b.jpg) `)` &lt;break&gt;&lt;/break&gt; 返回一个新环境，该环境由新框架组成，其中列表![%5Ctextit%7Bnames%7D](img/b34d1b289cd30ff57c0f10eace3f5ba4.jpg)中的符号为绑定到列表![%5Ctextit%7Bvalues%7D](img/3baf5ec2562fedaf6a6b452f0afa6648.jpg)中的相应元素（每个元素标记为_可变_），其中封闭环境为环境![%5Ctextit%7Bbase-env%7D](img/8eb23c5c52bf61986e1c118498e41f1b.jpg)。
*   `set_name_value(` ![%5Ctextit%7Bname%7D](img/abe58dbcc6883742e3e0d539cb238bc1.jpg)，![%5Ctextit%7Bvalue%7D](img/8941e10ab9faad8a97352258c597168e.jpg)，![%5Ctextit%7Benv%7D](img/76ca56831868eee1e619fb18576ad6e4.jpg) `)` &lt;break&gt;&lt;/break&gt; 在环境![%5Ctextit%7Benv%7D](img/76ca56831868eee1e619fb18576ad6e4.jpg)的第一帧中将给定的![%5Ctextit%7Bname%7D](img/abe58dbcc6883742e3e0d539cb238bc1.jpg)设置为给定的![%5Ctextit%7Bvalue%7D](img/8941e10ab9faad8a97352258c597168e.jpg)]。
*   `assign_name_value(` ![%5Ctextit%7Bname%7D](img/abe58dbcc6883742e3e0d539cb238bc1.jpg)，![%5Ctextit%7Bvalue%7D](img/8941e10ab9faad8a97352258c597168e.jpg)，![%5Ctextit%7Benv%7D](img/76ca56831868eee1e619fb18576ad6e4.jpg) `)` &lt;break&gt;&lt;/break&gt; 检查与名称![%5Ctextit%7Bname%7D](img/abe58dbcc6883742e3e0d539cb238bc1.jpg) 的绑定，盔甲该名称现在绑定到值![%5Ctextit%7Bvalue%7D](img/8941e10ab9faad8a97352258c597168e.jpg)，或者如果该名称未绑定其值标记为不可变，则发出错误信号。

为了实现这些操作，我们将环境表示为帧列表。 环境的包围环境是列表的`tail`。 空环境就是空列表。

```js
function enclosing_environment(env) {
    return tail(env);
}
function first_frame(env) {
    return head(env);
}
function enclose_by(frame, env) {    
    return pair(frame, env);
}
const the_empty_environment = null;
function is_empty_environment(env) {
    return is_null(env);
}
```

环境的每个框架都表示为一对列表：该框架中绑定的变量的列表和关联值的列表。 [[1]](76#footnote-1)

```js
function make_frame(names, values) {
    return pair(names, values);
}
function frame_names(frame) {    
    return head(frame);
}
function frame_values(frame) {    
    return tail(frame);
}
```

要通过将名称与值相关联的新框架扩展环境，我们创建一个由名称列表和值列表组成的框架，并将其与环境连接。 如果名称数量与值数量不匹配，我们将发出错误信号。

```js
function extend_environment(names, vals, base_env) {
    if (length(names) === length(vals)) {
        return enclose_by(
                   make_frame(names, 
                      map(x => pair(x, true), vals)),
                   base_env);
    } else if (length(names) < length(vals)) {
        error("Too many arguments supplied: " + 
              stringify(names) + ", " + 
              stringify(vals));
    } else {
        error("Too few arguments supplied: " + 
              stringify(names) + ", " + 
              stringify(vals));
    }
}
```

The function `extend_environment` is used by `apply` in section <ref name="sec:core-of-evaluator">[4.1.1](74)</ref> to bind the parameters of a function to its arguments. In order to allow for assignment to function parameters, as in function `make_withdraw_with_balance` of section <ref name="sec:local-state-variables">[3.1.1](49)</ref>, we choose to tag the values in `extend_environment` as mutable, using `map`.

要在环境中查找名称，我们在第一帧中扫描名称列表。 如果找到所需的名称，则返回值列表中的相应元素。 如果在当前框架中找不到该名称，则搜索封闭的环境，依此类推。 如果到达空的环境，则会发出&lt;quote&gt;未绑定名称&lt;/quote&gt;错误的信号。

```js
// We use a nullary function as temporary value for names whose
// declaration has not yet been evaluated. The purpose of the
// function definition is purely to create a unique identity;
// the function will never be applied and its return value 
// (null) is irrelevant.
const no_value_yet = () => null;
```

```js
function lookup_name_value(name, env) {
    function env_loop(env) {
        function scan(names, vals) {
            return is_null(names)
                   ? env_loop(
                       enclosing_environment(env))
                   : name === head(names)
                     ? head(head(vals))
                     : scan(tail(names), tail(vals));
        }
        if (is_empty_environment(env)) {
            error(name, "Unbound name: ");
        } else {
            const frame = first_frame(env);
            const value =  scan(frame_names(frame),
                                frame_values(frame));
	    return value === no_value_yet
              ? error(name, "Name use before declaration: ")
              : value;
        }
    }
    return env_loop(env);
}
```

要在指定的环境中为新值分配名称，我们将像`lookup_name_value`一样扫描名称，并在找到它时更改相应的值（前提是将其标记为可变的）。

```js
function assign_name_value(name, val, env) {
    function env_loop(env) {
        function scan(names, vals) {
            return is_null(names)
                ? env_loop(
                    enclosing_environment(env))
                : name === head(names)
                  ? ( tail(head(vals))
                      ? set_head(head(vals), val)
                      : error("no assignment " +
                          "to constants allowed") )
                  : scan(tail(names), tail(vals));
        } 
        if (env === the_empty_environment) {
            error(name, "Unbound name in assignment: ");
        } else {
            const frame = first_frame(env);
            return scan(frame_names(frame),
                        frame_values(frame));
        }
    }
    return env_loop(env);
}
```

 <split>函数应用程序和块的评估已经确保在评估实际的声明之前，将本地名称包含在当前环境中。 因此，可以像声明变量一样处理常量和变量的声明，只是我们可以坚持在最内层的框架中找到该名称。

```js
function set_name_value(name, val, env) {
    function scan(names, vals) {
        return is_null(names)
            ? error("internal error: name not found")
            : name === head(names)
              ? set_head(head(vals), val)
              : scan(tail(names), tail(vals));
    } 
    const frame = first_frame(env);
    return scan(frame_names(frame),
                frame_values(frame));
}
```</split> 

此处描述的方法只是表示环境的许多可行方式之一。 由于我们使用数据抽象将评估程序的其余部分与表示形式的详细选择隔离开来，因此如果需要，我们可以更改环境表示形式。 （请参见练习 [4.4](76#ex_4.4) 。）在具有生产质量的JavaScript系统中，评估程序环境操作的速度（尤其是变量查找的速度）对系统性能有重大影响。 。 这里描述的表示虽然在概念上很简单，但效率不高，通常不会在生产系统中使用。 [[2]](76#footnote-2)

<exercise>Instead of representing a frame as a pair of lists, we can represent a frame as a list of bindings, where each binding is a name-value pair. Rewrite the environment operations to use this alternative representation.<button class="btn btn-secondary solution_btn" data-toggle="collapse" href="#no_solution_76_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><split><exercise>JavaScript allows us to create new bindings for names by means of constant and variable declaration, but provides no way to get rid of bindings. Implement for the evaluator a <quote>function</quote> `make_unbound` that removes the binding of a name given as <quote>argument</quote> from the environment in which the application of the function is evaluated. This problem is not completely specified. For example, should we remove only the binding in the first frame of the environment? Complete the specification and justify any choices you make.<button class="btn btn-secondary solution_btn" data-toggle="collapse" href="#no_solution_76_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></split>

* * *

[[1]](76#footnote-link-1) Frames are not really a data abstraction in the following code: `assign_name_value` and `define_variable` use `set_head` to directly modify the values in a frame. The purpose of the frame functions is to make the environment-manipulation functions easy to read.

[[2]](76#footnote-link-2) The drawback of this representation (as well as the variant in exercise <ref name="ex:alternate-frame-representation">[4.4](76#ex_4.4)</ref>) is that the evaluator may have to search through many frames in order to find the binding for a given variable. (Such an approach is referred to as _deep binding_.) One way to avoid this inefficiency is to make use of a strategy called _lexical addressing_

