# 2.4.3数据定向编程和可加性

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

检查数据类型并调用适当函数的一般策略称为对类型的_调度。 这是在系统设计中获得模块化的强大策略。 另一方面，按照 [2.4.2](41) 部分的规定实施分派有两个明显的弱点。 一个缺点是通用接口函数（`real_part`，`imag_part`，`magnitude`和`angle`）必须了解所有不同的表示形式。 例如，假设我们想将复数的新表示形式并入我们的复数系统。 我们将需要使用一种类型来标识此新表示形式，然后向每个通用接口函数添加一个子句以检查新类型并为该表示形式应用适当的选择器。_

该技术的另一个缺点是，即使可以单独设计各个表示，我们也必须保证整个系统中没有两个函数具有相同的名称。 这就是为什么Ben和Alyssa必须从 [2.4.1](40) 部分更改其原始功能的名称。

这两个弱点的根本原因在于，用于实现通用接口的技术不是_附加_。 每次安装新的表示形式时，实现通用选择器功能的人员都必须修改这些功能，并且与各个表示形式进行交互的人员必须修改其代码，以避免名称冲突。 在每种情况下，必须对代码进行的更改都是简单明了的，但是仍然必须进行更改，这是造成不便和错误的根源。 就复数系统而言，这并不是什么大问题，但是假设复数没有两个，而是数百个不同的表示形式。 并假设在抽象数据接口中要维护许多通用选择器。 实际上，假设没有一个程序员知道所有接口功能或所有表示形式。 这个问题是真实的，必须在诸如大型数据库管理系统之类的程序中解决。

