# 2使用数据构建抽象

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

 <chapter>> 现在，我们进入了数学抽象的决定性步骤：我们忘记了符号代表什么。 ……[数学家]不必闲着； 他可以使用这些符号执行许多操作，而无需查看它们代表的东西。Hermann Weyl 数学思维方式

<chaptercontent></chaptercontent>

我们在第1章集中讨论了计算过程以及函数在程序设计中的作用。 我们看到了如何使用原始数据（数字）和原始运算（算术运算），如何通过组合，条件和参数的使用来组合函数以形成复合函数，以及如何通过使用函数声明来抽象过程。 我们看到功能可以看作是流程局部演化的模式，并且我们对功能中包含的流程的一些常见模式进行了分类，推理和简单算法分析。 我们还看到，高阶函数使我们能够操纵通用的计算方法，从而进行推理，从而增强了语言的功能。 这是编程的本质。

在本章中，我们将研究更复杂的数据。 第1章中的所有功能都对简单的数值数据起作用，而简单的数据不足以解决我们希望通过计算解决的许多问题。 程序通常被设计为对复杂现象进行建模，并且往往不是必须构造具有多个部分的计算对象才能对具有多个方面的真实世界进行建模。 因此，虽然我们在第1章中的重点是通过组合函数以形成复合函数来构建抽象，但在本章中，我们将转向任何编程语言的另一个关键方面：通过组合数据对象以形成_来构建抽象的方法 复合数据_。

为什么我们要使用编程语言来合成数据？ 出于同样的原因，我们需要复合功能：提高设计程序的概念水平，提高设计的模块性，并增强语言的表达能力。 正如声明函数的能力使我们能够在比语言的原始操作更高的概念级别上处理过程一样，构造复合数据对象的能力使我们能够在比语言的原始操作更高的概念级别上处理数据。 语言的原始数据对象。

考虑设计一个系统以执行有理数运算的任务。 我们可以想象一个操作`add_rat`接受两个有理数并产生它们的和。 就简单数据而言，有理数可以认为是两个整数：分子和分母。 因此，我们可以设计一个程序，其中每个有理数将由两个整数（一个分子和一个分母）表示，并且`add_rat`将由两个函数（一个产生和的分子而一个产生分母）实现。 。 但这很尴尬，因为那时我们需要明确地跟踪哪些分子对应于哪些分母。 在旨在对许多有理数执行许多操作的系统中，此类簿记详细信息将使程序严重混乱，更不用说它们会对我们的大脑产生什么影响。 如果我们可以&lt;quote&gt;将分子和分母&lt;/quote&gt;粘合在一起以形成一对_复合数据对象_，那么程序可以以与 一个有理数作为一个概念单元。

使用复合数据还使我们能够提高程序的模块化程度。 如果我们可以直接将有理数作为对象直接操纵，那么我们可以将程序中处理有理数本身的部分与有理数如何表示为整数对的细节分开。 将处理数据对象表示方式的程序各部分与处理数据对象的方式分开的一般技术是一种功能强大的设计方法，称为_数据抽象_。 我们将看到数据抽象如何使程序更易于设计，维护和修改。

复合数据的使用真正提高了我们编程语言的表达能力。 考虑形成&lt;quote&gt;线性组合&lt;/quote&gt; ![ax%2Bby](img/db6f0b307407c8955947009dc4a9838f.jpg)的想法。 我们可能想编写一个函数，该函数接受![a](img/070b1af5eca3a5c5d72884b536090f17.jpg)，![b](img/6872867a863714d15d9a0d64c20734ce.jpg)，![x](img/40779fc60a53ff2b70f832ec10cade09.jpg)和![y](img/c592009395c2de830215c39f7bb6f97b.jpg)作为参数并返回![ax%2Bby](img/db6f0b307407c8955947009dc4a9838f.jpg)的值。 如果参数是数字，那么这并不困难，因为我们可以很容易地声明函数

```js
function linear_combination(a, b, x, y) {
    return a * x + b * y;
}
```

但是，假设我们不仅仅关注数字。 假设我们想描述一个过程，该过程在定义加法和乘法（无论有理数，复数，多项式等）时都会形成线性组合。 我们可以将其表达为形式的函数

