欢迎回来! | 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是法语的苹果. |
是一个 这显然吗?注记. 当准备好的时候, 读课间: 一叉子Pie看 定型 (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 | 这意味着
|
Atom 之律
|
| 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 .这两个表达式具有相同的
|
cons 之第一诫两个 |
很好. 的规范形式是什么呢? | 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 | 这是一种看待求值的新方式. 为什么规范形式和值之间要做区分呢? |
每个东西都是一个表达式 在Pie中, 值也是表达式. Pie中的求值是寻找一个表达式, 而不是其他别的什么东西. |
规范表达式没有可供求值的剩余机会了. 通常而言, 规范的表达式更容易理解. 然而, 往往找到一个值就足够了, 因为顶层的构造子可以用来判断接下来必然发生的事情. | 97 | 如果找到一个值经常就足够了的话, 难道说这意味着我们可以自由地去寻找值, 并且然后可以想停就停呢? |
是这样的, 只要关于构造子的参数的信息从没有用到即可. 和four 是相同的Nat 吗? | 98 | 这里给出一个可能的回答. 它们不是相同的 是一个值, 而且它当然长得不像变量four . 找到four 的值也无济于事, 因为four 的值看起来非常不同. |
算是好的尝试. 但是, 实际上它们是相同的 | 99 | 怎么能这样呢? |
两个不是值的Nat 表达式相同, 如果它们的值相同. 恰存在两种Nat 值可以相同的方式: 每种构造子一个.如果两个都是 | 100 | 那么两个值都以add1 为顶的情况呢? |
zero 之诫
|
如果每个add1 的参数是相同的Nat , 那么两个add1 表达式是相同的Nat 值.为什么 和 是相同的
| 101 | 这两个表达式都是值. 这两个值都以add1 为顶, 因此它们的参数应该是相同的Nat .这两个参数都是 |
add1 之诫如果和是相同的 |
为什么 和 是相同的
| 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 | 和 是相同的 吗? |
应用之始律 如果是一个 而是一个, 那么 是一个. |
λ 始第一诫两个接受 (expect) 相同数目的参数的 |
λ 始第二诫如果是一个 那么和 是相同的 只要不出现在中.译注. 这里的 出现应该指的是 自由出现. |
不, 并非如此, 因为对于第二个λ 表达式中的变量进行一致换名以匹配第一个λ 表达式的参数将会产生 而(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 后面有写着 呢? |
define 之律和诫遵照
是一个 那么是一个 并且和是相同的 |
(Pair Atom Atom) 描述了我们可以怎样使用vegetables . 例如, 我们知道(car vegetables) 是一个Atom , 而(cons 'onion vegetables) 是一个
注记. 这对于小扁豆汤 (lentil soup) 来说也是一个好的开始. | 36 | 啊, 懂了. |
和 是相同的 吗? | 37 | 的确如此, 因为每个表达式的值都是一个序对, 并且其car 是'celery 而cdr 是'carrot . |
实际上, 每当是一个(Pair Atom Atom) 时, 那么和 是相同的 找出(car ) 和(cdr ) 的值是没有必要的. | 38 | 这看起来很合理. |
cons 之第二诫如果是一个 |
以下定义可以允许吗?
| 39 | 什么鬼? |
尽管可能是个愚蠢的想法, 但它是可以被允许的. 的规范形式是什么? | 40 | 那必然是10 , 因为五 (five) 加等于十. |
再想想. 请记得five 的奇怪定义... | 41 | ...哦, 好吧, 那应该是14 , 因为five 被定义成了9 . |
的确如此. | 42 | 这个定义可以允许吗? 似乎它看上去不那么蠢.
|
虽然和将five 定义为9 相比没那么愚蠢, 但是这个定义也是不被允许的.已经被使用了的名字, 不论是用于构造子, 消去子, 还是之前的定义, 都不适合再与 | 43 | 好. |
定义中的名字 在Pie语言中, 只有没有用过的名字, 才能和 |
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 为顶. 的规范形式为
|
which-Nat 之律如果是一个 那么 是一个. |
which-Nat 之第一诫如果 是一个, 那么其和是相同的. |
which-Nat 之第二诫如果 是一个, 那么其和( ) 是相同的. |
的规范形式是什么? | 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的反馈里, 也可以出现在我们写下来的表达式里. |
the 之律如果是一个类型而是一个, 那么 是一个. |
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, 抱子甘蓝. |
the 之诫如果是一个类型而是一个, 那么 和是相同的. |
值是很小一点的'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 | 存在并非完全的函数吗? |
完全函数 为每个可能的参数都赋有一个值的函数被称为完全函数(total function). |
至少这里没有. 在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 会被代之以. |
iter-Nat 之律如果是一个 那么 是一个. |
iter-Nat 之第一诫如果 是一个, 那么它和是相同的. |
iter-Nat 之第二诫如果 是一个, 那么它和 是相同的. |
是的. 的规范形式是什么? | 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也有参与.
|
rec-Nat 之律如果是一个 那么 是一个. |
rec-Nat 之第一诫如果 是一个, 那么其和是相同的. |
rec-Nat 之第二诫如果 是一个, 那么其和 是相同的. |
当然有了, 比如说使得(+ 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 指的是什么? |
应用之中律 如果是一个 而是一个, 那么 是一个, 其中每个都已被一致地替换为了.译注. 原文的实际上使用的是无衬线字体, 即 Y . 但是, 译者认为应该是一个代表句法上的变量的元变量. |
体里的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 ) 的意思是什么呢? |
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 是最小的自然数.列表的消去子和 |
nil 之律
|
:: 之律如果是一个而是一个 |
是的. 的类型是什么? | 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)! |
rec-List 之律如果是一个 那么 是一个. |
rec-List 之第一诫如果 是一个, 那么它和是相同的. |
rec-List 之第二诫如果 是一个, 那么它和 是相同的. |
的确美味! 的值是什么? | 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 | 是的, 因为 是一个 而 是一个
|
Vec 之律如果是一个类型而是一个 |
vecnil 之律
|
vec:: 之律如果是一个而是一个 |
是一个 吗? | 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 | 当然了.
|
依赖类型 由某个不是类型的东西所确定的类型被称为依赖类型(dependent type). |
这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. |
对于依赖类型应使用 ind-Nat 当 |
以下就是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 | 是(Vec Atom ) . |
ind-Nat 之律如果是一个 是一个( zero) , 而是一个 那么 是一个( ) .译注. 这里的变量 n-1 其实可以是任意的, 并且从理论上来说里不应该存在n-1 的自由出现. |
ind-Nat 之第一诫
和是相同的( zero) . |
ind-Nat 之第二诫
和 是相同的( (add1 )) . |
这是peas 的类型, 其描述了包含 个豌豆的列表. 的值如何, 其又意味着什么? | 22 | 其是 其描述了包含 个豌豆的列表. |
自然数上的归纳 通过给出零时的值以及将时的值转换为时的值的方法来构造对于任意自然数的值被称为自然数上的归纳. |
step必须要能够根据对于 的值构造出对于(add1 ) 的值.再次观察 | 23 | 不论 是什么Nat , step-peas 总是接受一个 然后产生一个 这是通过cons一个 'pea 到前端完成的. |
base将 替换以 因为 是仅有的
step-peas 将一个add1 替换成什么呢? | 24 | step-peas 将每个add1 替换以一个vec:: , 就像第5章第34框中的length 将一个列表中的每个:: 替换以add1 . |
现在定义peas 是可能的了, 只需使用mot-peas 和step-peas . | 25 | 以下是我们的定义.
|
(peas 2) 的值是什么?以下是最初的三个计算步骤.
译注. 不知道为什么, 原文说是两个步骤. | 26 | 以下就是了.
|
如果动机的参数是黯淡的, 那么说明ind-Nat 表现得就像是rec-Nat . 现在请定义一个函数also-rec-Nat , 其使用ind-Nat , 而行为正如rec-Nat .
| 27 | 因为类型并不依赖于target, 所以k 是黯淡的.
|
就像first 可以找出一个列表的第一个元素, last 可以找出最后一个元素.
| 28 | 此列表必然是非空的, 这意味着我们可以应用和first 的类型相同的想法.
|
如果一个列表只包含一个Atom , 那么哪个Atom 是最后一个呢? | 29 | 显然只有一种可能. |
的规范形式是什么? | 30 | 以下是我的猜测. 这个问题没有意义, 因为列表包含的是一个元素而不是零个元素. |
(last Atom zero) 的类型是什么?请记得Currying. | 31 | (last Atom zero) 的类型为 因此, 前一个框中的问题, 实际上是有意义的! |
的规范形式是什么? | 32 | 那必然是'flour . |
的确如此. 使用这个洞察, | 33 | base在(作为target的)Nat 为zero 时使用.
|
base-last 的定义是什么? | 34 | 其使用head 以获得一个(Vec E (add1 zero)) 中的唯一元素.
译注. 原文是 (Vec Atom (add1 zero)) , 这可能是一个笔误. |
这是我们第一次遇到base是一个函数的情况. 根据动机, base和step的几乎是答案的参数都是函数. 当base是一个函数而step将一个几乎是答案的函数转换为另一个函数时, 整个 译注. 虽然实际的动机还没有给出, 但是读者应该料想得到. | 35 | λ 表达式是值吗? |
是的, 因为λ 是一个构造子. | 36 | 函数的确是值. |
ind-Nat 表达式的类型是动机应用于target的结果, 这个target是要被消去的Nat .当抵达base时, 作为target的 | 37 | 应该是zero , 这就是base的意义所在. |
动机应用于zero 的结果是base的类型.请找出一个可以用作动机的表达式. | 38 | 怎么样呢? 将填上列表元素的类型而填上zero 就得到了base的类型.译注. 这里使用的元变量和实际上是不必要的, 或者说可以算是一种误用. |
ind-Nat 的base的类型在 |
很接近了, 但并不那么正确.
| 39 | 啊, 所以说那必然是 其可以被应用于列表元素的类型和zero 以得到base的类型. |
现在定义last 的动机.
| 40 | 以下就是了.
|
的类型和值分别是什么? | 41 | 类型是 而值为
|
这就像什么? | 42 | 第4章第54框里的twin-Atom . 应用mot-last 于一个将产生一个适合用于ind-Nat 的动机. |
此时base的类型的值是什么? 这个类型即(mot-last Atom zero) | 43 | 应该是类型
|
的值是什么? | 44 | 应该是
|
last 的step的目的何在? | 45 | last 的step将 时的几乎答案转换为对于(add1 ) 的答案.换言之, 中的最后一个元素的函数变为一个获取一个 中的最后一个元素的函数. 为什么这里有两个add1 ? |
外层的add1 作为类型的一部分是为了保证送给last 的列表至少拥有一个元素. 内层的add1 来源于将(add1 ) 传递给mot-last . | 46 | 外层的add1 使得函数完全, 而内层的add1 是出于ind-Nat 之律. |
step的类型是什么? | 47 | step的类型必然是 因为step必须要根据一个 构造出一个
|
这个类型如何以文字解释? | 48 | step将一个对于 而言的 函数转换为一个对于 而言的 函数.译注. 原文分别是 和(add1 ) , 但是译者自作主张改成了以上的形式. |
ind-Nat 的step的类型在
|
以下是step-last 的声明. 现在请定义step-last . | 49 | last 是几乎正确的函数, 但是只是对于拥有(add1 ) 个元素的列表而言的, 因而其接受拥有(add1 (add1 )) 个元素的列表的tail 作为参数.
译注. 原文有误, 已修正. |
内层的λ 表达式的参数es 的类型是什么? | 50 | es 是一个
|
为什么这是es 的类型呢? | 51 | 整个这内层的λ 表达式的类型为 而这个类型和 是相同的类型. 因此, 该λ 表达式的参数, 即es , 应该是一个
|
聪明.
| 52 | (tail es) 的类型为 其是几乎准备好的函数的适切参数的类型.译注. 这个函数即 last . |
第7章第49框中的较外层λ 表达式里的last 的类型是什么? | 53 | last 的类型为 即(mot-last E ) 之值. |
现在是时候定义last 了, 其claim 出现在第7章第28框之中. | 54 | 以下就是了.
|
的规范形式是什么?以下是我们计算的开始.
| 55 | 感谢帮助. 以下是更多的计算.
译注. 原文漏了一个编号. |
这是规范形式吗? | 56 | 并非如此, 还需要更多的步骤.
|
漂亮. 现在休息一下, 或许可以来点养生的蘑菇派. | 57 | 听起来很不错的样子. |
猜猜drop-last 的意思是什么. | 58 | 想必是丢掉一个Vec 里的最后一个元素. |
猜得不错!
| 59 | 它不由某个类型描述, 正如
也不由类型描述一样.这个类型必然包含一个其中带有 译注. 这个意即 . |
这是坚实的思考方式.
| 60 | drop-last 使得列表的长度收缩了一.
|
base-drop-last 是什么? | 61 | base应该找出拥有一个元素的列表的 也就是 因为最后一个元素就是那唯一的元素.
|
以下对于base-drop-last 的定义也能成立吗?
| 62 | 这个定义总是能产生相同的值, 但是没能同样清晰地传达想法. 我们的意图在于 |
听起来很对. 不过为什么上面的定义要用虚线框住呢? | 63 | 只是得到正确的答案是没有价值的, 如果我们不知道为什么正确的话. 理解答案至少和拥有正确答案同等重要. |
可读的表达式 只是得到正确的答案是没有价值的, 如果我们不知道为什么正确的话. 理解答案至少和拥有正确答案同等重要. |
看来某人一直在认真听讲!
| 64 | mot-drop-last 需要表达drop-last 总是在构造长度小一的Vec .
|
太快了, 请解释一下. | 65 | 在ind-Nat 之中, 应用动机于zero 得到的就是base的类型. 这意味着我们可以反过来将第7章第61框中base的类型里的zero 替换成参数k 以mot-drop-last .译注. mot-drop-last 这里是一个动词. |
这是一个敏锐的观察. 这种方法并不总是成立, 但的确是一个好的起点. 将特定的常量替换以变量并用该变量的 | 66 | step-drop-last 的类型遵循ind-Nat 之律.
|
那么step-drop-last 该如何定义? | 67 | 想出step-drop-last 需要动动脑子.
|
这是令人熟悉的归纳模式: 将一个对于 成立的 转换为一个对于 成立的 这个转换是如何工作的呢? | 68 | 正如 使用其几乎是答案的参数, 即last , 以找出它自己的(tail es) 的last , 也使用其几乎是答案的参数, 即drop-last , 以找出它自己的(tail es) 的drop-last .根据 上去.译注. 原文有误, 已修正. |
drop-last 的claim 出现在第7章第60框里.现在请定义 | 69 | 这个事情只是把我们已经得到的碎片拼接起来.
|
是的, drop-last 现在已得到定义.有时找出之后会被用到的函数是方便的. 例如, 请找出 之值以阐明这是如何成立的. | 70 | 以下是找出其值的图表.
|
很好——λ 表达式的确也是值. 为了找出规范形式, 则需要更多的步骤. 以下则是第一步.
| 71 | 在第6步里, es 被一致地换名为了ys , 这是为了明确这内层的λ 表达式有着自己的变量.
|
将es 换名为ys 其实当然是没有必要的, 因为变量名总是由包裹它的最内层λ 所绑定, 不过使得表达式更容易理解总归是一个想法.以下是新的两步.
| 72 | 几乎就要到终点了.
|
C'est magnifique! 想必你已经累了. | 73 | 的确如此, 而且也很饿. |
有时该怎么写下Pie表达式并不是显而易见的. | 1 | 那就是空白方框的作用, 是吧? |
的确如此. 然而, 绝大多数键盘都不容易输入空白方框. 与其输入空白方框, 不如使用 | 2 | 什么是TODO ? |
TODO 是一个表达式, 它是代表某个其他表达式的占位符. 一个TODO 可以具有任意的类型, 而Pie会追踪哪个TODO 应该具有哪个类型. | 3 | TODO 可以怎么用呢? |
每个TODO 都来自于某个特定的位置. 这里, 我们以框号引用它们. 在本书之外使用Pie时, 则有其他合适的手段.试着输入 看看会发生什么. | 4 | Pie会回之以 而其所提及的TODO 的确是出现在第4框的表达式的第2行第3列位置上的一个. |
现在试试 其更接近于第7章里的peas 的类型. | 5 | Pie会回之以 但是这个水平线是什么意思呢? |
当Pie回之以TODO 所被期望具有的类型时, 它也会包括可以在TODO 的位置上使用的变量的类型. | 6 | 水平线上面的n : Nat 的意思是变量n 是一个Nat . |
是这样的. 现在试试 其中的TODO 出现在定义的位置. | 7 | Pie会回之以 这也就是被claim 的那个类型. |
当TODO 被λ 包裹时, Pie会如何回应呢?
| 8 | 水平线上会有和n 相关的一行. |
试试看看. | 9 | 以下是发生的事情.
|
然后该怎么写呢? | 10 | 因为'pea 的数量依赖于n , 所以需要ind-Nat . |
Pie对于以下版本的peas 会作何回应呢?
| 11 | 每一个TODO 都具有ind-Nat 所期望的类型.
|
那么该将这里的两个TODO 替换成什么呢? | 12 | 第一个TODO 是一个(Vec Atom 0) , 故vecnil 是合适的. 第二个TODO 应该是一个二参数的函数, 以λ 构造, 应该使用vec:: 来给n-1 个豌豆添加一个'pea . |
很好的选择, 那么Pie对于以下版本该作何回应?
| 13 | vec:: 之律确定了每个TODO 的类型.
译注. 这里是反用其律. |
现在请将最后的两个TODO 替换. | 14 | 以下即是最终的定义.
|
蘑菇派怎么样? | 1 | 很好吃, 就是有点胀人. 这里有什么不那么胀人的吃的吗? |
怎么样呢? | 2 | 那应该还是可控的. |
(+ 1) 的规范形式是什么? | 3 | 让我们来找出它吧. |
现在开始.
| 4 | 规范形式需要一点更多的工作.
|
以下是一个定义.
(incr 0) 的规范形式是什么? | 5 | 只需三个步骤.
1 , 即(add1 zero) . |
(incr 3) 的规范形式是什么? | 6 | 计算该规范形式需要更多的步骤. 以下是为了找出其值的最初几步.
|
这的确是值. 但是规范形式又是什么呢? 再走几步.
| 7 | 规范形式是4 .
|
和 之间有什么关系呢? | 8 | 它们总能得到相同的答案, 不论参数为何. |
这意味着(+ 1) 和incr 是相同的 咯? | 9 | 它们是相同的, 如果它们有着相同的规范形式.
而incr 的规范形式为 因此它们并非相同. |
是这样的. 即便它们并非相同, 它们总是能够得到相同答案的事实其实可以写成一个类型. | 10 | 但是相同性难道不是判断的一种嘛? 这也不是类型啊. |
相同性的确是一种判断. 但是, 通过一个新的类型构造子, 类型可以表达被称为相等性(equality)的新想法. 将 写成一个类型是很消耗能量的. 你最好先吃这个 使你能量满满. | 11 | 另一个三明治? 好吧. |
一个表达式 是一个类型, 如果 是一个类型, 且 是一个, 且 是一个. | 12 | 这是另一种构造依赖类型的方式吗? |
= 之律一个表达式 是一个类型, 如果是一个类型, 是一个, 而也是一个. |
是的, = 是另一种构造依赖类型的方式, 因为和无需是类型.因为和是方便的名字, 一个 | 13 | 好的. |
from和to应理解为名词 因为和是方便的名字, 一个 |
是一个类型吗? | 14 | 是的, 因为Atom 是一个类型, 而'kale 和'blackberries 都是Atom . |
是一个类型吗?注记. 感谢Alfred North Whitehead (1861-1947) 并再次感谢Bertrand Russell. 他们的三卷本Principia Mathematica (分别出版于1910, 1912, 1913年) 的第379页写道, 根据这个命题可以推出, 当算术加法被定义时,. | 15 | 是的, 因为Nat 是一个类型而(+ 1 1) 和2 都是Nat . |
是一个类型吗? | 16 | 是的, 的确如此, 因为 和Nat 是相同的类型, 而from和to都是Nat . |
是一个类型吗? | 17 | 是的, 的确如此. 第8章第12框只是要求from和to都是Nat , 但无需是相同的Nat .但是 |
为了理解= , 首先需要理解看待类型的另一种视角.类型也可以读作陈述(statement). 注记. 感谢Robert Feys (1889-1961) 和Nicolaas Govert de Bruijn (1918-2012), 并再次感谢Haskell B. Curry. 感谢William Alvin Howard (1926-). 陈述有时也被称为命题(proposition). | 18 | 怎么读作陈述呢? |
类型 可以这么读:表达式. 怎么读作陈述呢? | 19 | 怎么样?二加二等于四 |
是的, 很好. | 20 | 和三加四等于七 有什么区别? |
陈述是另一种写下类型三加四等于七 的方法, 其是一个表达式, 但是是一个关于表达式的判断. 第1章第12框描述了判断. 一个判断不是一个表达式, 而是一个人在思考表达式时所采取的态度. | 21 | 以下是一个判断:三加四等于七是一个类型. |
你的观察十分敏锐.
| 22 | 还有其他的什么类似的吗? |
一个Π 表达式可以读作对于每个 (for every). 考虑以下例子: 可以读作对于每个. 译注. 这里的 对于每个 或许应该理解为对于每个类型为 . | 23 | 好的, 但是将类型读作陈述的目的何在? |
如果一个类型可以读作一个陈述, 那么判断该陈述为真意味着存在一个具有该类型的表达式. 也就是说, 言称意味着(为真) 存在一个表达式具有类型 译注. 读者或许需要仔细体会这里的微妙用语, 例如 相等和 相同有什么区别? | 24 | 这是不是意味着真性 (truth) 需要证据? |
实际上可以走得更远. 真性意味着拥有证据. 这种证据被称为一个证明(proof). 注记. 感谢BHK: L. E. J. Brouwer (1881-1966), Arend Heyting (1898-1980), Andrey Kolmogorov (1903-1987). | 25 | 每个类型都可以读作陈述吗? |
在原则上, 的确是可以的. 但是, 许多类型作为陈述并不有趣. | 26 | 什么使得一个陈述有趣呢? |
一个人对于一个陈述感兴趣, 那么它就变得有趣了. 但是, 最有趣的陈述来源于依赖类型. Nat 不是一个有趣的陈述, 因为它太容易证明了. | 27 | Nat 怎么被证明呢? |
选一个数字, 任意的数字. | 28 | 好的.
|
干得好. 你已经有了一个证明. | 29 | 这并不十分有趣. |
是的. | 30 | 这解释了前面说的. |
另一种思考陈述的方法是将其当作对于证明的期待, 或者是当作要被解决的问题. | 31 | 见到一个claim 之后, 期待一个定义也很正常. |
第8章第12框解释了何时一个= 表达式是一个类型, 但是它没有说这样的类型的值会是什么. | 32 | 这里, 值指的是和 证明相同的东西, 对吗? |
完全正确.
| 33 | same 怎么用呢? |
如果是一个, 那么表达式 是一个
| 34 | 举个例子呢? |
same 之律如果是一个, 那么表达式 |
表达式 是一个
| 35 | 这看起来似乎不对. 在第8章第34框里,
看起来相当不同. |
和 都与21 是相同的Nat , 所以它们三个是相同的. | 36 | 是不是这意味着 是一个 呢? |
是的. 是 的一个证明.
from和to是相同的.以类型构造子 = 与其构造子same , 表达式现在可以陈述之前只能被判断的想法.注记. 创造捕获了判断形式背后想法的表达式有时被称为是在内化(internalize)判断形式. | 37 | 为什么这如此重要呢? |
表达式可以与其他表达式一起使用. 通过将
| 38 | +1=add1 的定义显然有一个位于顶层的λ , 因为其类型的顶层有一个Π .
|
这是一个坚实的开始, 但是方框里应该填什么呢? | 39 | 根据λ 之律, 是这个λ 表达式的体的类型.译注. 这里是反用其律. |
是的, 这个方框里填的应该是一个 那么, 这个方框的类型的规范形式是什么? | 40 | 方框的类型的规范形式是 因为 的规范形式为 我知道了, 所以说第8章第38框的方框里填的表达式应该是
|
正确. 现在请完成定义. | 41 | 以下就是了.
|
+1=add1 证明了什么陈述呢? | 42 | 这个陈述是对于每个. |
以下是另外一个陈述.请将其翻译为类型.对于每个. | 43 | 让我们称其为incr=add1 .
|
现在请定义incr=add1 . | 44 | 难道这不就像+1=add1 吗?
|
并非如此. 的规范形式是什么? | 45 |
(add1 n) 不是相同的Nat .译注. 原文停留在了第2步, 但是译者认为译者自作主张加上去的第3步才是规范形式. |
是这样的. 这个规范形式是中立的. 什么是中立表达式? | 46 | 中立表达式描述于第2章第25框. 中立表达式是那些尚不能被求值的表达式. 译注. 原文写的是第24框, 但是译者认为应该是第25框. |
为什么 是中立的? | 47 | 因为iter-Nat 在target为zero 时选择base, 而当target以add1 为顶时选择step, 但是n 并不属于这两种情况. |
一种更加精确的定义中立表达式的方式是从最简单的中立表达式开始, 然后由此构建其他的中立表达式. 变量是中立的, 除非变量引用定义, 因为一个被定义的名字和其定义是相同的, 见 | 48 | 好的. |
而且, 如果一个消去子表达式的target是中立的, 那么整个表达式就是中立的. | 49 | 因此, 之所以表达式 是中立的, 是因为iter-Nat 是一个消去子, 而其target n 是一个变量.每个包含变量的表达式都是中立的吗? |
中立表达式 没有被 |
并不正确. 这个λ 表达式的体包含变量x , 但是λ 表达式是值, 而非中立表达式. | 50 | 但是如果整个表达式就只是(add1 x) , 那么它就是中立的了, 因为它包含中立的x .中立表达式都是规范形式吗? 译注. 令人遗憾的是, 这里作者犯了一个明显的错误. 实际上, 因为 add1 是一个构造子, 所以(add1 x) 是一个值, 而非中立表达式. |
并不总是如此. 一些类型拥有可以将中立表达式变为值的方法, 而在这种情况下, 中立表达式不被认为是规范形式, 因为其可以被变成值. | 51 | 这样的类型有哪些呢? |
一个类型以Π 为顶的中立表达式不是规范形式. 这是因为一个中立表达式和 是相同的, 而后者是一个值. | 52 | 为什么这意味着并非规范呢? 译注. 原文本来是 x 而非, 但是译者认为更好, 因为这个λ 需要避免捕获的自由变量, 而固定的符号x 并不适合这个工作. |
一个表达式为规范形式的含义是什么? | 53 | 根据大框规范形式所言, 如果两个表达式是相同的, 那么它们有着等同的规范形式. |
根据λ 终第二诫, 和 是相同的, 但是它们写下来的形式并不等同.注记. 这样的诫常被称为规则. 若规范形式中所有可能的规则均已被应用来构造值, 那么其被称为长规范形式. | 54 | 一个包裹在λ 里, 另一个则没有. |
这两个之中至多只有一个可以是规范形式. 实际上, 包裹在λ 里的那个即是规范形式. 因为以λ 为顶的表达式是值, 这些表达式并非中立. 中立表达式不以构造子为顶.译注. 译者认为 (λ () ( )) 不一定是规范的, 因为虽然表达式是中立的, 但是其并不一定是规范的. 若并非规范, 那么(λ () ( )) 的体尚有进一步归约的可能性, 故并非规范. | 55 | 还有其他这样的类型吗? |
是的. 因为 那么和 是相同的. 出于完全相同的原因, 序对的规范形式只可能是以cons 为顶的表达式, 于是并不存在规范的中立序对. | 56 | 中立表达式来源于什么地方呢? |
正如第8章第45框里的(incr n) 的规范形式, 中立表达式经常在= 表达式提及Π 表达式的参数名时出现.译注. 若是 = 表达式的from和to都不是中立表达式, 那么该类型是否具有对象 (或者说证明) 往往是平凡的. 当然了, 并不总是平凡的, 因为可能复杂的中立表达式藏在构造子下面. | 57 | 那么我们该如何找到incr=add1 的一个定义呢? same 没法完成工作, 毕竟incr=add1 的类型里有一个中立表达式. |
判断, 例如可以使用相当简单的规则进行机械化的验证. 这就是为什么判断是知识的合适基础. 译注. 这里的 然而, 表达式可以编码相当有趣的推理模式, 例如使用归纳以对于中立表达式中的变量尝试每一种可能性.n 应该说是代表一个具体的Nat , 而不是一个任意的Nat . | 58 | 是不是这意味着归纳可以用来证明(incr n) 和(add1 n) 是相等的, 甚至尽管它们并不相同?译注. 这里的 n 仍然相当微妙, 对于相等命题而言, n 相当于全称量化于Nat 上, 而对于相同判断而言, n 是一个任意但未知的Nat . |
是这样的, 之所以使用ind-Nat , 是因为类型依赖于target.
base-incr=add1 的类型是什么?注记. 类似于 base-incr=add1 的名字应该读作 而不是. | 59 | 一个ind-Nat 表达式里的base的类型应该是动机应用于zero 的结果. (incr zero) 并非中立, 而其规范形式为(add1 zero) , 见第8章第5框, 于是其和(add1 zero) 是相同的Nat .
|
现在对于base-incr=add1 的类型里的常量zero 进行抽象以定义mot-incr=add1 . | 60 | 每个zero 都变成了k .
|
根据ind-Nat 之律, step-incr=add1 的类型是什么?当下请使用虚线框. | 61 | 应该使用mot-incr=add1 , 但为什么要加上虚线框呢?
|
在声明或者定义的最终版本还未落实之前都会加上虚线框. 尽管这个是正确的类型, 但是还可以写得更容易理解一些. | 62 | 这更容易理解的写法是什么呢? |
以下是step-incr=add1 的类型的另一种写法.
| 63 | 为什么这是相同的类型呢? |
因为 和 是相同的类型.注记. 这使用了判断的第四种形式. 的值是什么? | 64 | 其值为 这是第8章第63框中的→ 表达式里的另一个类型. |
那么第8章第63框里的step-incr=add1 的类型该如何读作陈述呢? | 65 | 很难说.
|
表达式 可以读作陈述这种解释之所以能够成立, 是因为其值是将任何的证明变换为的一个证明的完全函数.如果, 那么. | 66 | 那我试试看. step的类型是一个 |
如果和 那么作为类型 表达式 可以读作陈述如果, 那么. |
那么step-incr=add1 的类型到底该如何读作陈述呢? | 67 | "对于每个Nat , 如果那么 等于
|
和之前的陈述不同的是, 为了证明这个陈述, 我们必须要观察到一个关于incr 的事实. 的规范形式是什么? | 68 | iter-Nat 会在n-1 上卡住, 但是一个add1 会被加到顶层.
|
换言之, 也就是 和 是相同的Nat , 因为(incr n-1) 和 是相同的Nat .这就是我们所需要的观察. | 69 | 好的, 所以说step-incr=add1 的类型也可以写成以下这样. 和第8章第63框的版本不同的部分已用白纸黑字标出.
|
我们没有加上虚线框的原因在于现在这个类型很容易看出来为什么成立了. 如果两个Nat 是相等的, 那么给它们都加上一也应该是相等的.译注. 这个上下文中 成立 (make sense)的意思即存在具有该类型的表达式. | 70 | 的确如此, 但是该如何以证明来得出其为真呢? |
关于 incr 的观察不论 和 是相同的Nat . |
以下是定义step-incr=add1 的起点.
| 71 | 对于n-1 的几乎证明 (almost-proof) 是一个 白框之内可以用什么将这个几乎证明转换为一个 的证明呢?译注. 这里的 几乎证明的意思并非 还不是证明, 而是出于之前和递归子相关的命名习惯, 或许可以理解为对于最终命题的几乎证明. |
cong 是一个在这里有用的= 的消去子.注记. congruence的缩写. | 72 | cong 表达式是什么? |
First things first. 现在应该坐下来吃个
| 73 | 又是三明治? 有点吃太多了. |
回到手头上的问题来, 用于以转换相等起来的两个表达式.如果是一个 而是一个 那么 是一个
| 74 | 还有其他看待cong 的方式吗? |
以下图表展示了cong 是如何使用的. | 75 | cong 该如何用来完成step-incr=add1 的定义呢?译注. 这里的 cong 表达式里的白框可以理解为参数, 整个cong 表达式差不多就相当于(λ () (cong )) , 其中元变量代表一个不会绑定中出现的自由变量的变量. |
cong 之律如果是一个 而是一个 那么 是一个
|
在我们这里的情况下, 是Nat , 也是Nat , 而是incr=add1n-1 .那么, 和分别是什么呢? | 76 | 因为incr=add1n-1 的类型为 故是(incr n-1) 而是(add1 n-1) . |
什么函数可以将而 呢? | 77 | 在这两种情形下, 都是一个add1 加到了顶层. 使用add1 作为如何? |
add1 是一个构造子, 但是若非add1 出现在顶层而下面藏着一个Nat , 其本身并不是表达式.换言之, 一个 | 78 | 使用incr 作为如何? |
尽管incr 的确能给参数加上一, 但是在参数是中立表达式的情况下无法立即导致添加add1 . | 79 | 使用(+ 1) 作为如何? |
真是出色的选择. 现在白框里可以填上一个表达式了.
译注. 其实直接使用 (λ (x) (add1 x)) 也可以, 但是鉴于Pie的类型检查器的限制, 你需要手动加上类型. | 80 | 好的.
|
现在能够定义incr=add1 了. | 81 | 既然动机, base, step均已定义, 那么之前第8章第59框里的incr=add1 的定义就不需要加上虚框了.
译注. 这个证明的最后多说一句, 就是 step-incr=add1 的类型在书里有三种形式, 但是即便写成另外两种形式, 证明仍然是成立的, 主要原因是(incr (add1 n-1)) 和(add1 (incr n-1)) 的确是相同的Nat , 这点可由Pie的类型检查器机械地判断. |
是时候再吃一个三明治了:
| 82 | 又一个! |
是的, 再来一个. 为什么 | 83 | 因为(incr n) 的规范形式是中立表达式 (见第8章第45框), 但是(+ 1 n) 的规范形式, 根据+ 的定义, 是(add1 n) . |
中立表达式是那些尚不能被求值的表达式, 但是替换其变量为值则能允许求值. 译注. 允许求值不代表一定能求出值, 也不代表求出的值不包含无法求值的中立部分, 或许这里的意思应该理解为允许更进一步的归约. 的类型是什么? | 84 | 表达式 是一个 换言之, 它是一个 鉴于(incr 2) 并不是中立的. |
的规范形式是什么? | 85 | 以下是相同于图表的起点.
cong 表达式该如何求值呢? |
就和其他消去子一样, 对于一个cong 表达式求值的第一步是对于其target求值. 如果target是中立的, 那么整个cong 表达式就是中立的, 因此不再能继续求值. | 86 | 那么若target并非中立呢? |
如果target并非中立, 那么其值以same 为顶, 因为same 是= 表达式唯一的构造子. 的值为
| 87 | 好的, 所以找出规范形式的下一步是是找出cong 的target的值. |
既然ind-Nat 的target已经以add1 为顶了, 故下一步是使用step.
| 88 | 这接下来的ind-Nat 的target是zero .
|
cong 之诫如果是一个而是一个 那么 和 是相同的
|
判断相同性和陈述相等性之间的交互作用 (interplay) 是与依赖类型打交道的核心. 现在我们的初次尝试还仅仅是停留在表明罢了. 译注. 或许读者应该参考判断相等 (judgmental equality) 和命题相等 (propositional equality) 的概念. | 89 | 但是我的胃怎么办呢? 最多只能再吃得下去一个三明治了. |
今天是你的幸运日!
| 90 | 啊, 释然了! 这里实际上只存在一个三明治: 是对于
全都相等的一个证明.译注. 在实际生活中, 这四种三明治的确都相当类似, 为长条形三明治. |
在前一章里, = 只有一个消去子, 叫做cong .但是, | 1 | 是什么呢? |
cong 表达式的类型是什么? | 2 | 根据cong 之律, 如果是一个 而是一个 那么 是一个
|
是这样的. 不过其和诸如 | 3 | 一个ind-Nat 表达式可以具有任意的类型——完全依赖于动机. 但是, 一个cong 表达式的类型总以= 为顶. |
cong 是一个特殊目的的消去子, 但是也存在着一个更加一般性的消去子, 叫做replace . | 4 | replace 是什么意思? |
如果两个表达式是相等的, 那么对于一个为真的任何事情对于另外一个也为真. 我们称这个原则为Leibniz律. 注记. Leibniz律也指如果对于一个为真的任何事情对于另外一个也为真, 那么它们是相等的. 感谢Gottfried Wilhelm Leibniz (1646–1716). | 5 | 这和replace 有什么关系吗?译注. 至少对于译者而言, 这其实并非Leibniz律 (或者说同一者不可区分原理), 因为相等 (equality) 和同一 (identity) 是不同的概念. |
replace 比cong 更为强大, 因为任何对于cong 的使用都可以改写为对于replace 的使用, 正如任何对于which-Nat , iter-Nat , rec-Nat 的使用也可以改写为对于ind-Nat 的使用. | 6 | replace 和cong 有着怎样的不同之处呢? |
类似于cong , replace 的target也是一个 然而, 和cong 不同的是, replace 有一个动机和一个base. | 7 | 那么一个replace 表达式的类型由应用动机于target确定吗? |
那是ind-Nat 里动机的用法, 而不是replace 里动机的用法.在 这是因为它解释了如何由一个找到一个 (因而也是一个陈述). | 8 | 那base呢? |
base是( ) 为真的一个证据. 换言之, base的类型为
译注. 这里隐式地默认了动机是. | 9 | 那整个replace 表达式呢? |
整个replace 表达式是( ) 为真的证据. 换言之, 其类型为
| 10 | 因此, replace 以替换了. |
回头看看第8章第80框. | 11 | 好的, 其定义使用了cong . |
replace 之律如果是一个 是一个 而是一个 那么 是一个
|
的确如此. 不过其也可以使用 再次问问其 | 12 | 根据第8章第69框的观察, incr 里面的add1 可以提到外面来, 就好像其已为cong 作了准备.
|
以下是使用了replace 的定义的起点. target是incr=add1n-1 , 其是这里唯一可用的相等性证明. | 13 | 作为target的incr=add1n-1 是一个 整个replace 表达式应该是一个
|
为了找出动机, 需要检视replace 表达式的类型.先看看target的类型的to部分. | 14 | to是(add1 n-1) , 其当然可以在replace 表达式的类型里找到.
|
动机是用来找出base和整个replace 表达式这两个的类型的. base的类型可以通过将target的类型的from置于空白方块里得到, 而整个表达式的类型则是通过将target的类型的to置于空白方块里得到.
| 15 | 缺失了一块的表达式可以被写成是λ 表达式. |
为了得到动机, 请以λ 抽象出target的类型里的to部分. | 16 | 这将给出以下表达式: 但是, 如果replace 将from代之以to, 我们为什么要抽象出to而不是from呢? |
base的类型可以通过应用动机于target的类型的from得到. 因此, 在这种情况下, 其为
| 17 | 应用动机于参数其实就和给空白方块填上参数一样.
|
既然我们知道了base的类型, 那么base是什么呢? | 18 | base应该是 将其填入使用replace 的step-incr=add1 的定义的对应白框中则得到
|
现在该来定义动机了. | 19 | 动机以n-1 作为一个参数, 就像step-* 以j 作为一个参数.
|
最后, 请完成第9章第17框里的定义. | 20 | 鉴于step-incr=add1 在第8章中已经定义过了, 虚框仍然保持.
|
是的, 每个声明只应该有一个定义. 现在请定义
| 21 | 这是iter-Nat 足以胜任的工作. step应该是(+ 2) , 因为(+ 2) 的规范形式为
|
(double n) 是n 的两倍大. 另外一个可以找出相同答案的函数是什么呢? 我们称其为twice .
| 22 | 比如说这个?
|
的确, 恰好这个陈述该怎么写成一个类型呢?对于每个. | 23 | 因为这个陈述很可能得到一个证明, 故取个名字.
|
非常敏锐. 为什么这个声明为真呢? | 24 | 每个Nat 值要么是zero , 要么以add1 为顶. (twice zero) 和(double zero) 都是zero . |
add1 呢? | 25 | 对于add1 而言, 和 是相同的Nat , 但是 则和 是相同的Nat . |
和 是相同的Nat 吗? | 26 | 不, 并不是. 但是当然它们必须是相等的. |
的确. 为了证明 | 27 | 是的, 因为只有第一个参数是+ 的定义里的iter-Nat 的target. |
尽管 和 不是相同的Nat , 但是它们的确是相等的Nat . | 28 | 虽然它们并不相同, 但是一个可以被replace 为另外一个. |
要证明的陈述是add1+=+add1 .
| 29 | 这看起来像是ind-Nat 可以胜任的工作. 就和step-* 一样, 动机和step都需要j , 而base为
|
为什么base是 呢? | 30 | 因为 和 是相同的Nat , 而 也和 是相同的Nat . |
mot-add1+=+add1 应该是什么呢? | 31 | 其是ind-Nat 表达式的类型对于target的抽象. 换言之, add1+=+add1 的声明里的每个n 的出现都会变成k .
|
以下是step-add1+=+add1 的类型. 写下 的更显式方法是什么? | 32 | 应用mot-add1+=+add1 将给出 而这个类型和 是相同的类型, 这是因为+ 的第一个参数是iter-Nat 的target. |
现在请定义step-add1+=+add1 . | 33 | 使用cong .
|
cong 和(+ 1) 在这个定义中扮演着怎样的角色呢? | 34 | add1+=+add1n-1 是一个 因而和cong 一道使用的(+ 1) 会给from和to都包裹上add1 , 这就给出了第9章第32框中的那个类型. |
现在add1+=+add1 的定义不用加上虚框了, 因为其所提及的每个名字现在都已被定义. | 35 | 以下就是它的定义了.
|
根据第9章第35框, 以下陈述为真:对于所有的 | 36 | 是的. 这也意味着 等于 因为和可以都是n-1 . |
什么表达式具有类型 呢? | 37 | 表达式 是一个
|
现在请使用 等于 的事实来证明twice=double . | 38 | 第9章第24框的内容暗示了我们应该使用ind-Nat 表达式.
译注. 以及第25框. |
mot-twice=double 应该是什么呢? | 39 | 只需遵循通常的对于target抽象的方法.
|
那么step-twice=double 呢? | 40 | step-twice=double 的类型和其他的每个step如出一辙.
|
以下是定义的开始.
twice=doublen-1 的类型是什么? | 41 | twice=doublen-1 是一个
|
框里填的表达式的类型应该是 而这个类型和 是相同的类型. | 42 | 第9章第25框解释了为什么 和 是相同的Nat .译注. 并没有解释, 只是说明而已. 另外, 原文是第24框, 但是实际上应该是第25框. 为什么 和 是相同的Nat 呢? |
一个即时的关于+ 的观察可以派上用场. 不论Nat 和为何,
| 43 | 这非常类似于前一章的关于incr 的观察. |
关于 + 的观察不论 和 是相同的Nat . |
运用这个关于+ 的观察,
cong 可以胜任证明的工作吗? | 44 | 表达式 是一个 其并非相同的类型.译注. 说的是其和 并非相同的类型. |
虽然并非相同的类型, 但是近乎于相同的类型. | 45 | 将类型中的 替换以 则能解决问题. |
鉴于等于 replace 可以将+ 的第二个参数的add1 移到外面来. | 46 | 是的, 因为replace 适用的条件在于某个东西的类型近乎满足要求, 而不满足要求的部分又等于某个可以使得整个类型满足要求的东西. |
在这种情况下, 满足要求的部分是什么? | 47 | 白色方块之外的一切都正合适. |
现在请定义动机. 译注. replace 表达式的动机.mot-step-twice=double 需要一个额外的参数, 正如step-* .
| 48 | 白色方块的位置变成了λ 表达式(所绑定)的变量.
|
replace 表达式的target是什么呢? | 49 | 表达式 应该替换以 故target应为
|
以下是到目前为止的定义.
| 50 | base应该是类型近乎正确的表达式, 即
|
step-twice=double 的完整定义是什么呢? | 51 |
译注. 原文这里有一句废话, 译者删去了. |
最后, 我们应该把twice=double 的虚线框去掉. | 52 | 到目前为止, 每个replace 表达式的类型都以= 为顶.
|
你说得很对. 不过, 之所以replace 有用, 是因为通过编写合适的动机, 其可以具有任意的类型.请找出 的两个证明.
| 53 | 如果一个陈述对于每个Nat 都为真, 那么其也对于17 为真. 一种证明的方式为应用twice=double 于17 . 这类似于第4章第54框里的twin-Atom . |
另一个证明是什么呢? | 54 | (twice 17) 和(double 17) 已经是相同的Nat 了, 故也可以使用same .
|
实际上, (same 34) 甚至是 的值.请定义一个叫做 的规范形式为
| 55 | 其类型应该是什么呢? |
正如名字所暗示的那样, 这个函数将会构造一个具有twice 倍那么多元素的Vec .
| 56 | 听上去有点困难. |
为什么呢? | 57 | 鉴于其类型依赖于一个Nat , 这暗示了该函数应该使用ind-Nat 定义, 并且step应该使用两次vec:: .为了使用 译注. 所以说问题在于使用两次 vec:: 和长度的顶层只有一个add1 不相匹配. |
为什么长度的顶层只有一个add1 呢? | 58 | 根据关于+ 的观察, 和 是相同的Nat . |
以下是陈述问题的更直接方式.
译注. 这里的问题指 复制一个 . | 59 | 这是更容易以ind-Nat 定义的. 以下是base.
|
是这样的——doubling一个空的Vec 仍然为空. 动机应该是什么呢?
| 60 | 这可以通过对于base的类型里的zero 抽象来找出.
|
step呢?
| 61 | 这个step将具有 个元素的Vec 的doubler转换为具有(add1 ) 个元素的Vec 的doubler, 并且 和 是相同的Nat , 故使用两次vec:: 是可以预料到的.
|
double-Vec 的定义是什么呢? | 62 | 所有的部件都已齐备, 故不需要加上虚框.
|
尽管对于所有的为真, 定义使用它们的依赖类型函数并不同等简单. 定义 double-Vec 是容易的, 而定义twice-Vec 则并不那么简单. | 63 | 是这样的. |
对于所有的的证明可以用来以 double-Vec 定义twice-Vec . | 64 | 这显然可以节约不少工夫. |
先解决容易的问题 如果两个函数产生相等的结果, 那么定义依赖类型函数时使用更简单的那个, 然后使用 |
的类型为
(double ) 需要变成(twice ) . target应该填什么呢? | 65 | (twice=double ) 怎么样? |
很接近了, 但是 是一个 其to和from以错误的顺序出现. | 66 | 难道说现在我们需要证明double=twice 吗? |
幸运的是, 这并不必要. = 另有一个特别的消去子symm 可以解决这个问题.如果是一个 那么 是一个
注记. symm 是symmetry (对称)的缩写. | 67 | 好的, 现在可以定义twice-Vec 了.
译注. 虽然没有完全沿着作者的思路, 不过以 double-Vec 定义twice-Vec 还有另外一种方式.
|
写得很好. | 68 | Whew! 译注. 表达疲劳时进行放松的行为. |
symm 之律如果是一个 那么 是一个
|
symm 之诫如果是一个, 那么 和 是相同的
|
在我们开始之前, 以下是三点期望. 你有没有...
| 1 | 更多的期望了! 以下是来源于第5章第2框的所有期望, 就着三点新的期望一起. 期望在于你已经
|
似乎这两个列表并不相衬. 第5章的列表没有明显的长度, 而这里的有.
| 2 | 但是append 不可能将List 和Vec 搞混. |
的确不行. 合并Vec 是vec-append 的工作, 我们还没有定义. 为了能够在List 上使用vec-append , 我们必须将其转换为Vec . | 3 | 但是若要构造Vec 的话, 难道我们不需要元素的数目吗? |
还有另一种可能性. 之前使用 | 4 | 这个新把戏是什么呢? |
一个值为一个 是什么意思? | 5 | 一个值是一个(Pair ) , 如果
|
如果 是一个 那么的类型是, 而的类型则由一致地将中的每个替换为得到.注记. Σ 读作sigma, 也可以写成 Sigma . | 6 | 何时 是一个类型呢?译注. 这里的原文作 x , 译者修改的理由可以参见前面类似的情形, 不再赘述. |
表达式 是一个类型, 若
注记. 另一种说法是 是上的一个类型族. 这个术语也可以用在 Π 表达式的体上. 是一个类型吗? | 7 | 是的, 因为Atom 是一个类型, 并且当bread 是一个Atom 时, 是一个类型. |
什么样的表达式具有类型 呢? | 8 | 比如说(cons 'bagel (same 'bagel)) ? |
的确. 是一个类型吗? | 9 | 是一个类型, 而在A 是一个时, A 当然是一个类型. |
Σ 之律表达式 是一个类型, 如果是一个类型, 而且当是一个时, 也是一个类型. |
Σ 之诫如果是一个 那么和 是相同的. |
请说出三个具有该类型的表达式. | 10 | Nat 是一个, 4 是一个Nat , 故 是一个 另外两个具有该类型的表达式是 和
|
是一个 吗? | 11 | 是的, 的确如此, 因为将 中的food 一致地替换为'toast 将得到 因此(same (:: 'toast nil)) 是可以接受的.
|
(Pair ) 是书写 的简短方式, 其中在里没有被用到. | 12 | 这类似于第6章第40框里所说的一些Π 表达式是如何可以被写成→ 表达式的.
|
例如:
| 13 | 什么值具有这个类型? |
比如说十七个'pea : 现在请给出另一个例子. | 14 | 一顿早餐怎么样?
|
从这顿早餐开始新的一天是很不错的. 以 | 15 | (Pair ) 该如何读作陈述呢? |
一个(Pair ) 既包含了的证据, 也包含了的证据, 而以cons 为顶. 这意味着(Pair ) 可以读作因为给出一个且 (and) 且 (and)的证据在于给出其两个部分的证据. 该如何读作陈述呢? | 16 | 其为陈述这个陈述没有证据, 因为. 没有证据, 因此 car 的部分放不了任何东西.译注. 不是每个具有序对类型的表达式都以 cons 为顶, 不过都可以改写成以cons 为顶的形式. |
的证据是一个序对, 其car 是一个, 而cdr 则是由将中的每个一致地替换为car 所得到的陈述的证据. | 17 | 将Σ 读作陈述会是什么样的呢? |
Σ 可以读作例如,存在 (there exists). 可以读作存在一个原子的列表, 其等于自身的反转. | 18 | 这个陈述是真的吗? |
以下是一个证明:
| 19 | 当然了, 因为反转空表还是空表. |
还有其他证明吗? | 20 | 是的, 许多列表从前往后和从后往前是相等的. 以下是另外一个证明:
注记. 这样的列表被称为回文(palindrome). |
以下表达式该如何读作陈述呢?
| 21 | 存在一个原子的列表, 将 . |
现在请证明这个陈述. | 22 |
23 |
24 |
1 |
2 |