我们需要的是一种进一步模块化系统设计的方法。 这由称为_数据定向编程_的编程技术提供。 要了解数据定向编程的工作原理，首先要观察到，每当我们处理一组通用的操作时，这些操作对于一组不同的类型都是通用的，实际上，我们是在处理包含可能操作的二维表 在一个轴上，可能的类型在另一轴上。 该表中的条目是为所呈现的每种自变量类型实现每种操作的功能。 在上一节开发的复数系统中，操作名称，数据类型和实际功能之间的对应关系散布在通用接口函数中的各种条件子句中。 但是相同的信息也可以组织在一个表中，如图 [2.22](42#fig_2.22) 所示。

数据定向编程是一种设计程序以直接使用此类表的技术。 以前，我们实现了将复杂算术代码与两个表示包连接在一起的机制，这是一组函数，每个函数对类型执行显式分配。 在这里，我们将接口实现为单个函数，该函数在表中查找操作名称和参数类型的组合以查找要应用的正确函数，然后将其应用于参数的内容。 如果这样做，则无需向系统添加新的表示包，就无需更改任何现有功能。 我们只需要向表中添加新条目即可。

&lt;split&gt;![](img/03bbac589f9f54307e9e88a02ce1f996.jpg)&lt;/split&gt; 为实现此计划，假设我们有两个函数`put`和`get`来操作操作和类型表：

*   `put(` ![%5Clangle%20%5Ctextit%7Bop%7D%20%5Crangle%2C%20%5Clangle%20%5Ctextit%7Btype%7D%20%5Crangle%2C%20%5Clangle%20%5Ctextit%7Bitem%7D%20%5Crangle](img/f8112420defa524e0a6befc80f518d2d.jpg) `)` &lt;break&gt;&lt;/break&gt; 将![%5Clangle%20%5Ctextit%7Bitem%7D%20%5Crangle](img/a9e30da542faf3e6f865378bb0ef0eb6.jpg)安装在表中，并由![%5Clangle%20%5Ctextit%7Bop%7D%20%5Crangle](img/f359c0089d69969d956eed79a7a9c2fd.jpg)和![%5Clangle%20%5Ctextit%7Btype%7D%20%5Crangle](img/ba5eeb5ba5583126ea74e4160d89d05d.jpg)索引。
*   `get(` ![%5Clangle%20%5Ctextit%7Bop%7D%20%5Crangle%2C%20%5Clangle%20%5Ctextit%7Btype%7D%20%5Crangle](img/aef81f2d30b430c9b7d2e80599180c36.jpg) `)` &lt;break&gt;&lt;/break&gt; 查找表中的![%5Clangle%20%5Ctextit%7Bop%7D%20%5Crangle](img/f359c0089d69969d956eed79a7a9c2fd.jpg)和![%5Clangle%20%5Ctextit%7Btype%7D%20%5Crangle](img/ba5eeb5ba5583126ea74e4160d89d05d.jpg)条目，并返回在那里找到的项目。 如果未找到任何项目，则`get`返回`undefined`。

For now, we can assume that `put` and `get` are included in our language. In chapter 3 (section <ref name="sec:tables">[3.3.3](60)</ref>) we will see how to implement these and other operations for manipulating tables.

这是在复数系统中如何使用数据定向编程的方法。 开发矩形表示法的Ben像他最初那样执行代码。 他定义了一个函数集合或_包_，并通过向表中添加条目来指示这些功能与系统其余部分的接口，这些条目告诉系统如何对矩形进行运算。 这可以通过调用以下函数来完成：

```js
function install_rectangular_package() {
    function real_part(z) { return head(z); }
    function imag_part(z) { return tail(z); }
    function make_from_real_imag(x, y) { return pair(x, y); }
    function magnitude(z) {
        return math_sqrt(square(real_part(z)) +
                   square(imag_part(z)));
    }
    function angle(z) {
        return math_atan(imag_part(z), real_part(z));
    }
    function make_from_mag_ang(r, a) {
        return pair(r * math_cos(a), r * math_sin(a));
    }
    // interface to the rest of the system
    function tag(x) {
        return attach_tag("rectangular", x);
    }
    put("real_part", list("rectangular"), real_part);
    put("imag_part", list("rectangular"), imag_part);
    put("magnitude", list("rectangular"), magnitude);
    put("angle", list("rectangular"), angle);
    put("make_from_real_imag", "rectangular",
        (x, y) => tag(make_from_real_imag(x, y)));
    put("make_from_mag_ang", "rectangular",
        (r, a) => tag(make_from_mag_ang(r, a)));
    return "done";
}

install_rectangular_package();
```

Notice that the internal functions here are the same functions from section <ref name="sec:representations-complex-numbers">[2.4.1](40)</ref> that Ben wrote when he was working in isolation. No changes are necessary in order to interface them to the rest of the system. Moreover, since these function definitions are internal to the installation function, Ben needn't worry about name conflicts with other functions outside the rectangular package. To interface these to the rest of the system, Ben installs his `real_part` function under the operation name `real_part` and the type `list("rectangular")`, and similarly for the other selectors.[[1]](42#footnote-1) The interface also defines the constructors to be used by the external system.[[2]](42#footnote-2) These are identical to Ben's internally defined constructors, except that they attach the tag. Alyssa's polar package is analogous:

```js
function install_polar_package() {
    // internal functions
    function magnitude(z) { return head(z); }
    function angle(z) { return tail(z); }
    function make_from_mag_ang(r, a) { return pair(r, a); }
    function real_part(z) {
       return magnitude(z) * math_cos(angle(z));
    }
    function imag_part(z) {
       return magnitude(z) * math_sin(angle(z));
    }
    function make_from_real_imag(x, y) {
       return pair(math_sqrt(square(x) + square(y)),
                   math_atan(y, x));
    }

    // interface to the rest of the system
    function tag(x) { return attach_tag("polar", x); }
    put("real_part", list("polar"), real_part);
    put("imag_part", list("polar"), imag_part);
    put("magnitude", list("polar"), magnitude);
    put("angle", list("polar"), angle);
    put("make_from_real_imag", "polar", 
        (x, y) => tag(make_from_real_imag(x, y)));
    put("make_from_mag_ang", "polar",
        (r, a) => tag(make_from_mag_ang(r, a)));
    return "done";
}

install_polar_package();
```

即使Ben和Alyssa仍使用各自的原始函数定义了彼此相同的名称（例如`real_part`），这些定义现在已包含在不同函数的内部（请参见 [1.1.8](10) ），因此没有名称冲突。

复杂算术选择器通过称为`apply_generic`的常规&lt;quote&gt;操作&lt;/quote&gt;函数访问表，该函数将通用操作应用于某些参数。 函数`apply_generic`在表中的操作名称和参数类型下查找，并在结果存在时应用结果函数： [[3]](42#footnote-3)

```js
function apply_generic(op, args) {
    const type_tags = map(type_tag, args);
    const fun = get(op, type_tags);
    return fun !== undefined
        ? apply(fun, map(contents, args))
        : Error("No method for these types in apply_generic",
                list(op, type_tags));
}
```

Using `apply_generic` , we can define our generic selectors as follows:

```js
function real_part(z) {
   return apply_generic("real_part", list(z));
}
function imag_part(z) {
   return apply_generic("imag_part", list(z));
}
function magnitude(z) {
   return apply_generic("magnitude", list(z));
}
function angle(z) {
   return apply_generic("angle", list(z));
}
```

Observe that these do not change at all if a new representation is added to the system.

我们还可以从表中提取出包外部程序要使用的构造函数，以便从实部和虚部以及大小和角度生成复数。 就像在 [2.4.2](41) 部分中一样，只要我们具有实部和虚部，就构造矩形数；而当我们具有大小和角度时，构造极数：

```js
function make_from_real_imag(x, y) {
   return get("make_from_real_imag", "rectangular")(x, y);
}
function make_from_mag_ang(r, a) {
   return get("make_from_mag_ang", "polar")(r, a);
}
```

<exercise>Section <ref name="sec:symbolic-differentiation">[2.3.2](36)</ref> described a program that performs symbolic differentiation:

```js
function deriv(exp, variable) {
    return is_number(exp)
           ? 0
           : is_variable(exp)
             ? (is_same_variable(exp, variable)) ? 1 : 0
             : is_sum(exp)
               ? make_sum(deriv(addend(exp), variable), 
                          deriv(augend(exp), variable))
               : is_product(exp)
                 ? make_sum(make_product(multiplier(exp), 
                                deriv(multiplicand(exp), 
                                      variable)),
                            make_product(deriv(multiplier(exp), 
                                               variable), 
                                multiplicand(exp)))
                 // more rules can be added here
                 : Error("unknown expression type in deriv", 
                         exp);
}
```

```js
deriv(list("*", list("*", "x", "y"), list("+", "x", 4)), "x");
// [ "+",
//   [["*", [["*", ["x", ["y", null]]], 
//           [["+", [1, [0, null]]], null]]],
//    [["*",
//      [["+",
//        [["*", ["x", [0, null]]], 
//         [["*", [1, ["y", null]]], null]]],
//         [["+", ["x", [4, null]]], null] ] ],
//     null ]]]
```

We can regard this program as performing a dispatch on the type of the expression to be differentiated. In this situation the <quote>type tag</quote> of the datum is the algebraic operator symbol (such as +) and the operation being performed is `deriv`. We can transform this program into data-directed style by rewriting the basic derivative function as

```js
function deriv(exp, variable) {
    return is_number(exp)
           ? 0
           : is_variable(exp)
             ? (is_same_variable(exp, variable) ? 1 : 0)
             : get("deriv", 
                   operator(exp))(operands(exp), variable);
}
function operator(exp) {
   return head(exp);
}
function operands(exp) {
   return tail(exp);
}
```

1.  说明以上操作。 为什么我们不能将谓词`is_number`和`is_same_variable`吸收到数据定向的调度中？
2.  在上述程序使用的表中编写求和与乘积的衍生函数以及安装它们所需的辅助代码。
3.  选择您喜欢的任何其他微分规则，例如指数（练习 [2.56](36#ex_2.56) ），然后将其安装在此数据定向系统中。
4.  在这个简单的代数操纵器中，表达式的类型是将其绑定在一起的代数运算符。 但是，假设我们以相反的方式对函数进行了索引，因此`deriv`中的调度行看起来像

    ```js
    get(operator(exp), "deriv")(operands(exp), variable);
    ```

    一样。

<button class="btn btn-secondary solution_btn" data-toggle="collapse" href="#solution_42_1_div">Solution</button> <solution>1.  _Explain what was done above. Why can't we assimilate the predicates `is_number` and `is_same_variable` into the data-directed dispatch?_The operator symbols come very handy as &lt;quote&gt;type&lt;/quote&gt; keys in the operator table. For numbers and variables, there aren't such obvious keys, although we could introduce names for those types of expressions, as well, if we change the way expressions are represented as lists.
2.  _Write the functions for derivatives of sums and products, and the auxiliary code required to install them in the table used by the program above._

    ```js
    function deriv_sum(operands, variable) {		
        return make_sum(deriv(addend(operands), variable),
                        deriv(augend(operands), variable));
    }
    function deriv_product(operands, variable) {
        return make_sum(make_product(multiplier(operands),
                            deriv(multiplicand(operands),
                                  variable)),
                        make_product(deriv(multiplier(
                                                 operands),
                                           variable),
                            multiplicand(operands)));
    }
    function install_deriv() {
        put("deriv", "+", deriv_sum);
        put("deriv", "*", deriv_product);
        return "done";
    }
    install_deriv();
    ```

3.  _Choose any additional differentiation rule that you like, such as the one for exponents (Exercise [2.56](36#ex_2.56)), and install it in this data-directed system._

    ```js
    function deriv_exponentiation(operands, variable) {		
        const bas = base(operands);
        const exp = exponent(operands);
        return make_product(exp,
                   make_product(make_exponentiation(bas, make_sum(exp, -1)),
    	                    deriv(bas, variable)));
    }
    function install_exponentiation_extension() {
        put("deriv", "**", deriv_exponentiation);
        return "done";
    }
    install_exponentiation_extension();
    ```

4.  _In this simple algebraic manipulator the type of an expression is the algebraic operator that binds it together. Suppose, however, we indexed the functions in the opposite way, so that the dispatch line in `deriv` looked like

    ```js
    get(operator(exp), "deriv")(operands(exp), variable);
    ```

    What corresponding changes to the derivative system are required?_We would need to change the order of arguments in the installation procedure for the differentiation library:

    ```js
    put("+", "deriv", deriv_sum);
    put("*", "deriv", deriv_product);
    put("**", "deriv", deriv_exponentiation);
    ```</solution></exercise> <exercise>Insatiable Enterprises, Inc., is a highly decentralized conglomerate company consisting of a large number of independent divisions located all over the world. The company's computer facilities have just been interconnected by means of a clever network-interfacing scheme that makes the entire network appear to any user to be a single computer. Insatiable's president, in her first attempt to exploit the ability of the network to extract administrative information from division files, is dismayed to discover that, although all the division files have been implemented as data structures in JavaScript, the particular data structure used varies from division to division. A meeting of division managers is hastily called to search for a strategy to integrate the files that will satisfy headquarters' needs while preserving the existing autonomy of the divisions. Show how such a strategy can be implemented with data-directed programming. As an example, suppose that each division's personnel records consist of a single file, which contains a set of records keyed on employees' names. The structure of the set varies from division to division. Furthermore, each employee's record is itself a set (structured differently from division to division) that contains information keyed under identifiers such as `address` and `salary`. In particular:

1.  为总部实现`get_record`功能，该功能从指定的人员档案中检索指定员工的记录。 该功能应适用于任何部门的文件。 解释各个部门文件的结构。 特别是，必须提供什么类型的信息？
2.  为总部实现`get_salary`功能，该功能从任何部门的人事档案中返回给定员工记录中的薪水信息。 为了使此操作有效，应如何构造记录？
3.  为总部实现`find_employee_record`功能。 这应该在所有部门的文件中搜索给定雇员的记录并返回记录。 假设此函数将雇员的姓名和所有部门文件的列表作为参数。
4.  当Insatiable接管新公司时，必须进行哪些更改才能将新的人员信息纳入中央系统？

<button class="btn btn-secondary solution_btn" data-toggle="collapse" href="#solution_42_2_div">Solution</button> <solution>1.  _Implement for headquarters a `get_record` function that retrieves a specified employee's record from a specified personnel file. The function should be applicable to any division's file. Explain how the individual divisions' files should be structured. In particular, what type information must be supplied?_We are tagging each division's file with a unique identifier for the division, using the tagging functions in section [2.4.2](41). We assume that each division provides an implementation of the `get_record` function and installs it in the company-wide operations table.

    ```js
    function make_insatiable_file(division, file) {
        return pair(division, file);
    }
    function insatiable_file_division(insatiable_file) {
        return head(insatiable_file);
    }
    function insatiable_file_content(insatiable_file) {
        return tail(insatiable_file);
    }
    function get_record(employee_name, insatiable_file) {
        const the_division 
            = insatiable_file_division(insatiable_file);
        const division_record = get("get_record", the_division)
                                (employee_name, 
                                 insatiable_file_content(
                                     insatiable_file);
        return record !== undefined      
               ? attach_tag(the_division, division_record)
               : undefined;
    }
    ```

2.  _Implement for headquarters a `get_salary` function that returns the salary information from a given employee's record from any division's personnel file. How should the record be structured in order to make this operation work?_Every division needs to implement functions such as `get_salary` and install them in Insatiable's operations table. Then, Insatiable's function `get_salary` can look like this:

    ```js
    function make_insatiable_record(division, record) {
        return pair(division, record);
    }
    function insatiable_record_division(insatiable_record) {
        return head(insatiable_record);
    }
    function insatiable_record_content(insatiable_record) {
        return tail(insatiable_record);
    }
    function get_salary(insatiable_record) {
        const the_division = 
            insatiable_record_division(insatiable_record);
        return get("get_salary", the_division)
               (insatiable_record_content);
    }
    ```

    Note that we rely on the fact that any employee record that gets returned by `get_record` is tagged with its division, which is used in the generic function `get_salary` to retrieve the correct implementation from the operation table.
3.  _为总部实现`find_employee_record`功能。 这应该在所有部门的文件中搜索给定雇员的记录并返回记录。 假设此函数将雇员的姓名和所有部门文件的列表作为参数。_

    ```js
    function find_employee_record(employee_name, 
                                  personnel_files) {
        if (is_null(personell_files)) {
            return undefined;
        } else {
            const insatiable_record 
                = get_record(employee_name,
                             head(personell_files));
            return insatiable_record !== undefined
                   ? insatiable_record
                   : find_employee_record(employee_name, 
                                  tail(personell_files));
        }
    }
    ```

4.  _When Insatiable takes over a new company, what changes must be made in order to incorporate the new personnel information into the central system?_We would need to do the following, for each newly acquired company:
    *   确定要用作与新部门相关的任何数据项的标签的名称。
    *   编写所有特定于部门的功能，例如`get_salary`，并使用Division标签将它们安装在公司范围的操作表中。
    *   将员工文件添加到`personell_files`列表中。 注意，这是一个&lt;quote&gt;破坏性&lt;/quote&gt;操作，类似于操作表的扩展，因为数据结构被永久且不可撤销地修改。 [3.3](57) 部分详细说明了此概念。</solution></exercise> 

## 讯息传递

数据定向编程的关键思想是通过显式处理操作和类型表（例如图 [2.22](42#fig_2.22)[2.4.2](41) 节中使用的编程风格通过使每个操作都照顾自己的调度来组织所需的类型调度。 实际上，这会将操作和类型表分解为行，每个通用操作函数代表该表的一行。

另一种实现策略是将表分解为列，而不是使用分派数据类型的&lt;quote&gt;智能操作&lt;/quote&gt;，而是与分派操作名称的&lt;quote&gt;智能数据对象&lt;/quote&gt;一起使用。 我们可以通过安排事情来做到这一点，以便将诸如矩形数之类的数据对象表示为一种函数，该函数以所需的操作名称作为输入并执行所示的操作。 在这种情况下，`make_from_real_imag`可以写为

```js
function make_from_real_imag(x, y) {
    function dispatch(op) {
        return op === "real_part"
            ? x
            : op === "imag_part"
              ? y
              : op === "magnitude"
                ? math_sqrt(square(x) + square(y))
                : op === "angle"
                  ? math_atan(y, x)
                  : Error("Unknown op in make_from_real_imag", 
                          op);
    }
    return dispatch;
}
```

The corresponding `apply_generic` function, which applies a generic operation to an argument, now simply feeds the operation's name to the data object and lets the object do the work:[[4]](42#footnote-4)

```js
function apply_generic(op, arg) {
    return head(arg)(op);
}
```

Note that the value returned by `make_from_real_imag` is a function—the internal `dispatch` function. This is the function that is invoked when `apply_generic` requests an operation to be performed.

这种编程样式称为_消息传递_。 该名称来自以下图像：数据对象是一个实体，它以&lt;quote&gt;消息的形式接收所请求的操作名称。&lt;/quote&gt; 我们已经在 [2.1.3](27) 部分中看到了消息传递的示例，在其中我们看到了如何使用[ 没有数据对象，只有功能。 在这里，我们看到消息传递不是数学技巧，而是用于组织具有通用操作的系统的有用技术。 在本章的其余部分中，我们将继续使用数据定向编程而不是消息传递来讨论通用算术运算。 在第3章中，我们将返回消息传递，我们将看到它可以是构建仿真程序的强大工具。

<exercise>Implement the constructor `make_from_mag_ang` in message-passing style. This function should be analogous to the `make_from_real_imag` function given above.<button class="btn btn-secondary solution_btn" data-toggle="collapse" href="#solution_42_3_div">Solution</button> <solution>```js
function make_from_mag_ang(r, a) {
    function dispatch(op) {
        return op === "real_part"
            ? r * math_cos(a)
            : op === "imag_part"
              ? r * math_sin(a)
              : op === "magnitude"
                ? r
                : op === "angle"
                  ? a
                  : Error("Unknown op in make_from_real_imag", 
                          op);
    }
    return dispatch;
}
```</solution></exercise> <exercise>As a large system with generic operations evolves, new types of data objects or new operations may be needed. For each of the three strategies—generic operations with explicit dispatch, data-directed style, and message-passing-style—describe the changes that must be made to a system in order to add new types or new operations. Which organization would be most appropriate for a system in which new types must often be added? Which would be most appropriate for a system in which new operations must often be added?<button class="btn btn-secondary solution_btn" data-toggle="collapse" href="#solution_42_4_div">Solution</button> <solution>*   _具有显式分派的泛型操作_：对于每种新类型，我们都需要触摸每个泛型接口函数，并添加新的大小写。
*   _数据定向样式_：在这里，通用接口功能的实现可以整齐地打包在每种新类型的&lt;quote&gt;安装&lt;/quote&gt;库中。 我们也可以为新操作安装&lt;quote&gt;安装&lt;/quote&gt;库。
*   _消息传递样式_：就像数据定向样式一样，我们需要为每种新类型编写一个库。 在这种情况下，该库由一个调度函数组成，每个通用接口函数都有一个案例。

Overall, it's probably best to use a data-directed style when we need to frequently add new operations, and message-passing, when we frequently add new types.</solution></exercise>

* * *

[[1]](42#footnote-link-1) We use the list `list("rectangular")` rather than the string `"rectangular"` to allow for the possibility of operations with multiple arguments, not all of the same type.

[[2]](42#footnote-link-2) The type the constructors are installed under needn't be a list because a constructor is always used to make an object of one particular type.

[[3]](42#footnote-link-3) In `apply_generic`, `op` has as its value the first argument to `apply_generic` and `args` has as its value a list of the remaining arguments. <break></break>The function `apply_generic` also uses the primitive function `apply`, which takes two arguments, a function and a list. The function `apply` applies the function, using the elements in the list as arguments. For example,

```js
apply(sum_of_squares, list(1, 3))
```

returns 10.

[[4]](42#footnote-link-4) One limitation of this organization is it permits only generic functions of one argument.