```js
function linear_combination(a, b, x, y) {
    return add(mul(a, x), mul(b, y));
}
```

where `add` and `mul` are not the primitive functions `+` and `*` but rather more complex things that will perform the appropriate operations for whatever kinds of data we pass in as the arguments `a`, `b`, `x`, and `y`. The key point is that the only thing `linear_combination` should need to know about `a`, `b`, `x`, and `y` is that the functions `add` and `mul` will perform the appropriate manipulations. From the perspective of the function `linear_combination`, it is irrelevant what `a`, `b`, `x`, and `y` are and even more irrelevant how they might happen to be represented in terms of more primitive data. This same example shows why it is important that our programming language provide the ability to manipulate compound objects directly: Without this, there is no way for a function such as `linear_combination` to pass its arguments along to `add` and `mul` without having to know their detailed structure.[[1]](23#footnote-1)

我们通过实现上述有理数算法系统来开始本章。 这将构成我们讨论复合数据和数据抽象的背景。 与复合函数一样，要解决的主要问题是抽象作为一种解决复杂性的技术，我们将看到数据抽象如何使我们能够在程序的不同部分之间建立合适的_抽象障碍_。

我们将看到形成复合数据的关键是编程语言应提供某种&lt;quote&gt;胶水&lt;/quote&gt;，以便可以将数据对象组合起来以形成更复杂的数据对象。 有很多可能的胶水。 实际上，我们将发现如何完全不使用特殊的&lt;quote&gt;数据&lt;/quote&gt;操作而仅使用函数来形成复合数据。 这将进一步模糊&lt;quote&gt;函数&lt;/quote&gt;和&lt;quote&gt;数据之间的区别，&lt;/quote&gt;在第1章末尾已经变得微不足道了。我们还将探索一些表示序列和树的常规技术。 处理复合数据的一个关键思想是_闭包_的概念—我们用于组合数据对象的胶水不仅应允许我们组合原始数据对象，而且还应组合复合数据对象。 另一个关键思想是复合数据对象可以用作_常规接口_，以混合和匹配的方式组合程序模块。 我们通过展示一种利用闭包的简单图形语言来说明其中的一些想法。

然后，我们将通过引入_符号表达式_来增强语言的表示能力，该数据的基本组成部分可以是任意符号，而不仅仅是数字。 我们探索了代表对象集的各种替代方法。 我们将发现，就像给定的数值函数可以通过许多不同的计算过程来计算一样，给定的数据结构可以用更简单的对象表示的方式有很多，并且表示的选择可能对对象产生重大影响。 处理数据的流程的时间和空间要求。 我们将在符号区分，集合表示和信息编码的背景下研究这些思想。

接下来，我们将讨论处理可能由程序的不同部分不同表示的数据的问题。 这导致需要实现_通用操作_，该操作必须处理许多不同类型的数据。 在存在通用操作的情况下保持模块性需要比单独使用简单数据抽象所能建立的强大的抽象障碍。 特别是，我们引入_数据定向编程_作为一种技术，该技术允许单独设计各个数据表示形式，然后将_进行附加组合_（即，不做任何修改）。 为了说明这种方法在系统设计中的强大功能，我们通过将学到的知识应用于对多项式执行符号算术的程序包的实现来结束本章，其中多项式的系数可以是整数，有理数，复数 ，甚至其他多项式。

<chaptercontent></chaptercontent>

* * *

[[1]](23#footnote-link-1) The ability to directly manipulate functions provides an analogous increase in the expressive power of a programming language. For example, in section <ref name="sec:procedures-as-parameters">[1.3.1](19)</ref> we introduced the `sum` function, which takes a function `term` as an argument and computes the sum of the values of `term` over some specified interval. In order to define `sum`, it is crucial that we be able to speak of a function such as `term` as an entity in its own right, without regard for how `term` might be expressed with more primitive operations. Indeed, if we did not have the notion of <quote>a function,</quote> it is doubtful that we would ever even think of the possibility of defining an operation such as `sum`. Moreover, insofar as performing the summation is concerned, the details of how `term` may be constructed from more primitive operations are irrelevant.

</chapter>