目前的排版不论从效果还是从实现来看都相当丑陋, 但也只能将就一下了. 或许只有等到博客进行大型实质性重构时, 我才会考虑这些问题.
和其他小人书一样, 本书也包含大量文字游戏, 这有点难以传达? 若有必要, 我会添加一些译注.
欢迎回来! | 1 | 很高兴回到这里! |
让我们为一门叫做Pie的新语言擦拭并更新一些我们的旧玩具. 以下东西是一个 Atom , 这显然吗?
| 2 | 一点也不, Atom 是什么意思? |
要成为一个Atom , 就是要成为一个原子(atom).注记. 在Lisp中, 原子可以是符号, 数字, 以及其他许多东西. 但在这里, 原子只是符号. | 3 | 那么, 'atom 是一个Atom 是因为'atom 是一个原子. |
以下东西是一个Atom , 这显然吗?
译注. ratatouille指的是普罗旺斯杂烩. | 4 | 是的, 因为'ratatouille 也是一个原子.但是, 精确地说, 什么是一个原子呢? |
原子是由一个单引号 (tick mark) 后跟着一个或更多的字母或者连字符而来的. 注记. 在Pie中, 只有原子使用单引号. | 5 | 因此, 那么 显然是一个Atom 吗? |
当然了, 因为原子可以包含连字符. 那么 和 和 如何, 它们是原子吗? | 6 | 是一个原子, 因为连字符可以出现在一个原子里的任何位置; 不是一个原子, 因为它缺少了单引号; 不是一个原子, 因为它的后面既没有跟着一个字母, 也没有跟着一个连字符. |
'Atom 是一个Atom 吗? | 7 | 是的, 即便'Atom 也是一个Atom , 因为它是一个单引号后面跟着一个或更多的字母或者连字符. |
'at0m 是一个Atom 吗? | 8 | 不是, 因为根据第1章第5框, 原子只能包含字母或者连字符, 而字符0 并不是一个字母, 它是数位零. |
'cœurs-d-artichauts 是一个Atom 吗?译注. cœurs d'artichauts是一种食物, 即洋蓟心. | 9 | 是的, 因为œ 是一个字母. |
'ἄτομον 是一个Atom 吗?译注. ἄτομον是一个古希腊语词, 意思是不可分割的东西, 即原子. | 10 | That's Greek to me! 译注. 这算是一语双关. 但是希腊字母也是字母, 所以它必然是一个Atom . |
|
诸如
注记. 感谢Per Martin-Löf (1942–). | 11 | 判断的要义在于什么? |
一个判断是一个人对于一个表达所采取的一个态度. 当我们得以获知什么东西时, 我们就在作出一个判断. 关于 Atom 和'courgette , 可以判断什么? | 12 | 'courgette 是一个Atom .译注. courgette指的是西葫芦. |
一个判断形式(form of judgment)是一个带有空白的观察, 例如 ____是一个____. | 13 | 还有其他的判断形式吗? |
判断 (judgment)的另一种形式是 判断 (judgement). | 14 | 十分有趣. |
和 是相同的 吗? | 15 | 是的. 它们之所以是相同的 |
和 是相同的 吗? | 16 | 不是. 单引号之后, 它们有着不同的字母. |
|
第二种判断形式为 ____和____是相同的____. | 17 | 因而
译注. citron指的是香橼. |
这的确是一个判断, 而且我们有理由去相信.
| 18 | 诚然它是判断, 但是我们没有理由去相信它. 毕竟, 我们不该比较苹果 (apple) 和橙子. 译注. pomme是法语的苹果. |
是一个 这显然吗?注记. 当准备好的时候, 读[page 62]看 定型 (typing)指令. | 19 | 不, 完全不是. 成为一个 是什么意思呢?译注. baguette指的是法棍面包. |
成为一个 就是要成为一个序对, 其car 是一个Atom , 例如'ratatouille , 其cdr 也是一个Atom , 例如'baguette . | 20 | cons , car , cdr 看上去很眼熟. 不谈以前, 这里它们是什么意思呢? 它们和序对 (pair) 又有什么关系呢? |
一个序对以cons 起手, 而以另外两个部分作结, 我们称其为它的car 和cdr .注记. 在Lisp中, cons 可以使得列表更长. 但在这里, cons 只是用来构造序对. | 21 | 好吧, 这意味着之所以 是一个 是因为(cons 'ratatouille 'baguette) 是一个序对, 其car 是一个Atom , 其cdr 也是一个Atom .那么, |
甚至cons 和Pair 在单独情况下都不是表达式, 它们都需要两个参数. 和 是相同的 吗?注记. 在Lisp中, cons 是一个过程, 有着其自身的含义, 但是诸如cond 或lambda 这样的形式如果单独出现则是没有意义的. | 22 | 两个表达式为相同的 是什么意思呢? |
这意味着它们的car 都是相同的Atom , 它们的cdr 也都是相同的Atom . | 23 | 那么的确 和 是相同的
|
和 是相同的 吗? | 24 | 的car 是'ratatouille 而 的car 是'baguette .因此, 我们没有理由去相信它们是相同的 |
可以怎样描述呢? | 25 | 它是一个
|
描述其他表达式的表达式, 例如Atom , 被称为类型(type).
注记. 当一个名字 (例如 Pair 或者Atom ) 牵涉类型时, 首字母会大写. | 26 | 是的, 因为它描述了car 和cdr 均为Atom 的序对. |
第三种判断形式为 ____是一个类型. | 27 | 这意味着
|
|
| 28 | 它的确是一个判断, 但是我们没有任何理由去相信它, 因为'courgette 并不描述其他表达式. |
Atom 和Atom 是相同的类型吗? | 29 | 想必如此, 它们看上去就应该是相同的类型. |
第四种判断形式, 也是最后一种, 如下 ____和____是相同的类型. | 30 | 那么, 所以说
|
|
以下是一个判断吗?
| 31 | 它是一个判断, 但是没有理由去相信它. |
和 是相同的类型吗? | 32 | 看起来相当可信啊. |
判断是获知的行为, 而相信是获知的一部分. | 33 | 判断难道不是句子吗? |
句子从理解它们的人那里获得意义. 句子捕获了我们所拥有的思想, 而思想比我们用来表达思想的词语要重要得多. | 34 | 啊, 所以说得以获知 和 是相同的类型这一行为是一个判断. |
的确如此.
译注. pêche指的是桃子. | 35 | 好问题.
|
不, 当然不是, 因为
某些形式的判断只有在早前的判断的基础之上才具备意义. 注记. 这种早前的判断有时被称为前置假定(presupposition). | 36 | 有哪些呢? |
为了提问一个表达式是否由一个类型描述, 我们必须已经判断过给定的类型的确是一个类型. 为了提问两个表达式是否在一个类型下是相同的, 我们必须首先判断出这两个表达式都由该类型所描述. 注记. 当然, 为了描述那两个表达式, 那个给定的类型也得是类型. 在提问两个表达式是否是相同的类型之前, 有什么判断是必要的吗? | 37 | 为了提问两个表达式是否是相同的类型, 我们必须先要判断这两个表达式的确都是类型. |
和 是相同的 吗? | 38 | 看起来十分熟悉啊. 想必是这样, 因为car 可以找到一个序对的car , 所以它们是相同的. |
和 是相同的 吗? | 39 | 诚然如此, 因为这个序对的cdr 是'baguette . |
于是 是一个...译注. aubergine指的是茄子. | 40 | ... (Pair Atom Atom) , 因为 是一个序对, 其car 是Atom 'aubergine , 其cdr 是Atom 'courgette . |
和 是相同的 吗? | 41 | 是的, 的确如此. |
正如从第1章第39框到第1章第41框所展现的那样, 写法不同的表达式或许可能是相同的. 其中一种写法要比其他写法都更加直接. | 42 | 'baguette 的确看上去比 更加直接. |
一个表达式的规范形式(normal form)是书写该表达式最直接的方式. 任何两个相同的表达式都有着等同的规范形式, 并且任何两个有着等同规范形式的表达式都是相同的. | 43 | 'olive-oil 是 的规范形式吗? |
这个问题是不完整的. 相同总是相对于某个类型而言的, 因此规范形式也由类型决定. | 44 | 'olive-oil 是Atom 的规范形式吗? |
是的, 的确如此. 是一个规范的 吗?注记. 规范的(normal)是具有规范形式(in normal form)的简略说法. | 45 | 是的, (cons 'ratatouille 'baguette) 的确是规范的.每个表达式都具有一个规范形式吗? |
如果不刻画一个表达式的类型, 那么提问其是否具有规范形式也是没有意义的. 然而, 给定一个类型, 每个由该类型描述的表达式的确都有一个由该类型确定的规范形式. | 46 | 如果了两个表达式根据它们的类型是相同的, 那么它们就具有等同的规范形式. 因此, 这必然意味着我们可以通过比较两个表达式的规范形式来判断 (check) 它们是否相同. |
|
的规范形式是什么? | 47 | 类型是什么呢? 如果类型是 那么规范形式为
|
干得好! 之前我们对于什么是一个 的描述其实是不完整的, 而完整的描述应该是... | 48 | ...成为一个序对, 其car 是一个Atom , 其cdr 也是一个Atom , 或者是与这样一个序对相同的一个表达式. |
|
和 是相同的 吗? | 49 | 是的, 之所以这两个表达式是相同的(Pair Atom Atom) , 是因为 的规范形式是
|
为什么 和 是相同的(Pair Atom Atom) 呢? | 50 | 这看起来非常显然? |
是的, 但是不是每个看上去显然的东西实际上都是显然的. 第1章第23框描述了何谓一个表达式和另一个表达式是相同的
注记. 在Lisp中, 相同的原子使用两次 cons 产生的序对并不eq . 但在这里, 它们无法以任何方式进行区分. | 51 | 和 的顶层都是cons , 'aubergine 和'aubergine 是相同的Atom , 'courgette 和'courgette 是相同的Atom .这两个表达式具有相同的
|
|
很好. 的规范形式是什么呢? | 52 | 我猜是(Pair 'olive 'oil) , 是这样吗? |
实际上, 表达式 既不由某个类型刻画, 本身也不是一个类型, 因此提问其规范形式是毫无意义的.注记. 不被类型刻画且自身不是类型的表达式也被称为是病态类型(ill-typed)的. | 53 | 为什么呢? |
因为Pair 在其参数为实际原子时并非类型.只有在其参数均为类型 (例如 | 54 | 这是不是意味着Pair 不能和car 与cdr 一起使用呢? |
不, 完全不是. 的规范形式是什么? | 55 | 它的类型是什么呢? 规范形式总是相对于某个类型而言的. |
类型本身也有规范形式. 如果两个类型有着等同的规范形式, 那么它们就是相同的类型. 如果两个类型是相同的类型, 那么它们就具有等同的规范形式. | 56 | 类型 的规范形式必然是(Pair Atom Atom) , 因为 的规范形式为Atom , 并且 的规范形式也是Atom . |
|
就是这样. 现在我们知道 也是一个 因为... | 57 | ... 的规范形式为 而 是一个
|
另一种说法是 和 是相同的类型. | 58 | 如果一个表达式是一个 那么它也是一个 因为这两个类型是相同的类型. |
类似地, 如果一个表达式是一个 那么它也是一个 因为这两个类型是相同的类型. | 59 | 对于 也是类似的, 因为它和前面两个也是相同的类型. |
'6 是一个Atom 吗? | 60 | 不是, 我们没有理由去相信
6 既非字母也非连字符, 不是吗? |
的确如此. 是一个 吗? | 61 | 不, 因为(cons '17 'pepper) 的car 是'17 , 它并非一个Atom .尽管如此, 拥有数字应该也是很自然的事情才对. |
数字当然是很方便的. 除了Atom 和Pair , 我们还可以判断某个东西是否是一个Nat . | 62 | 让我们来试一下. |
1 是一个Nat 吗?注记. Nat 是自然数(natural number)的缩写. | 63 | 的确, 1 是一个Nat . |
1729 是一个Nat 吗? | 64 | 是的, 1729 是一个Nat . 它不仅是一个Nat , 还很著名!注记. 谢谢Srinivasa Ramanujan (1887–1920) 和Godfrey Harold Hardy (1877–1947). |
-1 是一个Nat 吗? | 65 | 嗯, 你确定? |
不, 当然不是. -23 呢? | 66 | 不是很清楚啊. |
正数是Nat . | 67 | 啊, 那么-23 不是一个Nat ? |
我们更喜欢采取积极 (positive) 的态度. 译注. 这大概是一语双关吧. 最小的Nat 是什么? | 68 | 0 不是一个自然数吗? |
啊, 所以我们也不能总是很积极. 我们该如何获得剩下来的Nat 呢?注记. 然而, 数字 1 总是正的. | 69 | 我们可以使用我们的老朋友add1 . 如果是一个Nat , 那么(add1 ) 也是一个Nat , 并且这个新的Nat 永远是正数, 即便是0 .有多少个 |
很多很多! | 70 | 存在最大的Nat 吗? |
并不, 因为我们总是可以... | 71 | ...使用add1 来加上一? |
的确是这样!
注记. 谢谢Giuseppe Peano (1838-1932). | 72 | 显然不是. |
(+ 0 26) 和26 是相同的吗?注记. 即便我们还没有解释 + , 暂时请用你自己对于加法的理解. | 73 | 这个问题没有意义. 但是, 我们可以问它们是否是相同的Nat 吗? |
当然可以.
| 74 | 是的, 这是因为(+ 0 26) 的规范形式为26 , 而26 和26 当然是相同的. |
zero 的意思是什么呢? | 75 | zero 和0 是相同的吗? |
在Pie里, zero 和0 不过是书写同一个Nat 的不同方式而已.
| 76 | 嗯, 如果zero 和0 是相同的Nat 的话, 那么这似乎很合理. |
实际上, one 没有意义. 但是, (add1 zero) 是书写数字1 的另一种方式.通过定义使得
| 77 | 为什么这个定义周围用虚线框住了呢? |
虚线框意味着这个定义有点问题, 以至于它不能在之后使用. | 78 | 这个定义有什么问题呢? 看上去很正常啊. |
当定义一个名字时, 有必要先claim 这个名字具有一个类型, 而one 是一个Nat .
| 79 | 因此, two 可以被定义成
|
|
如果1 是书写(add1 zero) 的另一种方式, 那么书写4 的另一种方式是什么呢? | 80 | 难道不应该是 吗? 我们不能定义four 来指代这个表达式吗? |
当然可以了. 那么, 再问一下书写8 有另外的方式吗? | 81 | 那必然是
|
8 是规范的吗? | 82 | 似乎如此, 但是为什么8 是规范的呢? |
之所以8 是规范的, 是因为其顶add1 是一个构造子(constructor), 并且塞在顶add1 下面的参数, 即7 , 也是规范的.注记. 第1章第81框中的顶 add1 只此一次用下划线标注出来以示强调. | 83 | 为什么7 , 亦写作 是规范的呢? |
7 是规范的完全是同理可得. | 84 | 这意味着zero 必然是规范的, 不然的话(add1 zero) 就不是规范的了. |
zero 的顶是什么呢? | 85 | 必须是zero . |
之所以zero 是规范的, 是因为顶zero 是一个构造子, 并且其没有参数. 是规范的吗? | 86 | 不是, 因为+ 不是构造子. |
一个以构造子为顶的表达式被称为一个值(value). 即便 不是规范的, 它的确是一个值.注记. 值也被称为典则(canonical)表达式. | 87 | 这个表达式不是规范的, 是因为 并非书写3 的最直接方式. |
|
现在我们给出另一个并非规范的表达式. 这是书写3 的最直接方式吗? | 88 | 肯定不是. 准确来说, 构造子究竟是什么呢? |
某些表达式是类型, 例如Nat 和(Pair Nat Atom) .对于新类型的解释的一部分是要说明其构造子为何. 构造子表达式是构造具有该类型的表达式的直接方式. | 89 | 构造子的例子有哪些呢? |
Nat 的构造子是zero 和add1 , 而Pair 的构造子是cons . | 90 | 值和规范形式之间有何关系? |
在一个值里, 顶层的构造子的参数不必是规范的. 但如果这些参数的确是规范的, 那么整个构造子表达式就具有规范形式. 所有的值都是规范的吗? | 91 | 显然不是. 和 都是值, 但是它们都不是规范的. |
|
以下空白方框里填什么会使得整个表达式不是一个Nat 值呢?
| 92 | 'aubergine 怎么样? |
诚然如此. 并非一个Nat 值, 因为'aubergine 是一个Atom 而不是一个Nat .当填充这样的方框时, 我们的期望是作为结果的表达式由某个类型刻画. | 93 | 然而, 若置于该方框里的是任何一个Nat 表达式, 那么整个表达式就是一个值. 这一整个表达式以add1 为顶, 而add1 是一个Nat 构造子. |
找出一个与某起始表达式相同的值被称为求值(evaluation). | 94 | 类型呢? 毕竟, 相同需要类型. |
有时当我们提及相同时, 我们并不显式提及类型. 然而, 总是存在一个意图的类型, 并且这个类型可以通过仔细阅读找到. | 95 | 难道求值指的不是找到一个表达式的意义(meaning)吗? 这不只是某个更简单的表达式. |
我们这里的含义不一样. 表达式并不指向某种外部的意义概念——在Pie中, 除了表达式和我们对于表达式的判断之外, 别无其他. 注记. 在Lisp中, 值和表达式是不同的, 而求值的结果是一个值. | 96 | 这是一种看待求值的新方式. 为什么规范形式和值之间要做区分呢? |
|
规范表达式没有可供求值的剩余机会了. 通常而言, 规范的表达式更容易理解. 然而, 往往找到一个值就足够了, 因为顶层的构造子可以用来判断接下来必然发生的事情. | 97 | 如果找到一个值经常就足够了的话, 难道说这意味着我们可以自由地去寻找值, 并且然后可以想停就停呢? |
是这样的, 只要关于构造子的参数的信息从没有用到即可. 和four 是相同的Nat 吗? | 98 | 这里给出一个可能的回答. 它们不是相同的 是一个值, 而且它当然长得不像变量four . 找到four 的值也无济于事, 因为four 的值看起来非常不同. |
算是好的尝试. 但是, 实际上它们是相同的 | 99 | 怎么能这样呢? |
两个不是值的Nat 表达式相同, 如果它们的值相同. 恰存在两种Nat 值可以相同的方式: 每种构造子一个.如果两个都是 | 100 | 那么两个值都以add1 为顶的情况呢? |
|
如果每个add1 的参数是相同的Nat , 那么两个add1 表达式是相同的Nat 值.为什么 和 是相同的
| 101 | 这两个表达式都是值. 这两个值都以add1 为顶, 因此它们的参数应该是相同的Nat .这两个参数都是 |
|
为什么 和 是相同的
| 102 | 这两个Nat 都以add1 为顶, 于是它们都是值.它们之所以相同, 是因为 和 是相同的
|
为什么(+ 0 1) 和(+ 1 0) 是相同的Nat ? | 103 | 这些Nat 并非值, 因而为了判断它们是否相同, 第一步应该是找出它们的值.这两个表达式都以 和 是相同的
|
很对. | 104 | 是否这意味着four 本可以按照以下方式定义?
|
为什么有虚线框呢? | 105 | four 已经被定义了, 因而不能被再次定义. |
|
不过当然了, 一开始four 的确可以像那样定义.实际上, 其他表达式都不能分辨出 | 106 | cons 是一个构造子吗? |
是的, cons 构造Pair . | 107 | 为了对于car 表达式求值, 是否有必要对于car 的参数求值? |
的确. 为了找出一个car 表达式的值, 我们从找出其参数的值开始.关于这参数的值, 我们能说什么呢? | 108 | 这参数的值以cons 为顶. |
在找出参数的值之后, 接下来应该做什么呢? | 109 | 整个表达式的值是cons 的第一个参数. |
的值是什么? | 110 | cons 的第一个参数是 这并非一个值. |
为了找出一个car 表达式的值, 首先找出其参数的值, 这应该是(cons ) , 而 的值然后就是的值.如何找出一个 注记. 这里的代表 car 而代表cdr . | 111 | 就和car 一样, 我们从对于cdr 的参数求值开始, 直至其变为(cons ) , 然后 的值就是的值.所有的构造子都有参数吗? |
当然不是, 回忆一下, 第1章第86框里的zero 是一个构造子.两个表达式为相同的 | 112 | 这必然意味着每个表达式的值都以cons 为顶, 并且它们的car 是相同的Atom 而cdr 是相同的Nat . |
非常好. | 113 | 原子是构造子吗? |
原子'bay 是一个构造子, 因而原子'leaf 也是一个构造子. | 114 | 所有原子都是构造子吗? |
是的, 每个原子都构造其自身. 这是不是意味着每个原子都是值? | 115 | 的确, 因为解释为什么
Atom 值. |
嗯. 在表达式 | 116 | 那必然是zero , 因为zero 是没有参数的构造子. |
对于表达式'garlic 而言, 什么是顶层的构造子? | 117 | 原子'garlic 是仅有的构造子, 所以它必然就是顶层的构造子.那么, |
不是, Nat 并非一个构造子. zero 和add1 是创造数据的构造子, 而Nat 描述了特定的数据, 其要么就是zero , 要么以add1 为顶, 且以另一个Nat 为其参数.
| 118 | 不是, 因为Pair 表达式是在描述以cons 为顶的表达式. 构造子创建数据, 而不是类型.那么, |
Pair 是一个类型构造子(type constructor), 因其构造了一个类型. 类似地, Nat 和Atom 也是类型构造子. 是一个 吗? | 119 | 不是. 难道它不应该是一个 吗? |
的确如此! 但是 是一个 请问 的类型是什么?注记. 谢谢Julia Child (1912-2004). | 120 | 基于我们的所见所闻, 它必然是一个
译注. basil, thyme, oregano分别是罗勒, 百里香, 牛至, 这是三种典型的香料. 另外, Julia Child是一位厨师和电视名人. |
诚然如此. | 121 | 好吧, 暂时就那么多了, 我的脑袋要炸了! |
或许你应该再一次阅读这一章. 判断, 表达式, 类型是本书最重要的概念. | 122 | 在完全读完这一章之后, 或许应该来点新鲜蔬菜. |
ratatouille如何? | 1 | 很好(très bien), 谢谢提问. |
第1章里有构造子和类型构造子, 分别构造值和类型. 然而, | 2 | 那么, car 是什么呢? |
car 是一个消去子(eliminator). 消去子将构造子构造的值拆开来.另一个消去子是什么呢? | 3 | 如果car 是一个消去子, 那么显然cdr 也是一个消去子. |
|
另一种看待(消去子和构造子的)不同的方式在于值包含信息, 而消去子允许我们去利用这样的信息. | 4 | 存在兼作构造子和消去子的东西吗? |
不, 并不存在. 可以定义一个函数(function), 其表达力相当于 | 5 | 怎么做呢? |
这需要请出我们的老朋友λ . | 6 | 这是什么? 我有点陌生. |
Oops! 它也被称为lambda .注记. λ 可以选择写成lambda . | 7 | 好吧, λ 可以构造函数.这是不是意味着 |
是的, 的确如此, 因为每个长得像(λ ( ) ) 的表达式都是一个值.这种值的消去子是什么呢? 注记. 记号的意思是零个或更多的, 因此的意思是一个或更多的. | 8 | 我们唯一能对函数做的事情就是将其应用于参数上. 这样的话函数怎么拥有消去子呢? |
应用函数于参数是函数的消去子. | 9 | 好吧. |
|
的值是什么?译注. lentils指的是小扁豆. | 10 | 它自λ 起, 因而它已经是一个值了. |
是的. 的值是什么呢? | 11 | 那必然是(cons 'garlic 'lentils) , 如果λ 和lambda 的行为一致且cons 是一个构造子的话.但是这难道不意味着即便 |
不, 并非如此, 但是这是个非常好的问题. 替换这个λ 表达式的flavor 行为之发生是因为该λ 表达式被应用于了一个参数, 而不是因为cons .注记. 一致地替换一个变量以一个表达式有时被称为替换(substitution). 这个λ 表达式的体的每个flavor 都会被替换为'garlic , 不论环绕flavor 的表达式是什么. | 12 | 因此, 也就是说 的值应该是 对吗? |
为什么前一个框里的 不需要计算呢? | 13 | 整个表达式以cons 为顶, 故其已是值. |
第2章第12框里有点夸大其词. 如果以下λ 表达式的体中的root 出现在另一个同名的λ 之下的话, 那么它就不会被替换了. 的值是什么?译注. 这里的例子有两个同名但不同的变量, 以示对比. | 14 | 那必然是 因为内层的root 出现在一个同名的λ 表达式之下. |
λ 的确和lambda 行为一致, 因而这的确是正确的答案.成为一个 就是要成为一个λ 表达式, 当其应用于一个Atom 作为参数时, 则求值至一个
注记. (→ Atom (Pair Atom Atom)) 读作箭头 原子 暂停 序对 原子 原子, 并且 → 可以写成是两个字符的版本: -> . | 15 | 那以这样的λ 表达式为值的表达式呢? |
是的, 那些表达式也是 因为当给出一个Atom 作为其参数时, 它们也会变成
| 16 | 它们也是 吗? |
的确如此, 因为 是Atom 而 也是Atom . | 17 | 提问何谓两个表达式为相同的Nat , Atom , (Pair Nat Atom) 都是有意义的.提问何谓两个表达式为相同的 或者相同的 也是有意义的吗? |
是的, 的确如此. 两个表达式为相同的 当它们的值是相同的
| 18 | 它们的值是λ 表达式. 两个λ 为相同的 是什么意思呢? |
两个接受 (expect) 相同数目的参数的λ 是相同的, 如果它们的体是相同的. 例如, 两个λ 表达式是相同的 如果它们的体是相同的
| 19 | 这意味着 和 不是相同的 吗? |
这两个表达式有什么不同之处呢? | 20 | 参数的名字是不同的. 尽管如此, 这通常无关紧要. 现在紧不紧要呢? |
两个λ 表达式也是相同的, 如果可以通过一致地对于参数换名使得它们的体变得相同.注记. 一致地对于变量换名常被称为变换(alpha-conversion). 感谢Alonzo Church (1903-1995). 一致地对于变量换名不会改变任何东西的意义. | 21 | 和 是相同的 吗? |
|
|
|
不, 并非如此, 因为对于第二个λ 表达式中的变量进行一致换名以匹配第一个λ 表达式的参数将会产生 而(cons d a) 和(cons a d) 并非相同的(Pair Atom Atom) . | 22 | 呢? 它和 是相同的 吗? |
|
首先, 一致地将换名为. 现在, 问题就变为 和x 是否是相同的Nat . | 23 | 恰有两种方式可以使得两个表达式成为相同的Nat . 一种是它们的值都是zero . 另一种是它们的值都以add1 为顶, 而这两个add1 的参数是相同的Nat .这些表达式并非 |
x 的值尚不可知, 因为这个λ 表达式还没有被应用于一个参数. 但是, 当这个λ 表达式已被应用于一个参数时, x 的值仍然是一个Nat , 因为... | 24 | ...因为这个λ 表达式是一个 故参数x 不可能是任何其他什么东西. |
并非值且因为变量的缘故还不能被求值的表达式被称为中立(neutral)的. | 25 | 这意味着 是中立的咯?译注. rutabaga是芜菁甘蓝的意思. |
不, 它并非中立, 因为 是一个值.如果 是一个值吗? | 26 | 不是, 因为cdr 是一个消去子, 而消去子把值拆散.在不知道 |
中立表达式使得我们有必要扩展对于何谓相同的看法. 每个变量与其自身都是相同的, 不管其类型如何. 这是因为变量只能被一致地替换, 所以说一个变量的两次出现不可能被代之以不同的值. | 27 | 因此, 如果我们假定y 是一个Nat , 那么 和y 是相同的Nat , 这是因为该car 表达式的规范形式是y , 而y 和y 是相同的Nat . |
的确如此, 并且类似地, 我们有 和 是相同的
| 28 | 是的, 因为中立表达式x 和x 是相同的Nat . |
和 是相同的 吗? | 29 | 我们应该会这样认为, 但是理由是什么呢? |
第一步应该是一致地将y 换名为x . 和 是相同的 吗? | 30 | 是的, 如果假定 和 是相同的Nat .但是 |
如果两个表达式以等同的消去子为顶, 并且两个消去子的参数是相同的, 那么这两个表达式也是相同的. 字面上等同的中立表达式是相同的, 不管它们的类型如何. | 31 | 因而 和 是相同的Nat , 若假定x 是一个(Pair Nat Nat) . |
|
是一个 吗? | 32 | → 后面跟着更多的表达式意味着什么呢? |
→ 后面跟着的表达式, 除了最后一个, 都是参数的类型. 最后一个则是值的类型.注记. 最后一个类型读的时候前面会有停顿. | 33 | 好吧, 那么 的确是一个 这些表达式不可避免地正在逐渐变长. |
一种缩短它们的方式是小心地使用define , 如第1章第77框那样, 其总是允许我们用简短的名字来代替冗长的表达式. | 34 | 好想法. |
设构造子cons 被应用于'celery 和'carrot , 我们可以称这个值为vegetables . 从现在开始, 每当名字vegetables 被使用, 它就和 是相同的 因为这就是vegetables 如何被define 的.译注. celery指的是西芹. | 35 | 为什么claim 后面有写着 呢? |
|
(Pair Atom Atom) 描述了我们可以怎样使用vegetables . 例如, 我们知道(car vegetables) 是一个Atom , 而(cons 'onion vegetables) 是一个
注记. 这对于小扁豆汤 (lentil soup) 来说也是一个好的开始. | 36 | 啊, 懂了. |
和 是相同的 吗? | 37 | 的确如此, 因为每个表达式的值都是一个序对, 并且其car 是'celery 而cdr 是'carrot . |
实际上, 每当是一个(Pair Atom Atom) 时, 那么和 是相同的 找出(car ) 和(cdr ) 的值是没有必要的. | 38 | 这看起来很合理. |
|
以下定义可以允许吗?
| 39 | 什么鬼? |
尽管可能是个愚蠢的想法, 但它是可以被允许的. 的规范形式是什么? | 40 | 那必然是10 , 因为五 (five) 加等于十. |
再想想. 请记得five 的奇怪定义... | 41 | ...哦, 好吧, 那应该是14 , 因为five 被定义成了9 . |
的确如此. | 42 | 这个定义可以允许吗? 似乎它看上去不那么蠢.
|
虽然和将five 定义为9 相比没那么愚蠢, 但是这个定义也是不被允许的.已经被使用了的名字, 不论是用于构造子, 消去子, 还是之前的定义, 都不适合再与 | 43 | 好. |
|
Nat 有一个消去子可以区分值为zero 的Nat 和值以add1 为顶的Nat . 这个消去子被称为which-Nat . | 44 | which-Nat 到底是如何辨别应该是哪一种Nat 的呢? |
一个which-Nat 表达式具有三个参数: , , :
which-Nat 判断是否是zero , 如果是, 那么整个which-Nat 表达式的值即的值. 否则的话, 如果是(add1 ) , 那么整个which-Nat 表达式的值即( ) 的值. | 45 | 因此, which-Nat 既要判断一个数字是否是zero , 又要在数字并非zero 时去除其顶的add1 . |
的确如此. 的规范形式是什么呢? | 46 | 必然是'naught , 因为其target zero 是zero , 故整个which-Nat 表达式的值是, 即'naught .为什么 |
黯淡是用来指出n 在那个λ 表达式的体里没有被使用. 没有被使用的名字都将以黯淡的形式出现. | 47 | 为什么没有被使用呢? |
which-Nat 提供了使用更小的Nat 的可能性, 但是它并不要求一定使用. 当然, 为了提供这种可能性, which-Nat 的最后一个参数必须要接受一个Nat . | 48 | 好. |
|
的值是什么? | 49 | 必然是'more , 因为4 是另一种书写(add1 3) 的方式, 而其以add1 为顶. 的规范形式为
|
|
|
|
的规范形式是什么? | 50 | 难道说是11 , 因为 是11 ? |
这个的规范形式应该是10 , 因为一个which-Nat 表达式的值是将藏在其target底下的Nat 作为参数传给其step得到的.译注. 也就是说, 要扒开一层 add1 . | 51 | 啊, 所以说规范形式为10 是因为 是10 . |
请定义一个叫做gauss 的函数, 使得(gauss ) 是从zero 到的所有Nat 之和.
注记. 根据民间传说, Carl Friedrich Gauss (1777-1855) 在他上小学被要求加起一个很长的数列时发现了. | 52 | 将多个Nat 加起来当然应该是一个Nat .
|
是的. 现在请定义它. | 53 | 怎么做呢? |
第一步是挑选一个样例参数. 好的选择大概应该在5 至10 之间, 这样的话既足够大而有趣, 也足够小而可控. | 54 | 5 怎么样, 然后呢? |
听上去不错. 的规范形式应该是什么? | 55 | 应该是, 即. 译注. 当然, 更严格地说, 是 15 . |
接下来的一步应该是收缩参数.
白框框住的是未知的代码, 而白纸黑字的部分则是其中的已知部分. 在这被框住的内容之中, 我们该如何从 得到 呢?
| 56 | 我们必须给(gauss 4) 加上5 , 而和为15 .
|
接下来, 我们要使得这种手段对于任意的以add1 为顶的Nat 成立.如果是一个 得到 呢?
请记得 | 57 | 找出(gauss (add1 )) 的方法在于将前一个框中的答案里的4 替换成.
zero 怎么样呢? |
(gauss zero) 是什么? | 58 | 显然是0 . |
现在让我们来定义gauss .请记得白色的框和白纸黑字的部分. | 59 | 小菜一碟! 名字n-1 暗示了其代表着一个脱去了n 的一层add1 的Nat , 或者说比n 小一的Nat .
|
很好的尝试. 如果递归可以作为一个选项, 那么这就不需要用虚线框住了. 可惜, 递归不是一个选项. | 60 | 为什么不让其成为一个选项呢? |
因为递归不是一个选项. | 61 | 为什么不让其成为一个选项呢? |
因为递归不是一个选项. | 62 | 好吧, 请解释为什么递归不是一个选项. |
递归之所以不是一个选项, 是因为每个表达式都必须具有一个值. 一些递归定义使得我们可以写下没有值的表达式. | 63 | 举个例子呢? 一个递归定义和一个没有值的表达式. |
forever 就是一个这样的例子.
(forever 71) 的值是什么? | 64 | 好问题. 为什么它被虚线框住了? |
递归不是一个选项, 故像forever 这样的递归定义将永远被虚线框住. | 65 | 但是, 对于像gauss 这种需要递归的定义该怎么办呢? |
递归定义存在着一种安全的替代品, 其允许我们写下gauss 而不需要包括gauss 这个名字, 对于其他诸多类似的定义也是如此. | 66 | 这里是gauss 的安全替代版本定义的起点.
|
就目前的走向来看, 还是正确的. 要义在于gauss 不能出现在其自身的定义里. | 67 | 现在递归不是一个选项这句话的含义是清晰的了. 难道说这意味着Pie中不可能写下 |
Pie中写下gauss 是可能的, 但是which-Nat 和define 还不足以让我们能够应对这个任务. 我们需要不同的消去子, 但是时机还远未成熟. | 68 | 耐心是一种美德. |
给诸如(Pair Nat Nat) 这样的表达式定义更加简短的名字也是可能的. | 69 | 这种情况下的claim 是什么样的呢? |
另一个好问题! 诸如 注记. 读作 you, 是universe(宇宙)的缩写, 以其描述了所有的类型 (除了自身). | 70 | 类型是值吗? |
有的类型是值. 一个为类型的表达式是一个值, 当其以某个类型构造子为顶时. 到目前为止, 我们见过的类型构造子有 | 71 | 所有的类型都是值吗? |
|
并非如此. 是一个类型, 但不是一个值, 因为car 既不是一个构造子, 也不是一个类型构造子. | 72 | 什么样的表达式由 描述呢?译注. prune, 西梅干. |
因为 和 是相同的类型, 所以 所描述的表达式和Atom 是同样的. | 73 | 类型构造子和构造子有什么区别? |
类型构造子构造类型, 而构造子 (或者说数据构造子) 构造由那些类型描述的值. 作出一个表达式是一个类型的判断需要知道其构造子. 但是, 的意义并不由知道所有的类型构造子给出, 因为可以引入新的类型. | 74 | (cons Atom Atom) 是一个吗? |
不是, 但 是一个 一个原子, 例如'plum , 是一个Atom . 从另一方面来说, Atom 不是一个Atom , 而是一个由刻画的类型. | 75 | 让我们来思考(Pair Atom Atom) 的事情. 是一个 吗? |
不是, 这是因为Atom 是一个类型, 而不是一个Atom . | 76 | 是一个吗? |
不是, 但的确是一个类型. 没有表达式可以成为其自身的类型. 注记. 可以是一个, 而可以是一个, 如此类推. 感谢Bertrand Russell (1872-1970). 感谢Jean-Yves Girard (1947-). 在这本书里, 单一的就够用了, 因为我们不需要被某个类型刻画. | 77 | 每个为的表达式是否也都是一个类型呢? |
是的, 如果是一个, 那么是一个类型. | 78 | 是否每个类型都由刻画呢? |
|
每个由刻画的表达式都是一个类型, 但是不是每个为类型的表达式都由刻画. | 79 | 是一个 吗? |
的确如此. 定义 译注. pear和pair同音. | 80 | 那必然是 从现在开始, Pear 的意思是 这个名字只有四个字符, 而其代表的类型有十四个字符. |
每当Pear 出现时, 是不是它就和(Pair Nat Nat) 是相同的类型? | 81 | 的确, 根据define 之诫. |
(cons 3 5) 是一个Pear 吗? | 82 | 是的, 因为 是一个 而 恰被定义为这个类型. |
这是很好的论证. | 83 | Pear 是一个值吗? |
不是. 由define 定义的名字既不是类型构造子也不是构造子, 故并非值.
| 84 | 你说的消去子是不是指的是将类型Pear 的值拆开的那种东西? |
是的.
| 85 | 什么叫做使得信息能够被使用? |
使得任意Pear 中的信息能够被使用的Pear 的消去子是这样的一种东西, 其可以应用函数于Pear 的两个Nat 参数. | 86 | 好吧. |
什么样的函数可以应用到两个作为参数的Nat 上呢? | 87 | 一个例子: + . |
交换Nat 的表达式是什么样的呢? | 88 | 如何? |
非常好. 从一个Pear 里提取出第一个Nat 的表达式是什么样的呢? | 89 | 那必然是
|
非常接近了. 实际上, 应该是
| 90 | 你真无聊. 但是, 没有这个黯淡的效果, 表达式本身也应该是正确的吧? |
是这样的. 为了从一个Pear 中得到一个类型为的值, 我们必然需要有一个类型为 的表达式. + 的类型是什么?注记. 毕竟可以是任意的类型. | 91 | 其取两个Nat 而产生一个Nat , 故类型必然是
|
很好. 当 的类型是什么? | 92 | 显然是 其和 相同.一个 |
|
试试这个: 是否可以写下elim-Pear 的claim 而不使用Pear 或Pear-maker 呢? | 93 | 可以, 只需要将Pear-maker 和Pear 替换为相应的定义即可. 名字Pear-maker 和Pear 从不是必要的. 名字elim-Pear 是必要的吗? |
什么时候定义是必要的呢? | 94 | 什么时候都不是必要的! |
是这样的. elim-Pear 和它定义里的那个λ 表达式是相同的. 的值是什么? | 95 | 怎么样? |
一个好的开始, 但这还不是值. | 96 | 值是(cons 17 3) , 这是因为elim-Pear 和其定义中的那个λ 表达式相同, 并且 和3 是相同的Nat , 而 和17 是相同的Nat , 最后 和 是相同的Pear . |
将两个Pear 加起来会是什么意思呢? | 97 | 难道说是将每个Pear 的第一个Nat 和第二个Nat 分别加起来吗? |
很好的猜测. 这种pearwise加法的类型是什么? 译注. 译者认为pearwise并非贴切, 因为是逐 Pear 的部分相加, 而不是逐Pear 相加, 可能partwise是更好的选择. | 98 | 对吗? |
怎么用elim-Pear 定义pearwise加法? | 99 | 好难啊, 是不是两个Pear 都要消去, 因为这些Nat 都是结果的一部分? |
当然了. 请定义 和 是相同的Pear . | 100 | 首先, 将anjou 和bosc 各自拆成其相应的部分, 然后再将它们的第一部分和第二部分分别加起来.
译注. anjou和bosc都是梨的品种. |
或许现在应该好好休息一下, 然后回来重读本章. | 101 | 是的, 的确是个好想法. 但是, 我们该如何到达第3章呢? |
通过到达第3章. | 102 | 幸亏递归不是一个选项. |
是时候玩Pie了呢. | 1 | 玩弄食物是不是有点不礼貌? |
尽管派 (pie) 的确是一种美味的食物, 但是Pie是一种语言, 玩玩而已无伤大雅. | 2 | 让我们开始吧. |
使用Pie很像是对话: 其接受声明, 定义和表达式, 并回之以反馈. | 3 | 什么样的反馈呢? |
对于声明和定义而言, 反馈是判断它们是否具备意义. 对于表达式而言, 反馈是表达式的类型和规范形式. | 4 | 不具备意义会怎么样呢? |
Pie会解释它们出了什么问题, 有时添加有用的提示. | 5 | 表达式可能会出什么问题呢? |
在吃Pie前请吃蔬菜. 尝试输入 看看会发生什么.译注. spinach, 菠菜. | 6 | Pie回之以 这里的the 是什么意思? |
其意即'spinach 是一个Atom .在Pie里, 一个表达式要么是一个类型, 要么由一个类型描述. Pie可以自己找出诸多表达式的类型, 包括原子. | 7 | 怎么样呢? |
这个表达式并不由某个类型刻画, 因为'spinach 不是一个序对. | 8 | Pie总能判断描述了表达式的类型吗? |
不能, 有时Pie需要帮助. 此时, 使用 注记. the 表达式也被称为类型注解. | 9 | 例如? |
Pie不能确定单独的cons 表达式的类型. | 10 | 为什么呢? 是一个 这难道不是显然的吗?译注. cauliflower, 花椰菜. |
这对于我们来说是显然的, 但是之后cons 将变得更加壮丽 (magnificent), 提升了的表达力 (power) 使得其类型不再能被自动确定. | 11 | 那么, 怎样让Pie确定 是一个序对呢? |
试试这个:
| 12 | 因此, 一个the 表达式将一个表达式与其类型联系起来, 不仅是在Pie的反馈里, 也可以出现在我们写下来的表达式里. |
|
Pie中存在两种表达式: 一种Pie可以自行判断其类型, 另一种对于Pie而言则需要我们的帮助. | 13 | 还有其他帮助Pie判断类型的方式吗? |
的确是有的. 在第1章里, claim 在其相应的define 之前出现是必要的, 而这告诉了Pie对于定义的意义而言使用何种类型. | 14 | 为什么我们不在每次Pie不能确定一个表达式的类型时就使用claim 和define 呢? |
原则上是可以的, 但总是把名字都写出来令人厌倦. | 15 | 还有什么其他方法能够帮助Pie找到类型吗? |
的确还有一种方法. 如果某处使用了一个表达式而此处只有一个类型能够成立 (make sense), 那么Pie就会使用这个类型. | 16 | 举个例子呢? |
当检查 (是否)由一个类型描述时, Pie使用 作为 的类型. | 17 | 这里, 内层的cons 不需要一个the 是因为其类型是来源于外层的cons 的类型的.以 译注. kale, 羽衣甘蓝. |
并非如此. 的值是的值. | 18 | 那么 的值是什么?译注. brussels sprout, 抱子甘蓝. |
|
值是很小一点的'brussels-sprout .现在试试这个:
| 19 | Pie说: 为什么不是 呢? |
是一个类型, 但是其不具备类型. 这是因为没有表达式可以是其自身的类型, 正如第2章第77框所写的那样. 当一个表达式是一个类型但不具备类型时, Pie只回以其规范形式. | 20 | 还有其他别的什么类型不具备类型的吗? |
当然有了, 例如
都是类型, 但是它们不以作为它们的类型. | 21 | 还有其他什么关于Pie的方面是我们应该知道的呢? |
暂时就这么多了. 之后还有机会. | 22 | 下一步是什么? |
尽情玩乐. | 23 | 好主意. (Sounds like a plan!) |
以下是来源于第2章第59框的gauss 的虚框定义. 现在是时候以不使用显式递归的方式来正确地定义gauss 了. | 1 | 难道说我们将要像下面这样来定义gauss 吗?
|
为什么递归定义不是一个选项? | 2 | 因为递归定义不是一个选项. |
确实. 但是, 某些递归定义总是能产生一个值. | 3 | 比如说gauss , 不是吗? |
的确如此.
| 4 | 是zero . |
(gauss 1) 的值是什么? | 5 | 是1 , 因为
注记. 当表达式垂直对齐而左边又有竖杠时, 我们将假定除了最后一行之外的每一行后面都跟着 相同于 (is the same as). 这种图表被称为 相同于 (same as)图表. 译注. 原书的竖杠是连贯的, 虽然也可以实现这样的效果, 但是译者太懒了. |
这就是值吗? | 6 | 不是还有更多要做的吗?
|
|
实际上 已经是一个值了. 为什么? | 7 | 好吧, 那是因为构造子add1 就在顶上. |
诚然如此.
| 8 | 是(add1 zero) . |
为什么(gauss 2) 具有规范形式? | 9 | 因为(gauss 2) 的规范形式只依赖于(gauss 1) 的规范形式 (其的确有一个规范形式) 和+ 的规范形式.
|
一旦定义了+ , 它的确具有规范形式. 暂时, 假定+ 具有规范形式. | 10 | 好吧. |
为什么(gauss 3) 具有规范形式? | 11 | 因为(gauss 3) 的规范形式只依赖于(gauss 2) 的规范形式 (其的确有一个规范形式) 和+ 的规范形式. 暂时, 我们假定+ 具有规范形式. |
为什么对于任意的Nat , (gauss (add1 )) 都有规范形式? | 12 | 因为(gauss (add1 )) 的规范形式仅依赖于(gauss ) 的规范形式, 的值, 以及+ 的规范形式.的值要么是 译注. 恕译者愚钝, 此处无法理解. |
为每个可能的参数都赋有一个值的函数被称为完全函数(total function).
| 13 | 存在并非完全的函数吗? |
|
至少这里没有. 在Pie里, 所有的函数都是完全的. 消去子是什么? 注记. 因为所有函数都是完全的, 所以子表达式的求值顺序并不重要. 如果某些函数并非完全, 那么求值顺序将会变得重要, 因为其将决定函数是否会应用到并非拥有值的参数上. | 14 | 消去子拆开由构造子构造的值. |
将一个Nat 拆开是什么意思? | 15 | 难道which-Nat 不会拆开一个Nat 吗? |
这意味着which-Nat 是Nat 的一个消去子. 但是, 以add1 为顶的Nat 底下还藏着一个更小的Nat , 而which-Nat 并不会消去这更小的Nat . | 16 | 存在消去更小Nat 的方法吗? |
一种消去更小Nat 的方法是使用iter-Nat . | 17 | 什么是iter-Nat ? |
一个iter-Nat 表达式长得像这样: 和which-Nat 一样, 当是zero 时, 整个iter-Nat 表达式的值即的值. | 18 | iter-Nat 和which-Nat 不一样的地方在哪里? |
和which-Nat 不一样的是, 当是(add1 ) 时, 整个iter-Nat 表达式的值是 的值. | 19 | 所以说的值中的每一个add1 都将被代之以一个, 而zero 会被代之以. |
|
|
|
是的. 的规范形式是什么? | 20 | 是8 , 因为add1 相继应用于3 五次是8 :
|
整个iter-Nat 表达式的类型和的类型相同吗? | 21 | 必然如此, 因为当是zero 时, 整个iter-Nat 表达式的值是的值. |
让我们使用作为的类型的一个名字. 的类型如何? 译注. 也就是说, 是一个元变量. | 22 | 被应用于, 其也被应用于由产生的几乎是答案的东西 (almost-answer), 故必然是一个
|
就和第2章第45框中的which-Nat 一样, 名字, , 是引用iter-Nat 的参数的便利方式.以下的
| 23 | target是 base是 step是
|
到目前为止, 我们引用+ 就好像其已被完全理解了一样, 并且我们还假定其具有规范形式, 然而我们还没有定义+ .
| 24 | + 接受两个Nat 而返回一个Nat .
|
的确如此. 若是递归可以成为一个选项, 那么以下将会是一个正确的定义. 那么, 该如何利用iter-Nat 来定义+ 呢? | 25 | 使用iter-Nat 定义+ 需要一个base和一个step. base应该是j , 鉴于以下相同于图表:
|
step基于+ 的递归版本的白框里的内容, 其描述了如何将一个几乎是答案的东西+n-1 变为一个真正的答案.将白纸黑字的部分 (其包含有递归) 替换以献给step的几乎是答案的参数, 记得保留白框里其他内容. | 26 | 以下就是我们想要的step了.
|
除非类型和定义里的所有名字都已定义, 不然我们无法定义一个新的名字. 注记. 如果定义可以相互引用, 那么我们就无法保证每个被定义的函数都是完全函数了. | 27 | 而+ 需要引用step-+ , 当然其既已定义.
|
是的, 现在+ 就定义好了.
译注. 右侧排版没有按照原文, 见谅. | 28 | 是8 , 因为
8 . |
iter-Nat 可以用来定义gauss 吗? | 29 | iter-Nat 展现了一种反复消去add1 下面藏着的更小的Nat 的方式.消去更小的 |
是挺接近的, 但是step没有足够的信息. gauss 需要一个结合了which-Nat 和iter-Nat 的表达力的消去子. 这个消去子叫做rec-Nat . | 30 | rec-Nat 是什么?译注. 根据The Little Typer官网的勘误表, 其实 rec-Nat 是可以用iter-Nat 表达的. |
rec-Nat 的step会被应用到两个参数上: 一个是add1 下面藏着的那个更小的Nat , 另一个是这更小的Nat 上的递归答案. 这是第2章第59框里的gauss 定义所使用的方法.此即所谓 注记. rec-Nat 模式也被称为原始递归(primitive recursion). 感谢Rózsa Péter (1905-1977), Wilhelm Ackermann (1896-1962), Gabriel Sudan (1899-1977), 以及David Hilbert (1862-1943). | 31 | 如何使用rec-Nat 定义gauss 呢? |
这个框里有两种gauss 的定义: 一个是来源于第2章第59框虚框版本, 另一个则是使用rec-Nat 的版本.
有什么区别呢? | 32 | 三个区别:
译注. 原文中 rec-Nat 版本的gauss 也被加上了虚框, 并不是因为这个定义是错的, 而是因为之后出现的利用辅助过程step-gauss 的定义才是正式的. |
名字n-1 和gaussn-1 暗示了其意义, 尽管它们只是变量名而已.
| 33 | 我们该如何确定一个rec-Nat 表达式的值呢? |
和iter-Nat 一样, 如果target是zero , 那么整个rec-Nat 表达式的值即的值. | 34 | 如果target以add1 为顶呢? |
which-Nat 应用其step于藏在add1 下的这更小的Nat .
这两者该如何结合起来呢? | 35 | 我猜一下. step将被应用到这更小的 |
很好的想法. 当rec-Nat 和作为target的非zero 的Nat 一起使用时, target将藉由每次移除一个add1 收缩. 而且和iter-Nat 一样, base和step是不会改变的. 的值是什么? | 36 | 其为step应用于zero 和新的rec-Nat 表达式, 即
|
前一个框中作为结果的表达式并不是一个值, 但是其和原本的表达式是相同的. 那么值是什么呢? | 37 | 其是一个值, 因为其以add1 为顶. |
其规范形式如何? | 38 | target是zero , 而base是0 . |
一个rec-Nat 表达式只在target为Nat 时是一个表达式. | 39 | base和step的类型应该是什么呢? |
base必然具有某个类型, 让我们再次称其为. 可以是任意的类型, 但是整个rec-Nat 表达式必然和base有着相同的类型, 即. | 40 | 这就是所有要说的了吗? |
还没有说完. 如果base是一个, 那么step必然是一个 为什么这是step的合适类型呢? | 41 | step会被应用于两个参数: 第一个参数是一个Nat , 因为其为target里藏在add1 下的那个东西; 第二个参数是almost , 其为是因为其也由rec-Nat 产生. |
那么, 这是如何与which-Nat 和iter-Nat 里的step的类型联系起来的呢? | 42 | 就像which-Nat , rec-Nat 的step接受藏在target的add1 下的那个更小的Nat 作为参数. 就像iter-Nat , 其也接受递归的几乎是答案的结果. |
以下是一个函数, 其判断一个Nat 是否是zero .
注记. 我们将 't 和'nil 当成两个任意的值使用. 这或许对于Lisper而言是熟悉的 (感谢John McCarthy (1927-2011)), 但是zerop 在Scheme中被称为zero? (感谢Gerald J. Sussman (1947-) 和Guy L. Steele Jr. (1954-)). | 43 | 为什么要使用递归性的rec-Nat 来定义实际上只需要判断顶层构造子是zero 还是add1 的过程呢? 毕竟, which-Nat 就已经够用了. |
which-Nat 很容易解释, 但是rec-Nat 可以做到任何which-Nat (和iter-Nat ) 可以完成的事情.为什么 | 44 | 之所以选取n-1 这个暗示比n 小一的名字, 还是因为其比target Nat 小一, target Nat 即正被消去的那个Nat 表达式. 名字zeropn-1 暗示着(zerop n-1) . |
step仅仅是一个λ 表达式, 故其他任意未被使用的变量名亦可运用, 但是我们往往在step中使用这种命名变量的风格.
| 45 | 不使用其参数的λ 表达式意义何在? |
rec-Nat 的step总是接受两个参数, 尽管其并不总是使用它们.
| 46 | 让我们看看.
36 (也就是(add1 35) ) 而言的zerop 值并不需要, 故我们没有找到它的理由. |
在表达式的值实际变得必要之前, 我们都无需对于表达式求值. 否则的话, 对于step-zerop 的参数 求值就太花工夫了, 以至于若记录在相同于图表中则需另费至少105行. | 47 | 有时懒惰 (laziness) 是一种美德. |
(zerop 37) 和(zerop 23) 相同吗? | 48 | 是的, 的确.
|
以下是gauss 的step.
| 49 | 这个定理使用了来源于第3章第44框的命名约定. |
是的, 的确如此. 定义step的另一个好处在于我们需要显式地写下其类型, 而不是任其由其于 | 50 | 显式类型使得阅读和理解定义更为容易. |
诸如zeropn-1 和gaussn-1 这样的step中的λ 变量几乎(almost)就是答案了, 其意见于第2章第56框. | 51 | 好.
|
以下就是了. base是什么? | 52 | base即rec-Nat 的第二个参数. 这个情况下, 是0 , 一个Nat . |
step是什么? | 53 | 即step-gauss . |
的确如此. | 54 | 根据这个定义, (gauss zero) 是什么? |
是0 , 因为 和rec-Nat 的第二个参数相同, 即0 .以下是我们找出
| 55 | 出发.
add1 为顶. |
这个值是规范的吗? | 56 | 不是, 但是以下图表找出了其规范形式.
|
为什么rec-Nat 使用起来总是安全的? | 57 | 这是个好问题. 当target以 |
如果step和第3章第43框的情况一样不依赖于几乎是答案的参数, 那么我们就已经抵达了一个值. 如果step的确依赖于几乎是答案的参数, 那么我们可以保证递归抵达base, 其总是一个值或者是一个可以成为值的表达式. | 58 | 我们是怎么知道的呢? |
因为每个target Nat 要么和zero 相同, 要么和(add1 ) 相同, 其中是一个更小的Nat . | 59 | 我们怎么知道更小的? |
使得相同或者更大的方式是假设target Nat 由无限多的add1 构造而成. 然而, 因为每个函数都是完全的, 我们没有办法做到这点. 类似地, 没有步骤 (step) 可以不是完全的, 因为这里所有的函数都是完全的, 而每一步骤不过就是应用一个函数.译注. 这段话中的step有点一语双关的意味. | 60 | 所以说, 为什么我们不能将这种推理风格应用于任意的递归函数呢? |
这种推理风格无法以我们的工具表达. 但是, 一旦我们相信step完全的rec-Nat 是一种消去任意作为target的Nat 的方法, 那么每个新定义都是完全的了.注记. 大致上说, 我们没法做, 但是即便我们能做, 那也会很累人的. | 61 | 还有什么使用rec-Nat 的更有趣的例子吗? |
其可以用来定义* , 意即乘法.换言之, 如果 应该是n 和j 之积.注记. * 读作乘 (times). | 62 | * 接受两个Nat 并返回一个Nat , 故以下是* 的类型.
|
对于每一个步骤 (step), + 给当时的答案 (the answer so far) 加上一. * 的每一步会做什么呢? | 63 | * 会将j 加到第二个参数上去, 也就是几乎是答案的那个参数. |
以下是make-step-* , 其对于每个给定的j 产生一个step函数.
| 64 | 这看起来和之前的step不太一样. |
不论j 为何, make-step-* 都构造了一个合适的step . 这个step接受两个参数, 因为和rec-Nat 一起使用的step都要接受两个参数, 就和第3章第46框中的step-zerop 一样.现在定义 | 65 | 好.
|
看起来好像make-step-* 在干什么新鲜的事情. 它是一个λ 表达式, 并且产生一个λ 表达式. 其实无需两步过程, 我们可以将嵌套的λ 合并为一个.
make-step-* 对于任意给定的j 产生一个step. 而且, 尽管看起来不同, make-step-* 和step-* 实际上有着相同的定义. | 66 | 不可能是相同的定义呀, step-* 明明是一个具有三个参数的λ 表达式. |
实际上, 所有的λ 表达式都恰接受一个参数. 不过是 的缩写而已. | 67 | 是不是 也是某种东西的缩写呢? |
其是 的缩写. | 68 | 如果一个函数接受三个参数, 那么可以只应用函数于其中一个. 可以仅仅应用这个函数于两个参数吗? |
如果是一个 那么 不过是 的缩写, 而这又是 的缩写. | 69 | 是不是每个函数都恰接受一个参数呢? |
诚然如此, 每个函数恰接受一个参数. 定义多参数函数为嵌套的单参数函数的过程被称为Currying. 注记. 感谢Haskell B. Curry (1900-1982) 和Moses Ilyich Schönfinkel (1889–1942). | 70 | 以下是* 的正式定义. 尽管step-* 看上去像一个三参数的λ 表达式, 它可以只接受一个参数. rec-Nat 期望其step 是一个恰接受 (get) 两个参数的函数. |
以下是计算(* 2 29) 的规范形式的图表的前五行.
| 71 | 啊, Currying也有参与.
|
|
|
|
当然有了, 比如说使得(+ 29 0) 以及作为结果的(+ 29 29) 规范的过程.注记. 这个图表节约纸张, 能量, 时间. | 72 | 谢谢. 从一开始, 这种图表似乎就会变得很乏味. |
就是这样. 这个定义取什么名字比较合适呢? | 73 | 这个函数总是返回0 . |
非常善于观察啊. 像 注记. 实际上, 第3章第73框中的定义本意在是 factorial . 然而, 疏忽导致了错误一直存在于诸多草稿版本之中却未被发现 (至于有多少这样的版本, 作者不想说了). 我们将纠错的任务留给读者. | 74 | 那么, 更为强大的类型可以阻止我们像第2章第39框里那样将five 定义成是9 吗?译注. 原文为第2章第36框, 应该是笔误. |
当然不行了. 类型并不能阻止将 | 75 | 非常有趣. |
在第2章第80框里, 我们定义Pear 为
Pear 的消去子是由car 和cdr 定义的. | 1 | 并且... |
Pear 的一个消去子必须要做什么呢? | 2 | 这个消去子必须要暴露 (或者说解包) 一个Pear 中的信息. |
Pair 的消去子呢? 它必须要做什么? | 3 | Pair 的一个消去子必然要暴露一个Pair 中的信息. |
那很接近了. 正如第1章第22框所言, Pair 单独不是一个表达式, 然而 是一个表达式, 并且它有消去子. 也有消去子. | 4 | 再次尝试: 的一个消去子必然要暴露一个特定 中的信息, 一个 的消去子必然要暴露一个特定 中的信息. |
但是, 这意味着Pair 需要许多消去子, 因为就像第2章第36框一样, 更深的嵌套总是可能的. | 5 | 听起来要记得好多名字的样子. |
本会如此! 实际上, 还有更好的方式. 我们可以提供一个 | 6 | 不论为何? 即便是'apple-pie 吗? |
好吧, 当然不是绝对任意的. 根据第1章第54框, 除非和是类型. 也就是说, 必须是一个类型且必须是一个类型. | 7 | 哇! 那这消去子看起来长什么样呢? |
给个例子.
因为elim-Pair 还未被定义, 所以kar 的定义才会用虚线框起来, 不是别的什么原因. | 8 | 为什么elim-Pair 有这么多参数啊? |
在这个定义里, elim-Pair 的前三个参数都是类型Nat . 前两个参数刻画了要被消去的Pair 的car 部分和cdr 部分的类型. 这第三个参数Nat 描述了内层的λ 表达式的结果类型.注记. 因此, 内层的 λ 表达式的参数a 和d 的类型也都是Nat . | 9 | 内层的λ 表达式的意图是什么? |
内层的λ 表达式描述了如何使用p 的值中的信息. 所谓的信息即p 的car 和cdr 部分. | 10 | 为什么d 是黯淡的? |
参数名d 之所以是黯淡的, 是因为其出现在内层的λ 表达式里, 却未被使用, 就和第2章第47框一样.现在请定义一个类似的函数 | 11 | 几乎和kar 一模一样.
这次之所以a 是黯淡的, 是因为其在内层的λ 表达式中未被使用, 而d 是正常的. 鉴于elim-Pair 还没有定义, kdr 也被虚框包裹, 就和kar 一样. |
的确如此. 请编写一个叫做 | 12 | 以下是swap 的类型.
|
现在定义swap . | 13 | 以下是swap 的定义. 就和kar 和kdr 一样, 其也被包裹在虚框里.
|
一般说来, elim-Pair 的用法如下: 其中是一个(Pair ) , 而根据的car 和cdr 来确定整个表达式的值. 这个值必然具有类型.
| 14 | 以下是一个猜测, 可能是 因为A , D , X 是前三个参数, 第四个参数是一个(Pair A D) , 而第五个应该基于一个A 和一个D 作出一个X 来. |
但是, 这个表达式里的A , D , X 是什么呢? | 15 | A , D , X 是elim-Pair 的前三个参数? |
它们会引用之前已经定义过的类型吗? | 16 | 不会, 因为它们指的是不论什么样的参数. |
一个表达式中的名字要么引用一个定义, 要么指的是由一个λ 所命名的一个参数. 显然这个表达式里没有λ , 并且A , D , X 也都没有被定义. | 17 | 这必然意味着第4章第14框中的那个表达式实际上并非一个类型. |
的确如此. 然而, 这种思维过程的确言之成理. 回忆一下, 成为一个 是什么意思. | 18 | 一个 是一个λ 表达式, 当接受一个时, 产生一个. 它也可以是一个值为这样的λ 表达式的表达式. 我说的对不对呢? |
和都是类型吗? | 19 | 必然如此, 否则 就不是一个类型了. |
在之前我们提出的elim-Pair 的类型里, A , D , X 是类型构造子吗? | 20 | 并非如此, 它们和Nat 或者Atom 不是一种表达式, 因为每次elim-Pair 应用时它们都可能改变, 而Nat 永远是Nat . |
在之前我们提出的elim-Pair 的类型里, A , D , X 是被定义来指代类型的名字吗? | 21 | 当然不是, 出于相同的原因, 即每次elim-Pair 应用时它们都可能改变. 但是, 一旦一个名字被define 了, 它就永远指的是相同的东西了. |
这个消去子必然要能够讨论任意的类型A , D , X . | 22 | 听上去→ 没法完成任务. |
的确不行, 但是Π 行.注记. Π 读作pie, 并且也可以写成 Pi . | 23 | Π 是什么意思呢? |
以下是一个例子.
| 24 | 是不是这意味着一个λ 表达式的类型可以是一个Π 表达式? |
好问题. 的确可以. | 25 | 如果说Π 和→ 都可以用来描述λ 表达式, 那么它们有什么区别呢? |
的值是什么? | 26 | 那必然是λ 表达式 这是因为flip 被定义为是一个λ 表达式, 而其被应用于了两个参数, Nat 和Atom . |
的值是什么? | 27 | 其是 一个
|
Π 和→ 的不同在于一个函数应用于其参数这样的表达式的类型. 的类型为 这是因为当一个由Π 表达式所描述的表达式被应用时, 参数表达式将代替Π 表达式的体里的参数名. | 28 | Π 表达式的体和λ 表达式的体有着怎样的联系呢? |
Π 表达式和λ 表达式都会引入参数名, 而体是这些名字可以使用的地方. | 29 | 什么是参数名? |
在这个Π 表达式里, 参数名是A 和D . Π 表达式可以有一个或更多的参数名, 而这些参数名可以出现在Π 表达式的体中. | 30 | Π 表达式的体是什么? |
在这个Π 表达式里, 体是 这个是(flip 所代表的)λ 表达式的体的类型, 这个体由Π 表达式的体所描述.译注. 这句话读起来稍显冗余. | 31 | Π 表达式的体里的A 和D 指的是什么? |
|
体里的A 和D 指的是尚不可知的特定类型. 不论哪两个类型和作为由Π 表达式所描述的λ 表达式的参数, 应用这λ 表达式的结果总是一个
译注. 请读者注意字体, A 和相当不同. | 32 | 是不是这意味着 的类型为 呢? |
对的. 但为什么会是如此呢? | 33 | 变量A 和D 被替换以其相应的参数: Atom 和(Pair Nat Nat) . |
和 是相同的类型吗? | 34 | 的确如此, 因为正如第2章第21框所言, 一致地对于变量换名不会改变任何东西的意义. 译注. meringue, 蛋白酥. |
和 是相同的类型吗? | 35 | 是的, 因为 和A 是相同的类型, 而 和D 是相同的类型. |
我们可以这样定义flip 吗?
| 36 | 以下是我的猜测. 在这个定义里, 外层的 译注. 原文的后两个 A 和D 本是黯淡的, 但是译者认为既然它们指的是Π 表达式中的相应变量, 所以说它们应该是正常颜色更好. |
第4章第36框中提出的flip 定义是可以允许的. 然而, 就像定义five 为意指9 一样, 这是愚蠢的. | 37 | 为什么可以允许这样的定义呢? |
外层的λ 中的名字不需要匹配Π 表达式中的名字. 外层的λ 表达式中的C 与Π 表达式中的A 相对应, 因为它们都是第一个名字. 外层的λ 表达式中的A 与Π 表达式中的D 相对应, 因为它们都是第二个名字. 重要的是参数命名的顺序.注记. 尽管使用不相匹配的名字并非错误, 但这的确相当令人困惑. 我们总是使用匹配的名字. 内层的λ 表达式中的p 与什么相对应? | 38 | 与p 相对应的是→ 后跟着的(Pair A D) , 其给出了内层λ 表达式的参数类型. |
如何对于第4章第36框中的定义里的C 和A 进行一致换名以改善这个定义? | 39 | 首先, A 应该被重命名为D . 接着, C 应该被重命名为A . 这不就是第4章第24框中的定义吗? |
现在可以定义Pair 的那个消去子了吗? | 40 | 是的, 其类型应该是 这看起来很像第4章第14框里的那个类型. |
的确如此.
| 41 |
|
现在kar 不需要虚框了.
| 42 | kdr 也是.
|
swap 也是. | 43 | 是的.
|
尽管一个Π 表达式可以拥有任意数目的参数名, 还是首先描述单参数Π 表达式为类型的情形最为简单.成为一个 即是成为一个λ 表达式, 当且被应用于一个类型时, 将会产生一个表达式, 其类型是将中的每个一致地替换以的结果. | 44 | 是不是忘了什么? |
也可以是一个表达式, 其值是这样的λ 表达式. | 45 | 不要忘记求值是重要的. 这是对于 |
不, 还不完全. 基于单参数的 该怎样理解呢? | 46 | 它应该意味着一个λ 表达式 (或者能求值至这样的λ 表达式), 当其被应用于两个类型和时, 将会产生一个表达式, 其类型可由一致地将中的每个替换以然后再将这新的中的每个替换以获得.译注. 这里假定和相异, 至于不相异的情况该如何解释, 实际上多个参数的 Π 表达式可以理解为单参数Π 表达式的嵌套. |
Π 表达式可以拥有任意数目的参数, 而其所描述的λ 表达式有着相同数目的参数.什么表达式具有类型 呢? | 47 | 比如说以下这个?
|
以下我们为熟悉的表达式取了个名字.
的值是什么?注记. 自第2章第19框起就熟悉了. | 48 | 是
|
以下是一个非常类似的定义.
的值是什么? | 49 | 是 这些定义有什么问题? 为什么它们被虚线框起来了?译注. 之所以用虚线框起来, 是因为这些定义不够一般, 于是不值得定义, 而应该代之以一个更一般的版本. |
之于
Nat 和Atom 没什么特别之处. 因此, 与其对于每个类型写下一个新的定义, 我们不如使用Π 构建一个一般目的性的twin , 其可以对于任意的类型成立. | 50 | 以下即是一般目的性的twin .
|
(twin Atom) 的值是什么? | 51 | (twin Atom) 是
|
(twin Atom) 的类型是什么? | 52 | 一致地将 中的每个Y 替换以Atom 就得到了
|
twin-Atom 的类型和(twin Atom) 的类型之间有什么联系? | 53 | twin-Atom 的类型和(twin Atom) 的类型是相同的类型. |
接着, 使用一般性的twin 来定义twin-Atom .
| 54 | 可以使用来自于第4章第27框的技巧.
|
和 是相同的 吗? | 55 | 是的, 并且其值 (也是规范形式) 为 对于甜点而言, 这就是双倍了! |
Π 怎么样? | 1 | 美味至极. 尽管如此, 使用餐巾可以使得用餐不那么狼狈. |
在我们开始之前, 你有没有
| 2 | 这俨然是期望清单. |
是的, 不过这些是很好的期望.
| 3 | 这段代码有些令我困惑, 在于以下几个方面:
|
'nil 和第5章第3框里的nil 是相同的吗? | 4 | 不是, 因为第5章第3框里的nil 并非一个Atom , 其不以单引号开头.
|
List 是一个类型构造子. 如果是一个类型, 那么(List ) 是一个类型.注记. 读作 类型为的元素的列表或者更简单的 的列表. | 5 | 那么, 成为一个(List ) 的意思是什么呢? |
|
nil 是一个(List Atom) 吗? | 6 | 在第5章第3框里nil 看起来扮演着空列表的角色. |
是的, nil 的确是一个(List Atom) .
| 7 | 似乎不是这样, 因为nil 已经是一个(List Atom) 了. |
实际上, nil 也是一个(List Atom) .
| 8 | 是的, 因为(List Atom) 是一个类型, 所以(List (List Atom)) 也是一个类型. (List (Pair Nat Atom)) 怎么样呢?是不是 |
的确如此. | 9 | 这意味着nil 也是一个 咯? |
不, 并非如此, 因为'potato 不是一个类型. | 10 | 是不是这和第1章第52框中 并非类型出于相同的原因? |
是的. 之所以 | 11 | 好吧. 这意味着如果是一个类型, 那么(List ) 是一个类型, 对吗? |
并且, 如果(List ) 是一个类型, 那么nil 是一个(List ) . | 12 | 好吧.
|
是的, nil 的确是一个构造子.猜猜 | 13 | 根据expectations , :: 是另一个构造子. |
:: 和cons 有什么区别?构造子 注记. 出于历史原因, :: 亦读作cons或者 list-cons. | 14 | ..., 但是构造子cons 构建一个Pair . |
可以有序对的列表, 也可以有列表的序对. 何时 注记. 的复数形式是, 读作 ease. 使用是因为一个列表的剩余部分可以具有任意数目的元素. | 15 | 嗯, 必须要是一个(List ) . 可以是nil , 而nil 是一个(List ) . |
可以是任意的东西吗? | 16 | 当然可以! |
当然不行! 再试试. | 17 | 我猜一下: 必须要是一个, 不然的话还没有任何用处. |
正确的答案, 错误的原因. 之所以必须要是一个, 是因为为了能够使用 请将 注记. 读作[ˈʁuˌb̥ʁœðˀ], 如果还是不懂, 就去问问丹麦人. 译注. rugbrød, 丹麦语, 意思是黑麦面包. | 18 | 那么成分有什么呢? |
rugbrød里的成分有:
| 19 | rugbrød 应该具有什么类型? |
(List Atom) , 因为每个成分都是一个Atom . | 20 | 好的, 以下就是了.
|
非常好. | 21 | 是的, rugbrød 非常美味! 尽管如此, 顶上还需要加点什么才好. |
让我们回到正题.
| 22 | 似乎看起来它们就没有相同的地方. 5 由add1 和zero 构成. 而且, 5 也不好吃. |
rugbrød 里包含多少种成分呢? | 23 | 五种. |
不仅只需要五种成分, rugbrød 甚至不需要揉面. | 24 | 那么, :: 是不是和add1 有什么关系? |
:: 使得一个列表更大, 而add1 使得一个Nat 更大.
| 25 | nil 是最小的列表, 而zero 是最小的自然数.列表的消去子和 |
|
|
是的. 的类型是什么? | 26 | 当
rec-Nat 表达式是一个. |
(List ) 的消去子写作 并且当
这和 | 27 | rec-List 的比rec-Nat 的多一个参数——其接受, 列表里的一个元素. |
说的很好! 在这两种情况下, step都接受相应构造子的每个参数, 并且也接受对于更小的值的递归性消去. | 28 | 消去子暴露了值中的信息. |
base也暴露了不少关于一个rec-List 的结果的信息. 能给出两个以0 为base的rec-List 用例吗? | 29 | 一个是找出列表的长度, 另一个是找出一个(List Nat) 中的所有Nat 之和. |
的确是两个不错的例子. 以此定义, 之值为何? | 30 | 必然是0 , 因为0 是base, 而base之值必然是rec-List 对于nil 之值. |
是的. 一个
注记. butter和mayonnaise也可以换成你喜欢的非乳制品替代物. | 31 | 听起来非常地美味(lækkert)! |
|
|
|
的确美味! 的值是什么? | 32 | 让我们看看.
|
规范形式是什么? 不要对于省略中间的表达式保有负担. | 33 | 规范形式为 或者更为人知的版本是2 . |
这个rec-List 表达式将condiments 中的每个:: 都替换为了add1 , 而nil 则被替换为了0 .方框里填什么名字好呢? | 34 | length 似乎比较恰当.
|
那么以下必然是length 的定义了.
| 35 | 但是 的长度是多少? |
那简单, 只需将Atom 替换以Nat .
| 36 | 而以下是对于Nat 列表而言的length .
|
列表可以包含任意类型的元素, 不仅是Atom 和Nat .什么可以用来作成一个对于所有类型成立的 | 37 | It's as easy as Π .
译注. 这里是显然的一语双关. |
这个claim 需要一个step.
| 38 | 每个步骤 (step) 时, 长度通过add1 增长.
|
这个定义使用了和第3章第66框中的step-* 相同的技巧, 以将step-length 应用于E .现在定义 | 39 | 将E 传递给step-length 将导致其(结果)取三个参数.
|
为什么step-length 中的e 是黯淡的? | 40 | 因为列表中的特定元素在计算列表长度时无需使用. |
(length Atom) 的值是什么? | 41 | 其为 这是将内层λ 表达式的体中的每个E 替换以Atom 得到的. |
请定义length 的一个特化版本, 其找出一个(List Atom) 的元素数目. | 42 | 这会使用和第4章第54框中的twin-Atom 定义相同的技巧.
|
这是一个很实用的技巧. 现在是时候将一片面包, 请定义一个函数, 其合并两个列表. | 43 | 这个定义的类型应该是什么呢? |
我们可以将一个(List Nat) 和一个(List (Pair Nat Nat)) 合并吗? | 44 | 不能. 一个列表中的所有元素都必须具有相同的类型. |
|
只要两个列表包含相同的元素类型, 它们就可以被合并, 不论这个类型是什么. 这是否暗示了 | 45 | 其类型必然是一个Π 表达式.
|
的确如此. 剩下来的参数应该是什么呢? | 46 | 这里必须有两个(List E) 参数. 而且, 结果应该也是一个(List E) . 据此, append 必然是一个λ 表达式. |
以下是claim, 现在请开始定义.
| 47 | 它应该是一个λ 表达式, 但是其体仍然成谜.
|
方框里应该是什么呢? | 48 | 应该是某种rec-List . |
的值是什么? | 49 | 显然应该是
|
的规范形式又应该是什么呢? | 50 | 那必然是
|
(append E nil end) 的值应该是end 的值. 因此, append 的最后一个参数end 应该是base.译注. (append E nil end) 中的E 和end 是元变量. | 51 | step呢? |
step的类型由rec-List 之律确定. 其应该能够对于任意的元素类型成立. | 52 | 比如说这个?
|
以之前的框为例, 填充step-append 的剩余部分.
注记. 当列表包含的元素类型为 E 时, 表达式(step-append E) 应该是append 的一个step. 请注意Currying. | 53 | 如果appendes 是 那么step-append 应该产生 如果appendes 是 那么step-append 应该产生 最后, 如果appendes 是 那么step-append 应该产生
译注. 这里的第一段我不是很理解, 因为你并不会在运行 append 过程中把参数end 拆开. |
这是很好的推理. 所以正确的定义是什么? | 54 | 现在append 就不应该用虚线框住了.
|
append 的定义很像+ . | 55 | 是不是有一个iter-List , 就像是iter-Nat , 而我们可以用它来定义append ?译注. 我想是不太行的, 因为按照类比, iter-List 的step不能知道参数e 会是什么. |
没有什么能够阻止我们定义iter-List , 但是又没有必要, 因为iter-List 能做的, rec-List 也能做, 就像iter-Nat 和which-Nat 能做的, rec-Nat 也能做. | 56 | 好的, 这里我们就用更富表达力的消去子吧. |
实际上还可以用另外的方式定义append , 其将:: 替换成别的什么东西. | 57 | 真的能行吗? |
当然可以. 除了使用:: 以将第一个列表的元素cons到结果的开头, 也可以使用 snoc 将第二个列表的元素加到结果的末尾.例如, 的值为
注记. 感谢David C. Dickson (1947-). | 58 | snoc 的类型是 step必须要做什么呢? |
这个step必须要将列表的当前元素cons到结果上. | 59 | 啊, 所以说这就像step-append . |
现在请定义snoc . | 60 | 以下是snoc .
译注. 说白了, 其实就是复用 append . |
干得好. 现在请定义
concat 的类型和append 的类型相同, 因为它们做相同的事情. | 61 | 除了使用snoc 而不是List 的cons :: , concat 必须要消去的是其第二个(作为参数的)列表.
译注. 不幸的是, concat 的定义是错误的, 它实际上相当于第一个列表和反转了的第二个列表合并. 没有什么特别好的补救措施, 因为snoc 和concat 本身对于List 就不那么自然. 尽管如此, 你的确可以使用之后的reverse 来反转end 以得到一个更加别扭但是还算正确的定义. |
一个列表也可以通过使用snoc 得以反转.
| 62 | reverse 接受单独一个列表作为参数.
|
每一步骤 (step) 该做什么呢? | 63 | 对于每个步骤, e 应该通过snoc 添加到反转了的es 的末尾.
|
现在请定义step-reverse 和reverse . | 64 | 以下就是了.
注记. 在使用Pie语言时, 必须要将这里的 nil 写成(the (List E) nil) 才行. |
现在是时候来点lækkert的东西了.
kartoffelmad 的规范形式是什么? | 65 | 即
译注. 原文将 condiments 和toppings 的顺序弄反了, 翻译是按照经过勘误的版本来的. |
我们问的是规范形式, 而非值, 这是很好的. 否则的话, 你原本可能就要在吃的时候自己组转除了'chives 的一切了. | 66 | 反转列表是令人发饿的工作. |
... | 1 | 在吃了那么多三明治之后, 吃点Π 也是好的. |
我们很高兴你问了... | 2 | 我很擅长预测你想要我提出的问题. |
当然了, 让我们开始吧. 让我们定义一个函数 | 3 | 那不是很简单吗? |
实际上, 这是不可能哒! | 4 | 为什么不可能? |
之所以不可能, 是因为nil 没有第一个元素... | 5 | ...因而first 不是完全的. |
写一个last 函数怎么样, 它不是找出第一个元素, 而是找出一个List 的最后一个元素? | 6 | 函数last 也不是完全的, 因为nil 也没有最后一个元素. |
为了能够写下一个完全函数first , 我们必须使用一个比List 更加特化 (specific) 的类型. 这样一个更加特化的类型被称为Vec , 其是向量 (vector)的缩写, 但是它真的只是带有长度的列表而已. 当是一个类型而是一个
注记. (Vec ) 读作长度为的的列表或者更简单的 的列表, 长度为. | 7 | 类型可以包含不是类型的表达式吗? |
正如类型可以是对于某个表达式求值的结果一样 (见第1章第55框), 某些类型可以包含其他并非类型的表达式. 译注. 意味不明的类比. | 8 | 那么, 之所以(Vec Atom 3) 是一个类型, 是因为Atom 是一个类型而3 显然是一个Nat . |
是一个类型吗? | 9 | 必然是的, 因为 和 是相同的类型, 且 和 是相同的Nat . 这意味着该表达式和 是相同的, 而其 (指后者) 显然是一个类型. |
(Vec zero) 唯一的构造子是vecnil . | 10 | 这是因为vecnil 的长度为zero 吗? |
恰是如此.
唯一的构造子. | 11 | 这里的是什么? |
这里, 可以是任意的Nat .当是一个而是一个 | 12 | 如果一个表达式是一个(Vec (add1 )) 那么其值至少拥有一个元素, 因而有可能定义first 和last , 是不是这样呢? |
对的. 是一个 吗? | 13 | 是的, 因为 是一个 而 是一个
|
|
|
|
是一个 吗? | 14 | 不是, 因为其并非恰有三个原子的列表. |
这和第6章第12框是怎样联系起来的呢? 译注. 原文是第11框, 但是译者认为第12框更合理一点. | 15 | 之所以其并非 是因为 不是一个
|
为什么 不是一个
| 16 | 如果它真的是一个(Vec Atom 2) 的话, 那么基于第6章第12框中的描述 就会是一个
|
为什么不能是这样呢? | 17 | 因为 是一个 而1 和zero 不是相同的Nat . |
为什么1 和zero 不是相同的Nat 呢? | 18 | 根据第1章第100框的解释, 两个Nat 相同, 当其值相同; 而其值相同, 当其均为zero 或者均以add1 为顶(且add1 的参数是相同的Nat ).译注. 括号里的内容是译者添加的. 另外, 不仅是第100框, 也有第101框的内容. |
现在可以定义first-of-one 了, 其获取一个(Vec 1) 的第一个元素. | 19 | 但是这可能吗? 到目前为止, 我们还没有Vec 的消去子. |
很好的论点. Vec 的两个消去子是head 和tail . | 20 | head 和tail 是什么意思呢? |
当 是一个 时, 是一个 的值可以具有怎样的形式? | 21 | 它不可能是vecnil , 因为vecnil 只有zero 个元素. 因此, 以vec:: 为顶. |
表达式 和是相同的. | 22 | tail 怎么样呢? |
当 是一个 时, 是一个
| 23 | 以vec:: 为顶. 和 是相同的 吗? |
不是, 但 和 是相同的 现在定义first-of-one . | 24 | first-of-one 使用head 来找出这仅有的元素.
|
的值是什么? | 25 | 是'shiitake . |
的值是什么? | 26 | 这个问题是没有意义的, 因为 并不由某个类型刻画, 而这又是因为 并非一个
|
完全正确, 这的确是一个毫无意义的问题. 现在请定义 | 27 | 那必然非常类似于first-of-one .
|
的值是什么? | 28 | 这个列表上的都是蘑菇珍品. 然而, 问题本身并不意义, 因为这个蘑菇珍品的列表放了三个蘑菇, 而不是恰有两个蘑菇. |
很好的论点. 是时候定义 | 29 | 存在first 对于任意长度成立吗? |
不行, 因为长度为zero 时并不存在first 的元素. 但是, 可以定义first , 使其找出任意至少拥有一个元素的列表的第一个元素. | 30 | 听起来有点困难. |
实际上, 并不那么困难. 事实上, 简单如... | 31 | ...Π ? |
Π 比我们所见更加灵活. | 32 | 什么是更加灵活的Π 呢? |
一人食的蘑菇派. (A mushroom pot pie, for one.) | 33 | 什么是更加灵活的Π 表达式呢? |
以下是first 的声明. 这里有什么新奇之处吗? | 34 | 参数名 后面跟着的, 是Nat . 而之前, Π 表达式里参数名后面跟着的总是. 中的E 指的是不论什么作为first 的第一个参数的. 是不是这意味着(add1 ) 中的 指的是不论什么作为first 的第二个参数的Nat 呢? |
|
完全正确. (add1 ) 保证了作为first 的第三个参数的列表至少拥有一个元素.现在请定义 | 35 | 以下就是了.
|
的值是什么? | 36 | 是'chicken-of-the-woods .但是, 为什么元素的数目是 而非仅仅是 呢? |
vecnil 中不能找到第一个元素, 因其只有zero 个元素.不论 | 37 | 我们通过使用更加特化的类型以排除不想要的(实际)参数, 这避免了试图定义一个并非完全的函数. |
|
相同的定义本可写成两个嵌套的Π 表达式的形式. 为什么这会是相同的定义? | 38 | 答案是, 因为具有多个参数名的Π 表达式不过就是嵌套的单参数名Π 表达式的简略写法而已. |
实际上, 这个定义本也可以写成三个嵌套的Π 表达式的形式. 为什么这也是相同的定义? | 39 | 真的是相同的定义吗? 前一个定义有 |
实际上, → 是当参数名不在Π 表达式的体中出现时的对于Π 表达式的一种简略写法. | 40 | 啊, 好吧. |
|
|
|
|
|
类型 本可以写成 因为es 在E 中没有被用到.实际上, 我们本也可以将 | 41 | 之前最后一个版本的first 也可以写成这样: 这是因为嵌套的Π 表达式本就可以写成单独一个Π 表达式的形式. |
更加特化的类型使得我们能够定义first , 这是我们自己的类型化版本的head .若是要定义 | 42 | 当然如此, 因为(tail vecnil) 和(head vecnil) 同样地毫无意义. |
这更特化的类型是什么呢? | 43 | 参数必然以vec:: 为顶.因为
|
head 和tail 都是函数, 而所有函数都是完全的. 这意味着它们不可能与List 一起使用, 因为List 无法排除nil .现在请定义 | 44 | 以下就是了.
|
我们的蘑菇派需要少许豌豆搭配. 是时候定义peas 了, 其产生所需数目的豌豆.什么样的类型表达了这种行为呢? | 1 | 类型是 因为peas 能够产生任意数目的豌豆. |
peas 到底应该产生多少豌豆呢? | 2 | 看情况咯. (It depends.) |
依赖于什么呢? (What does it depend on?) | 3 | 其依赖于豌豆所需要的数目, 即参数. |
第7章第1框中的类型 不够特化. 它没有表达出peas 精确地产生了其被索取的豌豆数目. | 4 | 豌豆的数目是Nat 参数. 以下的类型有用吗?
|
是的, 这个类型表达了作为peas 的参数的豌豆数目依赖于其被索取的数目. 这样的类型被称为依赖类型(dependent type).
| 5 | 当然了.
|
|
这peas 的定义并非表达式. 为了能够使用rec-Nat , base和step的参数peas 必须具有相同的类型. 然而, 这里的peas 可以是一个(Vec Atom 29) , 但是vecnil 是一个(Vec Atom 0) .换言之, 当类型依赖于作为target的 | 6 | iter-Nat 如何呢? |
rec-Nat 可以做任何iter-Nat 可以做的事情. | 7 | 有什么更强大的东西可用吗? |
那被称为ind-Nat , 这是induction on 的缩写. | 8 | 什么是ind-Nat ? |
ind-Nat 和rec-Nat 很像, 除了其允许base和step中几乎是答案的参数 (这里是peas ) 的类型包括作为target的Nat .换言之, | 9 | 这里有一个被称为how-many-peas 的Nat 包含在类型 里, 它是一个依赖类型吗? |
是的, 它依赖于Nat how-many-peas .为了与依赖类型打交道, | 10 | 这个额外的参数长什么样呢? |
这个额外的参数, 被称为动机(motive), 可以是任意的 一个ind-Nat 表达式的类型是动机应用于作为target的Nat 的结果.注记. 感谢Conor McBride (1973-). | 11 | 所以说动机是一个函数, 其体是一个. |
的确如此. 动机解释了为什么target要被消去.
| 12 | 这是个好问题. 不过, 至少其类型是清晰的.
注记. mot读作 moat. |
|
以下就是mot-peas 了.
(mot-peas zero) 的值是什么? | 13 | 它应该是一个, 因而也是类型, 即
|
peas 的base必然具有什么类型呢? | 14 | 当然其类型必然为 因为base的值是当zero 为target时的值. |
peas 的base应该是什么呢? | 15 | 其必然是vecnil , 因为vecnil 是仅有的
|
这个(类型)也是(mot-peas zero) .
| 16 | 在rec-Nat 里, step的参数是n-1 和几乎是答案的东西, 其为消去n-1 得到的值.给定 |
ind-Nat 里的step的参数也是n-1 和几乎是答案的东西.那么, 几乎是答案的东西的类型是什么? | 17 | 几乎是答案的东西的类型是动机应用于n-1 的结果, 因为几乎是答案的东西是target为n-1 时的值. |
对于target (add1 n-1) 而言, 值的类型是什么? | 18 | 一个ind-Nat 表达式的类型动机应用于target的结果. |
如果动机是, 那么step的类型为
| 19 | 举一个ind-Nat 的step的例子呢? |
以下是peas 的step.
| 20 | 为什么mot-peas 在step-peas 的类型里出现了两次? |
21 |
22 |
1 |
2 |