提交 7d918044 编写于 作者: W wizardforcel

2023-03-30 17:13:08

上级 3fac9e3a
# 《计算机程序的结构与解释》序言,1996 年& 1984 年
# 《计算机程序的结构与解释》序言,1996 年 & 1984 年
## 《SICP》1996 年第二版序言
......
......@@ -4,7 +4,7 @@
《计算机程序的结构和解释 (SICP JS)的 JavaScript 改编是在新加坡国立大学(NUS)为课程 CS1101S 开发的。该课程由 Low Kok Lim 共同教授了六年,他良好的教学判断对该课程和该项目的成功至关重要。CS1101S 教学团队包括许多新加坡国立大学的同事和 300 多名本科生助教。他们在过去九年里不断的反馈让我们解决了无数特定于 JavaScript 的问题,消除了不必要的复杂性,同时保留了 SICP 和 JavaScript 的基本特性。
SICP JS 是一个软件项目,也是一个图书项目。我们在 2008 年从原作者处获得了![f6-fig-5001.jpg](img/f6-fig-5001.jpg)图书来源。早期的 SICP JS 工具链是由刘航开发,冯飘飘完善的。陈·何岸开发子印刷版本的第一批工具,乔林·谭在此基础上开发子第一版电子书的工具,心悦和王千将这些工具重新用于当前的比较版。Samuel Fang 设计并开发了 SICP JS 的网络版。
SICP JS 是一个软件项目,也是一个图书项目。我们在 2008 年从原作者处获得了 LaTeX 图书来源。早期的 SICP JS 工具链是由刘航开发,冯飘飘完善的。陈·何岸开发子印刷版本的第一批工具,乔林·谭在此基础上开发子第一版电子书的工具,心悦和王千将这些工具重新用于当前的比较版。Samuel Fang 设计并开发了 SICP JS 的网络版。
SICP JS 和 CS1101S 的网络版很大程度上依赖于一个叫做源码学院的软件系统,它支持的 JavaScript 子语言叫做源码。在 SICP JS 的准备过程中,许多学生对源学院做出了贡献,系统将他们突出地列为“贡献者”自 2020 年以来,新加坡国立大学课程 CS4215“编程语言实现”的学生贡献了几个在 SICP JS 中使用的编程语言实现:3.4 节中使用的源代码的并发版本由郑群·库和乔纳森·陈开发;第 4.2 节中使用的惰性实现是由杰卢利·艾哈迈德、伊恩·肯德尔·邓肯、克鲁兹·乔马里·埃万格利斯塔和奥尔登·谭开发的;第 4.3 节中使用的不确定性实现是由阿尔萨兰·吉玛和阿努巴夫开发的;Daryl Tan 帮助将这些实现整合到学院中。
......@@ -44,7 +44,7 @@ Al Moyé安排我们将这些材料教授给惠普公司的工程师,并制作
丹·弗里德曼(Dan Friedman)是该计划社区的长期领导者。该社区更广泛的工作超越了语言设计问题,包括重大的教育创新,如基于 Schemer's Inc .的 EdScheme 的高中课程,以及 Mike Eisenberg、Brian Harvey 和 Matthew Wright 的精彩书籍。
我们感谢那些为使这本书成为一本真正的书做出贡献的人,特别是特里·艾林、拉里·科恩和麻省理工学院出版社的保罗·贝奇。艾拉·马泽尔找到了精彩的封面图片。对于第二版,我们特别感谢伯纳德和艾拉·马泽尔对书籍设计的帮助,以及非凡的巫师大卫·琼斯![f6-fig-5002.jpg](img/f6-fig-5002.jpg)。我们也非常感谢那些对新草稿发表深刻评论的读者:雅各布·卡泽尼尔森、哈迪·梅尔、吉姆·米勒,特别是布莱恩·哈维,他对这本书的贡献就像朱莉对他的书《简单计划》的贡献一样。
我们感谢那些为使这本书成为一本真正的书做出贡献的人,特别是特里·艾林、拉里·科恩和麻省理工学院出版社的保罗·贝奇。艾拉·马泽尔找到了精彩的封面图片。对于第二版,我们特别感谢伯纳德和艾拉·马泽尔对书籍设计的帮助,以及非凡的巫师大卫·琼斯 TeX。我们也非常感谢那些对新草稿发表深刻评论的读者:雅各布·卡泽尼尔森、哈迪·梅尔、吉姆·米勒,特别是布莱恩·哈维,他对这本书的贡献就像朱莉对他的书《简单计划》的贡献一样。
最后,我们要感谢多年来鼓励这项工作的各组织的支持,包括由 Ira Goldstein 和 Joel Birnbaum 促成的惠普公司的支持,以及由鲍勃·卡恩促成的 DARPA 的支持。
......
......@@ -473,7 +473,7 @@ p[1]
x > 5 && x < 10
```
语法形式`&&`的优先级低于比较运算符`>``<`,条件表达式语法形式`· · · ?· · · :· · ·`的优先级低于迄今为止我们遇到的任何其他运算符,这是我们在上面的`abs`函数中使用的一个属性。
语法形式`&&`的优先级低于比较运算符`>``<`,条件表达式语法形式`... ?... :...`的优先级低于迄今为止我们遇到的任何其他运算符,这是我们在上面的`abs`函数中使用的一个属性。
作为另一个例子,我们可以声明一个谓词来测试一个数字是否大于或等于另一个数字
......@@ -846,13 +846,13 @@ function sqrt(x) {
我们首先考虑阶乘函数,定义如下
```js
n! = n · (n 1) · (n 2) · · · 3 · 2 · 1
n! = n · (n 1) · (n 2) ... 3 · 2 · 1
```
有许多方法可以计算阶乘。一种方法是利用观察到的 n !等于 n 倍(n–1)!对于任意正整数 n :
```js
n! = n · [(n 1) · (n 2) · · · 3 · 2 · 1] = n · (n 1)!
n! = n · [(n 1) · (n 2) ... 3 · 2 · 1] = n · (n 1)!
```
这样,我们就可以计算出 n !通过计算(n–1)!并将结果乘以 n 。如果我们加上规定 1!等于 1,这个观察结果直接转化为计算机函数:
......
......@@ -443,7 +443,7 @@ Lem 抱怨说,Alyssa 的程序对这两种计算方式给出了不同的答案
### 2.2.1 表示顺序
我们可以用偶对构建的一个有用的结构是一个序列——数据对象的有序集合。当然,有许多方法可以用对来表示序列。图 2.4 给出了一个特别直观的表示,其中序列 1,2,3,4 被表示为一串对。每对的`head`是链中对应的物品,该对的`tail`是链中的下一对。最后一对的`tail`表示序列的结束,在盒指针图中表示为对角线,在程序中表示为 JavaScript 的原始值`**null**`。整个序列由嵌套的`pair`操作构成:
我们可以用偶对构建的一个有用的结构是一个序列——数据对象的有序集合。当然,有许多方法可以用对来表示序列。图 2.4 给出了一个特别直观的表示,其中序列 1,2,3,4 被表示为一串对。每对的`head`是链中对应的物品,该对的`tail`是链中的下一对。最后一对的`tail`表示序列的结束,在盒指针图中表示为对角线,在程序中表示为 JavaScript 的原始值`null`。整个序列由嵌套的`pair`操作构成:
![c2-fig-0004.jpg](img/c2-fig-0004.jpg)
......@@ -468,7 +468,7 @@ list(a[1], a[2], . . ., a[n])
pair(a[1], pair(a[2], pair(. . ., pair(a[n], null). . .)))
```
我们的解释器使用我们称之为盒符号的盒指针图的文本表示来打印对。`pair(1, 2)`的结果打印为`[1, 2]`,图 2.4:中的数据对象打印为`[1, [2, [3, [4, **null**]]]]`:
我们的解释器使用我们称之为盒符号的盒指针图的文本表示来打印对。`pair(1, 2)`的结果打印为`[1, 2]`,图 2.4:中的数据对象打印为`[1, [2, [3, [4, null]]]]`:
```js
const one_through_four = list(1, 2, 3, 4);
......@@ -496,7 +496,7 @@ pair(5, one_through_four);
[5, [1, [2, [3, [4, null]]]]]
```
用于终止线对链的值`**null**`可以被认为是一个没有元素的序列,即空列表。 [](#c2-fn-0009)
用于终止线对链的值`null`可以被认为是一个没有元素的序列,即空列表。 [](#c2-fn-0009)
方框符号有时很难读懂。在本书中,当我们想要指出一个数据结构的列表性质时,我们将使用替代的列表符号:只要有可能,列表符号使用`list`的应用程序,它的评估将产生期望的结构。例如,代替方框符号
......@@ -794,13 +794,13 @@ for_each(x => display(x), list(57, 321, 88));
### 2.2.2 层次结构
用列表来表示序列自然地推广到表示其元素本身可能是序列的序列。例如,我们可以把对象`[[1, [2, **null**]], [3, [4, **null**]]]`看作由
用列表来表示序列自然地推广到表示其元素本身可能是序列的序列。例如,我们可以把对象`[[1, [2, null]], [3, [4, null]]]`看作由
```js
pair(list(1, 2), list(3, 4));
```
作为三个条目的列表,第一个条目本身就是一个列表,`[1, [2, **null**]]`。图 2.5:显示了这种结构的线对表示。
作为三个条目的列表,第一个条目本身就是一个列表,`[1, [2, null]]`。图 2.5:显示了这种结构的线对表示。
![c2-fig-0005.jpg](img/c2-fig-0005.jpg)
......@@ -846,7 +846,7 @@ count_leaves(list(x, x));
* 一棵树`x``count_leaves``x``head``count_leaves`加上`x``tail``count_leaves`
最后,通过取`head` s,我们得到实际的叶子,所以我们需要另一个基本情况:
最后,通过取`head`,我们得到实际的叶子,所以我们需要另一个基本情况:
* `count_leaves`一片叶子是 1。
......@@ -1266,16 +1266,16 @@ function length(sequence) {
在给定的值 x 下,对 x 中的多项式求值可以表示为累加。我们评估多项式
```js
anxn + an1xn1 + · · · + a1x + a0
a[n]·x^n + a[n1]·x^(n1) + ... + a[1]·x + a[0]
```
使用众所周知的算法霍纳法则,其计算结构如下
```js
(· · · (anx + an1)x + · · · + a1) x + a0
(... (a[n]·x + a[n1]) x + ... + a[1]) x + a[0]
```
换句话说,我们从 a[n]开始,乘以 x ,加上 a[n][–1],乘以 x ,以此类推,直到我们到达 a [0] 。 [^(14)](#c2-fn-0014) 填写以下模板,生成一个使用霍纳法则计算多项式的函数。假设多项式的系数是按顺序排列的,从 a0 到 a[n]
换句话说,我们从`a[n]`开始,乘以`x`,加上`a[n–1]`,乘以`x`,以此类推,直到我们到达`a[0]`[^(14)](#c2-fn-0014) 填写以下模板,生成一个使用霍纳法则计算多项式的函数。假设多项式的系数是按顺序排列的,从`a[0]``a[n]`
```js
function horner_eval(x, coefficient_sequence) {
......@@ -1475,7 +1475,7 @@ function permutations(s) {
}
```
注意这个策略是如何将生成 S 的排列的问题简化为生成比 S 更少元素的集合的排列的问题。在最后一种情况下,我们一直工作到空列表,它表示一组没有元素的集合。为此,我们生成`list(**null**)`,它是一个只有一项的序列,即没有元素的集合。`permutations`中使用的`remove`函数返回给定序列中除给定项目之外的所有项目。这可以表示为一个简单的过滤器:
注意这个策略是如何将生成 S 的排列的问题简化为生成比 S 更少元素的集合的排列的问题。在最后一种情况下,我们一直工作到空列表,它表示一组没有元素的集合。为此,我们生成`list(null)`,它是一个只有一项的序列,即没有元素的集合。`permutations`中使用的`remove`函数返回给定序列中除给定项目之外的所有项目。这可以表示为一个简单的过滤器:
```js
function remove(item, sequence) {
......@@ -1500,7 +1500,7 @@ function remove(item, sequence) {
图 2.8:八皇后谜题的一种解法。
我们将该解决方案实现为函数`queens`,该函数返回将 n 个皇后放置在 n n 棋盘上的问题的所有解决方案的序列。函数`queens`有一个内部函数`queens_cols`,它返回将皇后放入棋盘前 k 列的所有方法的顺序。
我们将该解决方案实现为函数`queens`,该函数返回将`n`个皇后放置在`n x n`棋盘上的问题的所有解决方案的序列。函数`queens`有一个内部函数`queens_cols`,它返回将皇后放入棋盘前 k 列的所有方法的顺序。
```js
function queens(board_size) {
......@@ -1932,7 +1932,7 @@ list("a", b);
["a", [2, null]]
```
在 1.1.6 节中,我们引入了`===``!==`作为数字的基本谓词。从现在开始,我们将允许两个字符串作为`===``!==`的操作数。当且仅当两个字符串相同时,谓词`===`返回 true,当且仅当两个字符串不相同时,`!==`返回 true。 [^(31)](#c2-fn-0031) 使用`===`,我们可以实现一个叫做`member`的有用函数。这需要两个参数:一个字符串和一个字符串列表,或者一个数字和一个数字列表。如果第一个参数不包含在列表中(即,不是列表中任何项目的`===`,则`member`返回`**null**`。否则,它返回列表的子列表,从第一次出现的字符串或数字开始:
在 1.1.6 节中,我们引入了`===``!==`作为数字的基本谓词。从现在开始,我们将允许两个字符串作为`===``!==`的操作数。当且仅当两个字符串相同时,谓词`===`返回 true,当且仅当两个字符串不相同时,`!==`返回 true。 [^(31)](#c2-fn-0031) 使用`===`,我们可以实现一个叫做`member`的有用函数。这需要两个参数:一个字符串和一个字符串列表,或者一个数字和一个数字列表。如果第一个参数不包含在列表中(即,不是列表中任何项目的`===`,则`member`返回`null`。否则,它返回列表的子列表,从第一次出现的字符串或数字开始:
```js
function member(item, x) {
......@@ -1950,7 +1950,7 @@ function member(item, x) {
member("apple", list("pear", "banana", "prune"))
```
`**null**`,而的值
`null`,而的值
```js
member("apple", list("x", "y", "apple", "pear"))
......@@ -3950,11 +3950,11 @@ GCD 计算是任何对有理函数进行运算的系统的核心。上面使用
[8](#c2-fn-0008a) 在本书中,我们使用列表来表示由列表结束标记终止的一串对。相反,术语列表结构指的是任何成对的数据结构,而不仅仅是列表。
`**null**`在 JavaScript 中有多种用途,但在本书中我们将只使用它来表示空列表。
`null`在 JavaScript 中有多种用途,但在本书中我们将只使用它来表示空列表。
我们的 JavaScript 环境提供了一个原语函数`display_list`,它的工作方式类似于原语函数`display`,除了它使用列表符号而不是盒子符号。
这两个谓词的顺序很重要,因为`**null**`满足`is_null`并且也不是一对。
这两个谓词的顺序很重要,因为`null`满足`is_null`并且也不是一对。
[12](#c2-fn-0012a) 事实上,这正是练习 2.28 中的`fringe`函数。这里我们将其重命名,以强调它是通用序列操作函数家族的一部分。
......@@ -4017,7 +4017,7 @@ identity,flip _ vert);
* 对于任何集合`S`和任何对象`x``is_element_of_set(x, adjoin_set(x, S))`为真(非正式地:“将一个对象邻接到一个集合产生一个包含该对象的集合”)。
* 对于任意集合`S``T`和任意对象`x``is_element_of_set(x, union_set(S, T))`等于`is_element_of_set(x, S) || is_element_of_set(x, T)`(非正式:`union_set(S, T)`的元素是在`S``T`中的元素)。
* 对于任何对象`x``is_element_of_set(x, **null**)`为假(非正式地:“没有对象是空集的元素”)。
* 对于任何对象`x``is_element_of_set(x, null)`为假(非正式地:“没有对象是空集的元素”)。
正如我们在 1.2.4 节的快速指数算法和 1.3.3 节的半区间搜索方法中所看到的,在每一步将问题的规模减半是对数增长的显著特征。
......
......@@ -3095,7 +3095,7 @@ head(tail(filter(is_prime,
流是一个聪明的想法,它允许使用序列操作,而不会产生将序列作为列表操作的成本。有了流,我们可以两全其美:我们可以优雅地将程序公式化为序列操作,同时获得增量计算的效率。基本思想是安排只部分地构造一个流,并将部分构造传递给使用该流的程序。如果消费者试图访问尚未构造的流的一部分,则该流将自动构造足够多的自身来产生所需的部分,从而保持整个流存在的假象。换句话说,虽然我们将编写程序,就像我们正在处理完整的序列一样,但是我们设计我们的流实现来自动和透明地交织流的构造和它的使用。
为了实现这一点,我们将使用对来构造流,流的第一项位于对的头部。然而,我们不会将流的其余部分的值放在该对的尾部,而是会在那里放置一个“承诺”,以便在需要时计算其余部分。如果我们有一个数据项`h`和一个流`t`,我们通过对`pair(h, () => t)`求值来构造一个头为`h`尾为`t`的流——一个流的尾`t`被“包装”在一个没有自变量的函数中,这样它的求值就会被延迟。空流是`**null**`,和空列表一样。
为了实现这一点,我们将使用对来构造流,流的第一项位于对的头部。然而,我们不会将流的其余部分的值放在该对的尾部,而是会在那里放置一个“承诺”,以便在需要时计算其余部分。如果我们有一个数据项`h`和一个流`t`,我们通过对`pair(h, () => t)`求值来构造一个头为`h`尾为`t`的流——一个流的尾`t`被“包装”在一个没有自变量的函数中,这样它的求值就会被延迟。空流是`null`,和空列表一样。
要访问非空流的第一个数据项,我们只需选择对中的`head`,就像使用列表一样。但是要访问流的尾部,我们需要计算延迟的表达式。为了方便起见,我们定义
......@@ -3105,7 +3105,7 @@ function stream_tail(stream) {
}
```
这将选择该对的尾部,并应用在那里找到的函数来获得流的下一对(或者如果流的尾部为空,则使用`**null**`)——实际上,迫使该对尾部的函数履行其承诺。
这将选择该对的尾部,并应用在那里找到的函数来获得流的下一对(或者如果流的尾部为空,则使用`null`)——实际上,迫使该对尾部的函数履行其承诺。
我们可以创建和使用流,就像我们可以创建和使用列表一样,来表示按顺序排列的聚合数据。特别是,我们可以从第 2 章开始构建列表操作的流模拟,例如`list_ref``map``for_each` : [^(58)](#c3-fn-0058)
......@@ -3543,10 +3543,10 @@ function expand(num, den, radix) {
表示为无限的流。我们将序列 a[0]+a[1]x+a+[2]x²+a[3]x³+表示为其元素为系数的流。。。
1. a. The integral of the series a[0] + a[1]x + a[2]x² + a[3]x³ +· · ·is the series
1. a. The integral of the series a[0] + a[1]x + a[2]x² + a[3]x³ +...is the series
```js
c + a0x + a1x2 + a2x3 + a3x4 + · · ·
c + a0x + a1x2 + a2x3 + a3x4 + ...
```
其中 c 是任意常数。定义一个函数`integrate_series`,它将流 a [0] , a [1] , a [2] ,作为输入。。。代表一个幂级数和返回流一个一个 [0] ,![c3-fig-5004.jpg](img/c3-fig-5004.jpg)一个, [1] ,![c3-fig-5005.jpg](img/c3-fig-5005.jpg),一个, [2] ,。。。级数的积分的非常数项的系数。(由于结果没有常数项,所以不代表一个幂级数;当我们使用`integrate_series`时,我们将使用`pair`将适当的常量连接到流的开头。)
......
......@@ -451,7 +451,7 @@ function make_lambda_expression(parameters, body) {
sequence 语句将一系列语句打包成一条语句。语句序列解析如下:
```js
statement[1] · · · statement[n] =
statement[1] ... statement[n] =
list("sequence", list( statement[1] , . . ., statement[n] ))
```
......@@ -608,7 +608,7 @@ function operator_combination_to_application(component) {
`parse`的逆称为`unparse`。它将一个由`parse`生成的标记列表作为参数,并返回一个遵循 JavaScript 符号的字符串。
1. a. 按照`evaluate`的结构(没有环境参数)写一个函数`unparse`,但是产生一个表示给定组件的字符串,而不是对其求值。回想一下第 3.3.4 节,操作符`+`可以应用于两个字符串来连接它们,并且原语函数`stringify`将值 1.5、true、`**null**``undefined`转换为字符串。注意尊重操作符的优先顺序,用圆括号(总是或在任何必要的时候)将解对操作符组合产生的字符串括起来。
1. a. 按照`evaluate`的结构(没有环境参数)写一个函数`unparse`,但是产生一个表示给定组件的字符串,而不是对其求值。回想一下第 3.3.4 节,操作符`+`可以应用于两个字符串来连接它们,并且原语函数`stringify`将值 1.5、true、`null``undefined`转换为字符串。注意尊重操作符的优先顺序,用圆括号(总是或在任何必要的时候)将解对操作符组合产生的字符串括起来。
2. b. 你的`unparse`函数在解决本节后面的练习时会派上用场。通过向结果字符串添加`" "`(空格)和`"\n"`(换行符)来改进`unparse`,以遵循本书 JavaScript 程序中使用的缩进样式。在程序文本中添加(或删除)这样的空白字符以使文本更容易阅读被称为美化。
##### 练习 4.3
......@@ -1037,7 +1037,7 @@ function user_read(prompt_string) {
}
```
当用户取消输入时,函数`prompt`返回`**null**`。我们使用一个特殊的打印函数`user_print`,以避免打印复合函数的环境部分,这可能是一个很长的列表(甚至可能包含循环)。
当用户取消输入时,函数`prompt`返回`null`。我们使用一个特殊的打印函数`user_print`,以避免打印复合函数的环境部分,这可能是一个很长的列表(甚至可能包含循环)。
```js
function user_print(string, object) {
......@@ -1521,7 +1521,7 @@ function try_me(a, b) {
}
```
评估`try_me(0, head(**null**));`表示 JavaScript 中有错误。有了懒求值,就不会有错误了。评估该语句将返回 1,因为参数`head(**null**)`永远不会被评估。
评估`try_me(0, head(null));`表示 JavaScript 中有错误。有了懒求值,就不会有错误了。评估该语句将返回 1,因为参数`head(null)`永远不会被评估。
利用懒惰求值的一个例子是函数的声明`unless`
......@@ -3317,7 +3317,7 @@ list("computer", "programmer")
list("computer")
```
`$type`为空单,`**null**`
`$type`为空单,`null`
我们可以将查询语言对简单查询的处理描述如下:
......@@ -4193,7 +4193,7 @@ function find_assertions(pattern, frame) {
}
```
函数`check_an_assertion`将数据对象(断言)、模式和帧作为参数,如果匹配失败,则返回包含扩展帧的单元素流或`**null**`
函数`check_an_assertion`将数据对象(断言)、模式和帧作为参数,如果匹配失败,则返回包含扩展帧的单元素流或`null`
```js
function check_an_assertion(assertion, query_pat, query_frame) {
......@@ -4749,7 +4749,7 @@ job($x, list("computer", "wizard"))
'job(pair("Bitdiddle ",pair("Ben ",null)),list("computer "," wizard ")'
它显式地构造了构成第一个列表的两对。为了实现 4.4.1 节中使用的更简洁的格式,我们插入了子句来检查表达式是否构造了一个列表,在这种情况下,我们将其格式化为从表达式中提取的元素表达式列表的一个单独的应用程序`list`。列表结构是字面上的`**null**`或者是`pair`的应用,它的第二个参数本身就是列表结构。
它显式地构造了构成第一个列表的两对。为了实现 4.4.1 节中使用的更简洁的格式,我们插入了子句来检查表达式是否构造了一个列表,在这种情况下,我们将其格式化为从表达式中提取的元素表达式列表的一个单独的应用程序`list`。列表结构是字面上的`null`或者是`pair`的应用,它的第二个参数本身就是列表结构。
```js
function is_list_construction(exp) {
......@@ -4760,7 +4760,7 @@ function is_list_construction(exp) {
}
```
从给定的列表构造中提取元素表达式相当于收集应用程序的第一个参数`pair`,直到到达文字`**null**`
从给定的列表构造中提取元素表达式相当于收集应用程序的第一个参数`pair`,直到到达文字`null`
```js
function element_expressions(list_constr) {
......@@ -4993,7 +4993,7 @@ sum_of_squares(3, 4);
我们选择使用高阶函数`map`来实现`list_of_values`,我们也将在其他地方使用`map`。然而,可以在不使用任何高阶函数的情况下实现求值器(因此可以用没有高阶函数的语言编写),即使它支持的语言将包括高阶函数。例如,`list_of_values`可以不用`map`写成如下:
**函数** list_of_values(exps,env){
**return**is _ null(exps)
**null**
null
:pair(evaluate(head(exps),env),
list _ of _ values(tail(exps),env));T33)}
......@@ -5070,7 +5070,7 @@ Snarf:“获取,尤其是大文件或档案,目的是在得到或没有得
[33](#c4-fn-0033a) 与记忆化相结合的惰性求值有时被称为按需调用参数传递,与按名称调用参数传递相反。(Algol 60 引入的点名,类似于非记忆化的懒求值。)作为语言设计者,我们可以构建我们的评估器来记忆,而不是记忆,或者留给程序员一个选项(练习 4.29)。正如你在第三章中所预期的,这些选择提出了一些问题,在有作业的情况下,这些问题变得既微妙又令人困惑。(参见练习 4.25 和 4.27。)Clinger (1982)的一篇优秀文章试图澄清这里出现的多个维度的困惑。
[34](#c4-fn-0034a) 注意,一旦计算出表达式的值,我们也从 thunk 中删除了`env`。这对解释器返回的值没有影响。然而,它确实有助于节省空间,因为一旦不再需要,就从 thunk 中移除对`env`的引用,这允许对该结构进行垃圾收集并回收其空间,正如我们将在 5.3 节中讨论的。
类似地,我们可以允许对第 3.5.1 节的记忆延迟对象中不需要的环境进行垃圾收集,通过让`memo`做类似于`fun = **null**;`的事情来丢弃函数`fun`(包括在其中对构成流尾的 lambda 表达式进行求值的环境)的值。
类似地,我们可以允许对第 3.5.1 节的记忆延迟对象中不需要的环境进行垃圾收集,通过让`memo`做类似于`fun = null;`的事情来丢弃函数`fun`(包括在其中对构成流尾的 lambda 表达式进行求值的环境)的值。
[35](#c4-fn-0035a) 这个练习展示了懒惰评估和副作用之间的相互作用可能非常令人困惑。这正是你可能从第三章的讨论中所期待的。
......@@ -5159,10 +5159,10 @@ Snarf:“获取,尤其是大文件或档案,目的是在得到或没有得
在克拉克(1978)的文章“否定即失败”中可以找到对这种处理方法的讨论和论证。
一般来说,用一个包含`$y`的表达式来统一`$y`需要我们能够找到一个包含 `$y``$y` = 表达式的固定点。有时有可能从语法上形成一个似乎是解决方案的表达式。比如`$y` = `list("f", $y)`好像有定点`list("f", list("f", list("f",`。。。`)))`,我们可以通过从表达式`list("f", $y)`开始并反复用`list("f", $y)`替换`$y`来生成。不幸的是,并不是每个这样的方程都有一个有意义的固定点。这里出现的问题类似于数学中处理无穷级数的问题。例如,我们知道 2 是方程 y = 1 + y /2 的解。从表达式 1 + y /2 开始,反复用 1 + y /2 替换 y 得出
2 =y= 1+y/2 = 1+(1+y/2)/2 = 1+1/2+y/4 =`· · ·`
2 =y= 1+y/2 = 1+(1+y/2)/2 = 1+1/2+y/4 =`...`
从而得出【T38
然而,如果我们从观察到–1 是等式 y = 1 + 2 y 的解开始尝试相同的操作,我们得到
–1 =y= 1+2y= 1+2(1+2y)= 1+2+4y=`· · ·`,这导致
–1 =y= 1+2y= 1+2(1+2y)= 1+2+4y=`...`,这导致
–1 虽然在推导这两个方程中使用的形式操作是相同的,但是第一个结果是关于无穷级数的有效断言,而第二个不是。类似地,对于我们的统一结果,用任意语法构造的表达式进行推理可能会导致错误。然而,今天大多数逻辑编程系统通过接受循环数据结构作为匹配的结果,允许循环引用。这在理论上是合理的,使用理性树(Jaffar 和 Stuckey 1986)。接受循环数据结构允许自引用数据,例如引用雇主的雇员数据结构,反过来又引用雇员。
[75](#c4-fn-0075a) 在 JavaScript 中,获取包含字符串第一个字符`s`的字符串的实际方法是`s.charAt(0)`
......
......@@ -337,7 +337,7 @@ save(register-name)
restore(register-name)
```
到目前为止我们看到的唯一一种常量 - 值是一个数字,不过后面我们也会用到字符串和列表。比如,`constant("abc")`是字符串`"abc"``constant(**null**)`是空列表,`constant(list("a", "b", "c"))`是列表`list("a", "b", "c")`
到目前为止我们看到的唯一一种常量 - 值是一个数字,不过后面我们也会用到字符串和列表。比如,`constant("abc")`是字符串`"abc"``constant(null)`是空列表,`constant(list("a", "b", "c"))`是列表`list("a", "b", "c")`
## 5.2 一个收银机模拟器
......@@ -3353,7 +3353,7 @@ EC 评估值:
[5](#c5-fn-0005a) 在这里使用`receive`函数是一种让`extract_labels`有效返回两个值——`labels``insts`——而不用显式地创建一个复合数据结构来保存它们的方法。返回显式值对的另一个实现是
**函数**extract _ labels(controller){
**if**(is _ null(controller)){
**返回**对( **null****null**);
**返回**对( null ,null);
}**else**{
**const**result = extract _ labels(tail(控制器));
**const**insts = head(结果);
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册