| 欢迎回来! | 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 | 是(Vec Atom ). |
|
|
|
这是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的类型.译注. 这里使用的元变量和实际上是不必要的, 或者说可以算是一种误用. |
|
| 很接近了, 但并不那么正确.
| 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 ), 但是译者自作主张改成了以上的形式. |
|
以下是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 |
| 1 |
| 2 |