2.0 Building Abstractions with Data - CloneableX/SICP-learning GitHub Wiki

在第一章我们关注的是计算过程和程式在程序设计上所扮演的角色,介绍了关于基础数据和基础运算的知识,并且通过它们组合定义新程式。同时也将程式作为本地运算过程的模式分析,我们对许多程式实例进行了分析,并归纳总结出一些过程。最后还学习了高级程式,它不仅提升了编程语言的表现力,同时也展示了通用方法的可能性。可见上一章的知识展示了大量关于程序的本质。

在本章,我们将了解更加复杂的数据。虽然上一章通过简单的基础数据解决了很多问题,但许多问题的运算需要依靠更加复杂的数据类型。毕竟程序本身就是要为复杂问题建模的,现实世界中要建模的情况可能有多个方面,所以程序也需要构建拥有多个部分的对象。于是,与第一章关注于程式的视角不同,本章需要将视角转向编程语言的另一个关键部分,它将通过结合数据对象的方式提供组合数据的抽象。

为什么我们需要在编程语言中组合数据?它与我们需要组合程式的原因一致,为了增加程序语言对于概念的抽象能力,提高设计时的模块化能力,以及增加编程语言的表现力。如同定义程式提高了对于基础程式的高阶概念的抽象能力一样,构建复合数据对象也将提供对于基础数据对象的高阶概念抽象能力。

请思考这样一个问题,如果我们打算设计一个系统用于对有理数执行算法,可以具体想象为 add-rat 提供两个有理数并运算得到这两个有理数的和。如果提供的数据是这样两个数据,它们是由两个整数分别作为分子和分母构成,于是程序 add-rat 将被设计为两个程式,一个运算分母的和,一个运算分子的和。但这样的实现可以极为别扭,因为这种实现我们需要追踪分子与分母的对应关系。当这个系统运算大量数据时,程序将要记录大量的中间信息。相对而言,如果我们能够将分子与分母绑定一体效果会更好,程序便能够将其视为一个整体进行统一的运算,这种数据组合形式就是 复合数据对象(compound data object)

使用复合数据可以提高程序的模块化能力。如果我们能够将复合数据作为对象直接操作,便能够将该对象如何处理运算的细节单独分离,这种将数据运算实现细节分离的方法称为 数据抽象(data abstraction)。在后面的内容中,我们将见识数据抽象如何使程序更加容易设计、维护及修改。

同时,使用复合数据可以提高编程语言的表达能力。请思考关于线性组合的概念 ax+by,我们可以定义一个程式传入 a、b、x 和 y,然后返回 ax+by 的值即可。如果参数都是数字,这种表达方式并无不妥,如下列程式:

(define (linear-combination a b x y)
  (+ (* a x) (* b y)))

假设此时我们关心的不是数字,而是运算时使用的运算程式。程式的实际作用也就改变为加上及乘上有理数、复杂数字、多项式等等。可以将程式转换为如下形式:

(define (linear-combination a b x y)
  (add (mul a x) (mul b y)))

其中的 addmul 并不是基础程式 +*,而是更加复杂的程式,它们可以对任何类型的数据应用。此时上述程式的关键点落在 linear-combination 需要为不同类型的数据确定它们对应的乘法和加法如何运算,也就是说对于上述程式参数的实际值是什么不再重要。这个例子说明了为何编程语言需要提供直接维护复合对象的能力,如果没有这种能力,上述程式将无法在不知晓不同类型如何运算 addmul 的情况下计算结果。

本章以实现数据算法系统开始,其中会包含关于复合数据和数据抽象的背景知识。就如同复合程式,主要的问题依然是抽象技术的复杂性,以及如何在程序的各个部分进行合适的数据抽象。

并且我们将了解到其中的关键是提供这种结合数据的能力,使数据对象能够构建出更加复杂的数据对象,其中有很多种类型的组合。同时我们还会发现并不特殊的结合数据方式,就是程式。这将进一步模糊程式与数据之间的区别,这种区别在第一章结束时已经变得十分脆弱了。我们也将探索表现树形和序列的技术。在处理复合数据时将引入一个关键性概念—— 闭包(closure),这种能力使我们不止能够组合基础数据,也可以组合数据对象。另一个关键概念是组合数据对象通过混合匹配的方式作为 协议接口(conventional interfaces) 为构建的程序模块服务。我们还会通过闭包表达图形化语言的方式讲解一些关键概念。

然后会讨论 标识表达式(symbolic expression) 在编程语言中的表现力,这种表达式以任意标识作为数据,而不是数字。还会探索几种表示一组对象的方式,接着我们会发现一个给定的数学函数可以存在多种不同的运算过程,同样一个给定的数字结构也可以有多种更加简单的对象表示,这些不同的表现方式将对运算过程的时间、空间消耗产生重大影响。接着还会在标识表达式、集合和信息编码的情境中讨论这些概念。

接着,我们要探索数据在程序的不同部分表现不同的问题。此问题要通过实现 通用运算符(generic operations) 实现,而模块化地维护通用运算符的表现需要比单独的数据抽象更加强大的抽象边界。所以,我们将介绍 数据导向编程(data-directed programming) 技术提供数据分离设计及结合运算能力。为了说明系统设计方法的能力,我们在本章结尾将实现一个多项式中执行标识算法的包,其中多项式的系数可以是整数、有理数、复杂数或其他多项式。