提交 e283d5b5 编写于 作者: W wizardforcel

2023-03-30 17:22:06

上级 7c2b5576
......@@ -3,7 +3,7 @@
# 1
用函数构建抽象
> 在头脑的活动中,它对简单的想法施加力量,主要是这三种:1。将几个简单的想法组合成一个复合的想法,这样所有复杂的想法就产生了。2.第二种是把两个观念,不管是简单的还是复杂的,放在一起,把它们放在一起,以便立刻看到它们,而不是把它们结合成一个,这样它就能得到所有关于关系的观念。3.第三是将它们与伴随它们真实存在的所有其他观念分开:这被称为抽象,因此它的所有一般观念都是由它们构成的。
> 在头脑的活动中,它对简单的想法施加力量,主要是这三种:1)将几个简单的想法组合成一个复合的想法,这样所有复杂的想法就产生了。2)第二种是把两个观念,不管是简单的还是复杂的,放在一起,把它们放在一起,以便立刻看到它们,而不是把它们结合成一个,这样它就能得到所有关于关系的观念。3)第三是将它们与伴随它们真实存在的所有其他观念分开:这被称为抽象,因此它的所有一般观念都是由它们构成的。
>
> —约翰·洛克,一篇关于人类理解的论文(1690)
......@@ -172,8 +172,8 @@ circumference;
我们在这一章的目标之一是分离出关于程序化思考的问题。作为一个恰当的例子,让我们考虑,在评估运算符组合时,解释器本身也在遵循一个过程。
* 要评估运算符组合,请执行以下操作:
1. 1。对组合的操作数表达式求值。
2. 2。将运算符表示的函数应用于作为操作数值的参数。
1. 对组合的操作数表达式求值。
2. 将运算符表示的函数应用于作为操作数值的参数。
甚至这个简单的规则也说明了一般过程中的一些要点。首先,观察第一步,为了完成组合的求值过程,我们必须首先对组合的每个操作数执行求值过程。因此,评估规则本质上是递归;也就是说,作为其步骤之一,它包括调用规则本身的需要。
......@@ -183,7 +183,7 @@ circumference;
(2 + 4 * 6) * (3 + 12);
```
要求评估规则应用于四种不同的组合。我们可以通过用树的形式表示这种组合来获得这个过程的图片,如图图 1.1:所示。每个组合都由一个节点表示,节点的分支对应于该组合的运算符和操作数。终端节点(即没有分支的节点)代表运算符或数字。从树的角度来看求值,我们可以想象操作数的值向上渗透,从终端节点开始,然后在越来越高的级别上组合。总的来说,我们将会看到递归是一种非常强大的处理层次化、树状对象的技术。事实上,评估规则的“向上过滤值”形式是一种被称为树累积的通用过程的示例。
要求评估规则应用于四种不同的组合。我们可以通过用树的形式表示这种组合来获得这个过程的图片,如图 1.1 所示。每个组合都由一个节点表示,节点的分支对应于该组合的运算符和操作数。终端节点(即没有分支的节点)代表运算符或数字。从树的角度来看求值,我们可以想象操作数的值向上渗透,从终端节点开始,然后在越来越高的级别上组合。总的来说,我们将会看到递归是一种非常强大的处理层次化、树状对象的技术。事实上,评估规则的“向上过滤值”形式是一种被称为树累积的通用过程的示例。
![c1-fig-0001.jpg](img/c1-fig-0001.jpg)
......@@ -715,7 +715,7 @@ function sqrt_iter(guess, x) {
函数`sqrt`是由一组相互定义的函数定义的流程的第一个例子。注意`sqrt_iter`的声明是递归;也就是说,函数是根据它本身来定义的。能够根据函数本身来定义函数的想法可能会令人不安;这种“循环”定义怎么可能有意义,这似乎还不清楚,更不用说指定一个由计算机执行的明确定义的过程了。这将在第 1.2 节中详细讨论。但是首先让我们考虑一下`sqrt`这个例子所展示的其他一些要点。
请注意,计算平方根的问题自然会分解成许多子问题:如何判断猜测是否足够好,如何改进猜测,等等。这些任务中的每一项都由单独的功能来完成。整个`sqrt`程序可以被视为一簇功能(如图 1.2:所示),反映了问题分解成子问题。
请注意,计算平方根的问题自然会分解成许多子问题:如何判断猜测是否足够好,如何改进猜测,等等。这些任务中的每一项都由单独的功能来完成。整个`sqrt`程序可以被视为一簇功能(如图 1.2:所示),反映了问题分解成子问题。
![c1-fig-0002.jpg](img/c1-fig-0002.jpg)
......@@ -865,7 +865,7 @@ function factorial(n) {
}
```
我们可以用 1.1.5 节的替代模型来观看动作计算 6 中的这个函数!,如图 1.3:所示。
我们可以用 1.1.5 节的替代模型来观看动作计算 6 中的这个函数!,如图 1.3:所示。
![c1-fig-0003.jpg](img/c1-fig-0003.jpg)
......@@ -895,7 +895,7 @@ function fact_iter(product, counter, max_count) {
}
```
和前面一样,我们可以用代入模型来形象化计算 6 的过程!,如图 1.4:所示。
和前面一样,我们可以用代入模型来形象化计算 6 的过程!,如图 1.4:所示。
![c1-fig-0004.jpg](img/c1-fig-0004.jpg)
......@@ -998,7 +998,7 @@ function fib(n) {
}
```
考虑一下这种计算的模式。为了计算`fib(5)`,我们计算`fib(4)``fib(3)`。为了计算`fib(4)`,我们计算`fib(3)``fib(2)`。一般来说,演化后的流程看起来像一棵树,如图 1.5:所示。请注意,分支在每一层都分裂成两个(底部除外);这反映了一个事实,即`fib`函数每次被调用时都会调用自己两次。
考虑一下这种计算的模式。为了计算`fib(5)`,我们计算`fib(4)``fib(3)`。为了计算`fib(4)`,我们计算`fib(3)``fib(2)`。一般来说,演化后的流程看起来像一棵树,如图 1.5:所示。请注意,分支在每一层都分裂成两个(底部除外);这反映了一个事实,即`fib`函数每次被调用时都会调用自己两次。
![c1-fig-0005.jpg](img/c1-fig-0005.jpg)
......
......@@ -184,7 +184,7 @@ print_rat(add_rat(one_third, one_third));
在继续更多复合数据和数据抽象的例子之前,让我们考虑一下有理数例子提出的一些问题。我们根据构造函数`make_rat`和选择器`numer``denom`定义了有理数运算。一般而言,数据抽象的基本思想是为每种类型的数据对象标识一组基本操作,根据这些操作,该类型的数据对象的所有操作将被表达,然后在操作数据时仅使用那些操作。
我们可以想象有理数系统的结构,如图 2.1:所示。水平线代表隔离系统不同“层次”的抽象障碍。在每一层,屏障将使用数据抽象的程序(上图)与实现数据抽象的程序(下图)分开。使用有理数的程序只根据有理数包提供的“公共使用”的函数来操纵它们:`add_rat``sub_rat``mul_rat``div_rat``equal_rat`。反过来,这些仅根据构造函数和选择器`make_rat``numer``denom`来实现,它们本身是成对实现的。只要可以通过使用`pair``head``tail`来操作 pairs,pairs 是如何实现的细节与其余的有理数包无关。实际上,每一层的功能都是定义抽象障碍和连接不同层的接口。这个简单的想法有很多优点。一个优点是它使程序更容易维护和修改。任何复杂的数据结构都可以用编程语言提供的原始数据结构以多种方式表示。当然,表示的选择会影响对其进行操作的程序;因此,如果表示在以后某个时间被改变,所有这样的程序可能必须相应地被修改。在大型程序的情况下,这项任务可能既费时又费钱,除非通过设计将对表示的依赖限制在很少的程序模块中。
我们可以想象有理数系统的结构,如图 2.1:所示。水平线代表隔离系统不同“层次”的抽象障碍。在每一层,屏障将使用数据抽象的程序(上图)与实现数据抽象的程序(下图)分开。使用有理数的程序只根据有理数包提供的“公共使用”的函数来操纵它们:`add_rat``sub_rat``mul_rat``div_rat``equal_rat`。反过来,这些仅根据构造函数和选择器`make_rat``numer``denom`来实现,它们本身是成对实现的。只要可以通过使用`pair``head``tail`来操作 pairs,pairs 是如何实现的细节与其余的有理数包无关。实际上,每一层的功能都是定义抽象障碍和连接不同层的接口。这个简单的想法有很多优点。一个优点是它使程序更容易维护和修改。任何复杂的数据结构都可以用编程语言提供的原始数据结构以多种方式表示。当然,表示的选择会影响对其进行操作的程序;因此,如果表示在以后某个时间被改变,所有这样的程序可能必须相应地被修改。在大型程序的情况下,这项任务可能既费时又费钱,除非通过设计将对表示的依赖限制在很少的程序模块中。
![c2-fig-0001.jpg](img/c2-fig-0001.jpg)
......@@ -864,7 +864,7 @@ function count_leaves(x) {
##### 练习 2.24
假设我们评估表达式`list(1, list(2, list(3, 4)))`。给出解释器打印的结果,相应的盒指针结构,以及对树的解释(如图 2.6: )。
假设我们评估表达式`list(1, list(2, list(3, 4)))`。给出解释器打印的结果,相应的盒指针结构,以及对树的解释(如图 2.6: )。
##### 练习 2.25
......@@ -1087,7 +1087,7 @@ function even_fibs(n) {
* 过滤它们,选择偶数的;和
* 使用`pair`累加结果,从空列表开始。
信号处理工程师会发现很自然地将这些过程概念化为流经级联阶段的信号,每个阶段实现程序计划的一部分,如图 2.7:所示。在`sum_odd_squares`中,我们从一个枚举器开始,它生成一个由给定树的叶子组成的“信号”。该信号通过一个滤波器,该滤波器除了奇数元素之外,其他元素都被滤除。产生的信号依次通过图,图是一个将`square`功能应用于每个元素的“传感器”。然后,映射的输出被馈送到一个累加器,该累加器使用`+`从初始 0 开始合并元素。`even_fibs`的计划是类似的。
信号处理工程师会发现很自然地将这些过程概念化为流经级联阶段的信号,每个阶段实现程序计划的一部分,如图 2.7:所示。在`sum_odd_squares`中,我们从一个枚举器开始,它生成一个由给定树的叶子组成的“信号”。该信号通过一个滤波器,该滤波器除了奇数元素之外,其他元素都被滤除。产生的信号依次通过图,图是一个将`square`功能应用于每个元素的“传感器”。然后,映射的输出被馈送到一个累加器,该累加器使用`+`从初始 0 开始合并元素。`even_fibs`的计划是类似的。
![c2-fig-0007.jpg](img/c2-fig-0007.jpg)
......@@ -1547,7 +1547,7 @@ flatmap(new_row =>
当我们在 1.1 节开始学习编程时,我们强调了通过关注语言的原语、组合方式和抽象方式来描述语言的重要性。我们将遵循这个框架。
这种画面语言的优雅之处在于只有一种元素,叫做画家。画家绘制图像,该图像被移动和缩放以适合指定的平行四边形框架。例如,有一个我们称之为`wave`的原始画家,他画了一个粗糙的线条画,如图图 2.10:所示。图画的实际形状取决于画框——图 2.10:中的所有四幅图像都是由同一个`wave`画家绘制的,但是相对于四个不同的画框。画家可以比这更精细:名为`rogers`的原始画家为麻省理工学院的创始人威廉·巴顿·罗杰斯画了一幅画,如图图 2.11:所示。[^(21)](#c2-fn-0021)图 2.11:中的四幅图像是针对与图 2.10:中的`wave`图像相同的四帧绘制的。
这种画面语言的优雅之处在于只有一种元素,叫做画家。画家绘制图像,该图像被移动和缩放以适合指定的平行四边形框架。例如,有一个我们称之为`wave`的原始画家,他画了一个粗糙的线条画,如图 2.10:所示。图画的实际形状取决于画框——图 2.10:中的所有四幅图像都是由同一个`wave`画家绘制的,但是相对于四个不同的画框。画家可以比这更精细:名为`rogers`的原始画家为麻省理工学院的创始人威廉·巴顿·罗杰斯画了一幅画,如图 2.11:所示。[^(21)](#c2-fn-0021)图 2.11:中的四幅图像是针对与图 2.10:中的`wave`图像相同的四帧绘制的。
![c2-fig-0010.jpg](img/c2-fig-0010.jpg)
......@@ -1626,7 +1626,7 @@ function corner_split(painter, n) {
}
```
通过适当放置四个`corner_split`副本,我们得到一个名为`square_limit`的图案,其在`wave``rogers`中的应用如图 2.9:所示:
通过适当放置四个`corner_split`副本,我们得到一个名为`square_limit`的图案,其在`wave``rogers`中的应用如图 2.9:所示:
```js
function square_limit(painter, n) {
......@@ -2379,7 +2379,7 @@ function adjoin_set(x, set) {
}
```
上面声称搜索树可以在对数数量的步骤中执行,这是基于树是“平衡的”的假设,即每棵树的左右子树具有大约相同数量的元素,使得每个子树包含其父树的大约一半的元素。但是我们怎么能确定我们建造的树是平衡的呢?即使我们从平衡的树开始,用`adjoin_set`添加元素也可能产生不平衡的结果。由于新邻接元素的位置取决于该元素与集合中已有项目的比较情况,我们可以预期,如果我们“随机”添加元素,树将趋于平均平衡。但这不是保证。例如,如果我们从一个空集开始,并依次邻接数字 1 到 7,我们最终会得到一个高度不平衡的树,如图 2.17:所示。在这棵树中,所有左边的子树都是空的,所以它比简单的有序列表没有优势。解决这个问题的一种方法是定义一种操作,将任意树转换成具有相同元素的平衡树。然后,我们可以在每隔几个`adjoin_set`操作之后执行这个转换,以保持我们的集合平衡。还有其他方法可以解决这个问题,其中大多数涉及设计新的数据结构,搜索和插入都可以在θ(logn 步中完成。 [^(36)](#c2-fn-0036)
上面声称搜索树可以在对数数量的步骤中执行,这是基于树是“平衡的”的假设,即每棵树的左右子树具有大约相同数量的元素,使得每个子树包含其父树的大约一半的元素。但是我们怎么能确定我们建造的树是平衡的呢?即使我们从平衡的树开始,用`adjoin_set`添加元素也可能产生不平衡的结果。由于新邻接元素的位置取决于该元素与集合中已有项目的比较情况,我们可以预期,如果我们“随机”添加元素,树将趋于平均平衡。但这不是保证。例如,如果我们从一个空集开始,并依次邻接数字 1 到 7,我们最终会得到一个高度不平衡的树,如图 2.17:所示。在这棵树中,所有左边的子树都是空的,所以它比简单的有序列表没有优势。解决这个问题的一种方法是定义一种操作,将任意树转换成具有相同元素的平衡树。然后,我们可以在每隔几个`adjoin_set`操作之后执行这个转换,以保持我们的集合平衡。还有其他方法可以解决这个问题,其中大多数涉及设计新的数据结构,搜索和插入都可以在θ(logn 步中完成。 [^(36)](#c2-fn-0036)
![c2-fig-0017.jpg](img/c2-fig-0017.jpg)
......@@ -2724,7 +2724,7 @@ Sha boom
## 2.4 抽象数据的多重表示
我们引入了数据抽象,这是一种结构化系统的方法,使得程序的大部分可以独立于实现程序所操作的数据对象的选择而被指定。例如,我们在 2.1.1 节中看到了如何根据计算机语言构造复合数据的基本机制,将设计使用有理数的程序的任务与实现有理数的任务分开。关键思想是建立一个抽象屏障——在本例中,是有理数的选择器和构造器(`make_rat``numer``denom`)——将有理数的使用方式与它们在列表结构方面的底层表示隔离开来。类似的抽象障碍将执行有理数运算的函数(`add_rat``sub_rat``mul_rat``div_rat`)的细节与使用有理数的“高级”函数隔离开来。最终程序的结构如图 2.1:所示。
我们引入了数据抽象,这是一种结构化系统的方法,使得程序的大部分可以独立于实现程序所操作的数据对象的选择而被指定。例如,我们在 2.1.1 节中看到了如何根据计算机语言构造复合数据的基本机制,将设计使用有理数的程序的任务与实现有理数的任务分开。关键思想是建立一个抽象屏障——在本例中,是有理数的选择器和构造器(`make_rat``numer``denom`)——将有理数的使用方式与它们在列表结构方面的底层表示隔离开来。类似的抽象障碍将执行有理数运算的函数(`add_rat``sub_rat``mul_rat``div_rat`)的细节与使用有理数的“高级”函数隔离开来。最终程序的结构如图 2.1:所示。
这些数据抽象障碍是控制复杂性的强大工具。通过隔离数据对象的底层表示,我们可以将设计一个大型程序的任务分成可以单独执行的更小的任务。但是这种数据抽象还不够强大,因为谈论数据对象的“底层表示”并不总是有意义的。
......@@ -3375,7 +3375,7 @@ function make_complex_from_mag_ang(r, a){
}
```
我们这里有一个两级标记系统。一个典型的复数,如矩形形式的 3 + 4 i ,将被表示为如图 2.24:所示。外部标签(`"complex"`)用于将编号指向复合包装。一旦进入复杂包装,下一个标签(`"rectangular"`)用于将数字指向矩形包装。在一个大而复杂的系统中,可能有许多层次,每一层都通过一般操作与下一层连接。当数据对象被“向下”传递时,用于将它导向适当包的外部标签被剥离(通过应用`contents`),下一层标签(如果有)变得可见,用于进一步的分派。
我们这里有一个两级标记系统。一个典型的复数,如矩形形式的 3 + 4 i ,将被表示为如图 2.24:所示。外部标签(`"complex"`)用于将编号指向复合包装。一旦进入复杂包装,下一个标签(`"rectangular"`)用于将数字指向矩形包装。在一个大而复杂的系统中,可能有许多层次,每一层都通过一般操作与下一层连接。当数据对象被“向下”传递时,用于将它导向适当包的外部标签被剥离(通过应用`contents`),下一层标签(如果有)变得可见,用于进一步的分派。
![c2-fig-0024.jpg](img/c2-fig-0024.jpg)
......@@ -3482,7 +3482,7 @@ function apply_generic(op, args) {
##### 类型的层次结构
上面介绍的强制方案依赖于类型对之间自然关系的存在。通常在不同类型之间的关系上有更多的“全局”结构。例如,假设我们正在构建一个通用的算术系统来处理整数、有理数、实数和复数。在这样一个系统中,把一个整数看成一种特殊的有理数是很自然的,这种有理数又是一种特殊的实数,而实数又是一种特殊的复数。我们实际拥有的是所谓的类型的层次结构,例如,整数是有理数的一个子类型(也就是说,任何可以应用于有理数的运算都可以自动应用于整数)。相反,我们说有理数形成了整数的一个超类型。这里我们有一个非常简单的层次结构,其中每个类型最多有一个超类型和一个子类型。这种结构称为塔,如图 2.25:所示。
上面介绍的强制方案依赖于类型对之间自然关系的存在。通常在不同类型之间的关系上有更多的“全局”结构。例如,假设我们正在构建一个通用的算术系统来处理整数、有理数、实数和复数。在这样一个系统中,把一个整数看成一种特殊的有理数是很自然的,这种有理数又是一种特殊的实数,而实数又是一种特殊的复数。我们实际拥有的是所谓的类型的层次结构,例如,整数是有理数的一个子类型(也就是说,任何可以应用于有理数的运算都可以自动应用于整数)。相反,我们说有理数形成了整数的一个超类型。这里我们有一个非常简单的层次结构,其中每个类型最多有一个超类型和一个子类型。这种结构称为塔,如图 2.25:所示。
![c2-fig-0025.jpg](img/c2-fig-0025.jpg)
......
......@@ -770,7 +770,7 @@ if (balance >= amount) {
}
```
由此产生的环境结构如图 3.8:所示。被求值的表达式同时引用了`amount``balance`。变量`amount`将在环境中的第一帧中找到,而`balance`将通过跟随包围环境指针到 E1 找到。
由此产生的环境结构如图 3.8:所示。被求值的表达式同时引用了`amount``balance`。变量`amount`将在环境中的第一帧中找到,而`balance`将通过跟随包围环境指针到 E1 找到。
![c3-fig-0008.jpg](img/c3-fig-0008.jpg)
......@@ -963,7 +963,7 @@ set_balance(account, new-value)
成对的基本变异器是`set_head``set_tail`。函数`set_head`有两个参数,第一个必须是一对。它修改这一对,用指向第二个参数`set_head`的指针替换`head`指针。 [^(19)](#c3-fn-0019)
举个例子,假设`x`被绑定到`list(list("a", "b"), "c", "d")``y`被绑定到`list("e", "f")`,如图图 3.12:所示。对表达式`set_head(x, y)`求值会修改`x`绑定到的对,用`y`的值替换它的`head`。操作结果如图图 3.13:所示。结构`x`已经修改,现在等同于`list(list("e", "f"), "c", "d")`。由被替换的指针标识的代表列表`list("a", "b")`的对现在从原始结构中分离出来。 [^(20)](#c3-fn-0020)
举个例子,假设`x`被绑定到`list(list("a", "b"), "c", "d")``y`被绑定到`list("e", "f")`,如图 3.12:所示。对表达式`set_head(x, y)`求值会修改`x`绑定到的对,用`y`的值替换它的`head`。操作结果如图 3.13:所示。结构`x`已经修改,现在等同于`list(list("e", "f"), "c", "d")`。由被替换的指针标识的代表列表`list("a", "b")`的对现在从原始结构中分离出来。 [^(20)](#c3-fn-0020)
![c3-fig-0012.jpg](img/c3-fig-0012.jpg)
......@@ -979,7 +979,7 @@ set_balance(account, new-value)
const z = pair(y, tail(x));
```
`x``y`绑定到图 3.12 的原列表中。名称`z`现在被绑定到由`pair`操作创建的新对;`x`绑定的列表不变。`set_tail`的操作与`set_head`类似。唯一不同的是,替换的是指针对的`tail`指针,而不是`head`指针。执行`set_tail(x, y)`对图 3.12:列表的影响如图 3.15:所示。这里`x``tail`指针已经被指向`list("e", "f")`的指针所取代。还有,曾经是`x``tail`的列表`list("c", "d")`,现在脱离了结构。
`x``y`绑定到图 3.12 的原列表中。名称`z`现在被绑定到由`pair`操作创建的新对;`x`绑定的列表不变。`set_tail`的操作与`set_head`类似。唯一不同的是,替换的是指针对的`tail`指针,而不是`head`指针。执行`set_tail(x, y)`对图 3.12:列表的影响如图 3.15:所示。这里`x``tail`指针已经被指向`list("e", "f")`的指针所取代。还有,曾经是`x``tail`的列表`list("c", "d")`,现在脱离了结构。
![c3-fig-0014.jpg](img/c3-fig-0014.jpg)
......@@ -1118,7 +1118,7 @@ const x = list("a", "b");
const z1 = pair(x, x);
```
如图 3.16:所示,`z1`为一对,其`head``tail`均指向同一对`x`。由`z1``head``tail`共享`x``pair`实现的直接方式的结果。一般来说,使用`pair`来构建列表将会产生一个互连的对结构,其中许多单独的对被许多不同的结构共享。
如图 3.16:所示,`z1`为一对,其`head``tail`均指向同一对`x`。由`z1``head``tail`共享`x``pair`实现的直接方式的结果。一般来说,使用`pair`来构建列表将会产生一个互连的对结构,其中许多单独的对被许多不同的结构共享。
![c3-fig-0016.jpg](img/c3-fig-0016.jpg)
......@@ -1965,7 +1965,7 @@ probe("carry", carry);
"carry 0, new value = 0"
```
接下来,我们连接半加法器电路中的导线(如图 3.25: ),将`input_1`上的信号设置为 1,并运行模拟:
接下来,我们连接半加法器电路中的导线(如图 3.25: ),将`input_1`上的信号设置为 1,并运行模拟:
```js
half_adder(input_1, input_2, sum, carry);
......@@ -2164,7 +2164,7 @@ function celsius_fahrenheit_converter(c, f) {
}
```
该函数创建内部连接器`u``v``w``x``y`,并使用原语约束构造函数`adder``multiplier``constant`将它们链接起来,如图 3.28:所示。正如 3.3.4 节中的数字电路模拟器一样,用函数来表达这些原始元素的组合自动为我们的语言提供了一种对复合对象进行抽象的方法。
该函数创建内部连接器`u``v``w``x``y`,并使用原语约束构造函数`adder``multiplier``constant`将它们链接起来,如图 3.28:所示。正如 3.3.4 节中的数字电路模拟器一样,用函数来表达这些原始元素的组合自动为我们的语言提供了一种对复合对象进行抽象的方法。
为了观察运行中的网络,我们可以在连接器`C``F`上放置探针,使用类似于我们在第 3.3.4 节中用来监控电线的`probe`功能。在连接器上放置探针将导致每当连接器被赋予一个值时打印一条消息:
......@@ -3372,7 +3372,7 @@ stream_ref(primes, 50);
233
```
思考由`sieve`建立的信号处理系统是很有趣的,如图 3.31:中的“亨德森图”所示。 [^(63)](#c3-fn-0063) 输入流输入到一个“un `pair` er”中,该“un`pair`er”将流的第一个元素与流的其余部分分开。第一个元素用于构建一个整除滤波器,其余的元素通过该滤波器,滤波器的输出被馈送到另一个筛箱。然后,原始的第一元件被连接到内部筛的输出,以形成输出流。因此,不仅流是无限的,信号处理器也是无限的,因为筛子中包含一个筛子。
思考由`sieve`建立的信号处理系统是很有趣的,如图 3.31:中的“亨德森图”所示。 [^(63)](#c3-fn-0063) 输入流输入到一个“un `pair` er”中,该“un`pair`er”将流的第一个元素与流的其余部分分开。第一个元素用于构建一个整除滤波器,其余的元素通过该滤波器,滤波器的输出被馈送到另一个筛箱。然后,原始的第一元件被连接到内部筛的输出,以形成输出流。因此,不仅流是无限的,信号处理器也是无限的,因为筛子中包含一个筛子。
![c3-fig-0031.jpg](img/c3-fig-0031.jpg)
......@@ -4088,7 +4088,7 @@ function integral(integrand, initial_value, dt) {
##### 练习 3.80
串联 RLC 电路由一个电阻、一个电容和一个电感串联而成,如图 3.36:所示。如果 R 、 L 和 C 是电阻、电感和电容,那么这三个元件的电压( v )和电流( i )之间的关系由以下等式描述
串联 RLC 电路由一个电阻、一个电容和一个电感串联而成,如图 3.36:所示。如果 R 、 L 和 C 是电阻、电感和电容,那么这三个元件的电压( v )和电流( i )之间的关系由以下等式描述
![c3-fig-5013.jpg](img/c3-fig-5013.jpg)
......@@ -4224,7 +4224,7 @@ function stream_withdraw(balance, amount_stream) {
用对象建模是强大而直观的,很大程度上是因为这符合与我们所在的世界互动的感觉。然而,正如我们在本章中反复看到的,这些模型提出了约束事件顺序和同步多个过程的棘手问题。避免这些问题的可能性刺激了函数式编程语言的发展,它不包括任何赋值或可变数据的规定。在这种语言中,所有函数都实现了其参数的明确定义的数学函数,其行为不会改变。函数方法对于处理并发系统非常有吸引力。 [^(75)](#c3-fn-0075)
另一方面,如果我们仔细观察,我们可以看到与时间相关的问题也悄悄进入功能模型。当我们希望设计交互系统时,尤其是对独立实体之间的交互进行建模时,会出现一个特别麻烦的领域。例如,再次考虑允许联合银行账户的银行系统的实现。在使用赋值和对象的传统系统中,我们将通过让 Peter 和 Paul 向同一个银行帐户对象发送他们的交易请求来模拟 Peter 和 Paul 共享一个帐户的事实,正如我们在 3.1.3 节中看到的。从流的角度来看,这里没有“对象”本身,我们已经指出,银行帐户可以被建模为一个流程,该流程对一个事务请求流进行操作以产生一个响应流。因此,我们可以通过将彼得的交易请求流与保罗的请求流合并,并将结果提供给银行账户流流程,来模拟彼得和保罗拥有联合银行账户的事实,如图 3.38:所示。
另一方面,如果我们仔细观察,我们可以看到与时间相关的问题也悄悄进入功能模型。当我们希望设计交互系统时,尤其是对独立实体之间的交互进行建模时,会出现一个特别麻烦的领域。例如,再次考虑允许联合银行账户的银行系统的实现。在使用赋值和对象的传统系统中,我们将通过让 Peter 和 Paul 向同一个银行帐户对象发送他们的交易请求来模拟 Peter 和 Paul 共享一个帐户的事实,正如我们在 3.1.3 节中看到的。从流的角度来看,这里没有“对象”本身,我们已经指出,银行帐户可以被建模为一个流程,该流程对一个事务请求流进行操作以产生一个响应流。因此,我们可以通过将彼得的交易请求流与保罗的请求流合并,并将结果提供给银行账户流流程,来模拟彼得和保罗拥有联合银行账户的事实,如图 3.38:所示。
![c3-fig-0038.jpg](img/c3-fig-0038.jpg)
......
......@@ -1112,7 +1112,7 @@ function factorial(n) {
图 4.3:阶乘程序,被视为抽象机器。
同样,我们可以把评价者看作一台非常特殊的机器,它接受对机器的描述作为输入。给定该输入,评估器对自身进行配置以仿真所描述的机器。例如,如果我们将`factorial`的定义提供给评估者,如图 4.4:所示,评估者将能够计算阶乘。
同样,我们可以把评价者看作一台非常特殊的机器,它接受对机器的描述作为输入。给定该输入,评估器对自身进行配置以仿真所描述的机器。例如,如果我们将`factorial`的定义提供给评估者,如图 4.4:所示,评估者将能够计算阶乘。
![c4-fig-0004.jpg](img/c4-fig-0004.jpg)
......@@ -3702,7 +3702,7 @@ job($x, list("computer", "programmer"))
通过使用流来组织针对帧的模式测试。给定一个帧,匹配过程逐个通过数据库条目。对于每一个数据库条目,匹配器产生一个特殊的符号表示匹配失败,或者产生一个帧的扩展。所有数据库条目的结果被收集到一个流中,该流通过一个过滤器来剔除失败。结果是通过匹配数据库中的某个断言来扩展给定帧的所有帧的流。 [^(61)](#c4-fn-0061)
在我们的系统中,一个查询获取一个输入帧流,并对该流中的每一帧执行上述匹配操作,如图 4.5:所示。也就是说,对于输入流中的每个帧,查询通过匹配数据库中的断言来生成一个新的流,该流由该帧的所有扩展组成。所有这些流然后被组合成一个巨大的流,它包含输入流中每个帧的所有可能的扩展。这个流是查询的输出。
在我们的系统中,一个查询获取一个输入帧流,并对该流中的每一帧执行上述匹配操作,如图 4.5:所示。也就是说,对于输入流中的每个帧,查询通过匹配数据库中的断言来生成一个新的流,该流由该帧的所有扩展组成。所有这些流然后被组合成一个巨大的流,它包含输入流中每个帧的所有可能的扩展。这个流是查询的输出。
![c4-fig-0005.jpg](img/c4-fig-0005.jpg)
......@@ -3731,7 +3731,7 @@ can_do_job($x, list("computer", "programmer", "trainee"))
job($person, $x)
```
以与给定的`$x`绑定一致的方式。每个这样的匹配将产生一个包含`$x``$person`绑定的帧。两个查询的`and`可以看作是两个分量查询的串联组合,如图 4.6:所示。通过第一个查询过滤器的帧被第二个查询过滤并进一步扩展。
以与给定的`$x`绑定一致的方式。每个这样的匹配将产生一个包含`$x``$person`绑定的帧。两个查询的`and`可以看作是两个分量查询的串联组合,如图 4.6:所示。通过第一个查询过滤器的帧被第二个查询过滤并进一步扩展。
![c4-fig-0006.jpg](img/c4-fig-0006.jpg)
......@@ -3853,7 +3853,7 @@ rule(lives_near($person_1, $person_2),
##### 查询计算器和驱动程序循环
尽管潜在的匹配操作很复杂,但系统的组织很像任何语言的评估器。协调匹配操作的函数称为`evaluate_query`,它的作用类似于 JavaScript 的`evaluate`函数。函数`evaluate_query`将一个查询和一个帧流作为输入。它的输出是一个帧流,对应于查询模式的成功匹配,扩展了输入流中的一些帧,如图 4.5:所示。和`evaluate`一样,`evaluate_query`对不同类型的表达式(查询)进行分类,并为每种类型分配适当的函数。每个语法形式(`and``or``not``javascript_predicate`)都有一个函数,一个函数用于简单查询。
尽管潜在的匹配操作很复杂,但系统的组织很像任何语言的评估器。协调匹配操作的函数称为`evaluate_query`,它的作用类似于 JavaScript 的`evaluate`函数。函数`evaluate_query`将一个查询和一个帧流作为输入。它的输出是一个帧流,对应于查询模式的成功匹配,扩展了输入流中的一些帧,如图 4.5:所示。和`evaluate`一样,`evaluate_query`对不同类型的表达式(查询)进行分类,并为每种类型分配适当的函数。每个语法形式(`and``or``not``javascript_predicate`)都有一个函数,一个函数用于简单查询。
驱动程序循环类似于本章中其他赋值器的`driver_loop`函数,读取用户输入的查询。对于每个查询,它使用查询和由单个空帧组成的流调用`evaluate_query`。这将产生所有可能匹配的流(空框架的所有可能扩展)。对于结果流中的每个帧,它使用在帧中找到的变量值实例化原始查询。然后打印这个实例化的查询流。 [^(68)](#c4-fn-0068)
......@@ -4122,7 +4122,7 @@ put("and", "evaluate_query", conjoin);
设置`evaluate_query`在遇到`and`时分派给`conjoin`
我们类似地处理`or`查询,如图 4.7:所示。使用 4.4.4.6 部分的`interleave_delayed`函数分别计算并合并`or`的各种析取项的输出流。(参见练习 4.68 和 4.69。)
我们类似地处理`or`查询,如图 4.7:所示。使用 4.4.4.6 部分的`interleave_delayed`函数分别计算并合并`or`的各种析取项的输出流。(参见练习 4.68 和 4.69。)
```js
function disjoin(disjuncts, frame_stream) {
......
......@@ -35,7 +35,7 @@ function gcd(a, b) {
根据常数和寄存器内容计算值的操作在数据路径图中用包含操作名称的梯形表示。例如,图 5.1:中标有`rem`的方框表示计算其所连接的寄存器`a``b`的剩余内容的操作。箭头(无按钮)从输入寄存器和常量指向方框,箭头将运算的输出值连接到寄存器。测试由包含测试名称的圆圈表示。比如我们的 GCD 机器有一个操作,测试寄存器`b`的内容是否为零。测试也有来自其输入寄存器和常数的箭头,但是它没有输出箭头;它的值由控制器使用,而不是由数据路径使用。总的来说,数据路径图显示了机器所需的寄存器和操作,以及它们必须如何连接。如果我们把箭头看作电线,把按钮看作开关,那么数据路径图就很像由电子元件构成的机器的接线图。
为了让数据路径实际计算 gcd,必须按正确的顺序按下按钮。我们将根据控制器图来描述这个序列,如图 5.2:所示。控制器图的元素指示数据路径组件应该如何操作。控制器图中的矩形框标识要按下的数据路径按钮,箭头描述从一个步骤到下一个步骤的顺序。图中的菱形代表一个决定。根据菱形中标识的数据路径测试值,将遵循两个排序箭头中的一个。我们可以从物理类比的角度来解释控制器:把这个图想象成一个弹子在其中滚动的迷宫。当弹球滚进一个盒子时,它会按下由盒子命名的数据路径按钮。当弹球滚入一个决策节点时(比如对`b` = 0 的测试),它会离开由指示的测试结果所确定的路径上的节点。
为了让数据路径实际计算 gcd,必须按正确的顺序按下按钮。我们将根据控制器图来描述这个序列,如图 5.2:所示。控制器图的元素指示数据路径组件应该如何操作。控制器图中的矩形框标识要按下的数据路径按钮,箭头描述从一个步骤到下一个步骤的顺序。图中的菱形代表一个决定。根据菱形中标识的数据路径测试值,将遵循两个排序箭头中的一个。我们可以从物理类比的角度来解释控制器:把这个图想象成一个弹子在其中滚动的迷宫。当弹球滚进一个盒子时,它会按下由盒子命名的数据路径按钮。当弹球滚入一个决策节点时(比如对`b` = 0 的测试),它会离开由指示的测试结果所确定的路径上的节点。
![c5-fig-0002.jpg](img/c5-fig-0002.jpg)
......@@ -148,7 +148,7 @@ function remainder(n, d) {
assign("t", list(op("rem"), reg("a"), reg("b")))
```
在 GCD 中,控制器定义被包含一个循环的指令序列所取代,如图 5.6:所示。
在 GCD 中,控制器定义被包含一个循环的指令序列所取代,如图 5.6:所示。
![c5-fig-0005.jpg](img/c5-fig-0005.jpg)
......@@ -195,7 +195,7 @@ function sqrt(x) {
图 5.8:使用相同数据路径组件进行两种不同 GCD 计算的机器的控制器序列部分。
我们已经移除了重复的数据路径组件(因此数据路径再次如图 5.1:所示),但是控制器现在有两个 GCD 序列,它们的不同之处仅在于入口点标签。最好是将这两个序列替换成一个单独的序列——一个`gcd` 子程序——在这个序列的末尾,我们分支回到主指令序列中的正确位置。我们可以这样完成:在转移到`gcd`之前,我们将一个区别值(比如 0 或 1)放入一个特殊的寄存器`continue`。在`gcd`子程序结束时,我们返回到`after_gcd_1``after_gcd_2`,这取决于`continue`寄存器的值。图 5.9:显示了生成的控制器序列的相关部分,其中仅包含一份`gcd`指令。
我们已经移除了重复的数据路径组件(因此数据路径再次如图 5.1:所示),但是控制器现在有两个 GCD 序列,它们的不同之处仅在于入口点标签。最好是将这两个序列替换成一个单独的序列——一个`gcd` 子程序——在这个序列的末尾,我们分支回到主指令序列中的正确位置。我们可以这样完成:在转移到`gcd`之前,我们将一个区别值(比如 0 或 1)放入一个特殊的寄存器`continue`。在`gcd`子程序结束时,我们返回到`after_gcd_1``after_gcd_2`,这取决于`continue`寄存器的值。图 5.9:显示了生成的控制器序列的相关部分,其中仅包含一份`gcd`指令。
![c5-fig-0009.jpg](img/c5-fig-0009.jpg)
......@@ -500,7 +500,7 @@ function push(stack, value) {
##### 基本机器
`make_new_machine`函数,如图 5.13:所示,构造了一个对象,其本地状态由一个堆栈、一个最初为空的指令序列、一个最初包含一个初始化堆栈操作的操作列表以及一个最初包含两个名为`flag``pc`(代表“程序计数器”)的注册表。内部函数`allocate_register`向注册表添加新条目,内部函数`lookup_register`在表中查找寄存器。
`make_new_machine`函数,如图 5.13:所示,构造了一个对象,其本地状态由一个堆栈、一个最初为空的指令序列、一个最初包含一个初始化堆栈操作的操作列表以及一个最初包含两个名为`flag``pc`(代表“程序计数器”)的注册表。内部函数`allocate_register`向注册表添加新条目,内部函数`lookup_register`在表中查找寄存器。
![c5-fig-0013.jpg](img/c5-fig-0013.jpg)
......@@ -1218,7 +1218,7 @@ assign("the_stack", constant(null))
##### 练习 5.19
画出由产生的列表结构的盒指针表示法和内存向量表示法(如图 5.14:
画出由产生的列表结构的盒指针表示法和内存向量表示法(如图 5.14:
```js
const x = pair(1, 2);
......@@ -3014,7 +3014,7 @@ function factorial(n) {
##### 练习 5.38
编译了什么程序来产生如图 5.18:所示的代码?
编译了什么程序来产生如图 5.18:所示的代码?
![c5-fig-0018a.jpg](img/c5-fig-0018a.jpg)
![c5-fig-0018b.jpg](img/c5-fig-0018b.jpg)
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册