The Little Typer 他序 本书的主题是依赖类型论, 其是数学和编程奇妙诱人且惊人有效的统一. 在类型论中, 当你证明一个定理时, 你就在编写一个满足某个描述 (specification) 的程序——甚至当你写完后还能运行! 算术基本定理的证明相当于一个分解数字的程序. 这在反方向上也能成立: 每个程序都是一个证明, 而证明的描述足够合理 (sensible) 以至于能够实现. [译注: 所谓证明的描述, 其实指的就是命题.] 类型论是黑客的天堂.
然而直到现在, 对于许多人而言, 类型论仍然是一个深奥诡秘的世界, 充斥着神圣的文献, 受人崇敬的偶像, 以及晦涩难懂的术语——就像Umberto Eco笔下的小说里与世隔绝的世界. 现在开始无需困惑不解! 我的同行Dan Friedman和David Christiansen以引人入胜的有机风格揭开了类型论的秘密, 这对于那些以代码为严格性的试金石的人们而言尤其有用. 你将学到关于规范形式, 规范化, 类型族, 依赖消去的内容, 甚至还将了解归纳法的隐秘动机.
当你读完本书时, 你对于数学和编程的了解将到达新的境地, 获得通往无疑是这两者的未来的门票. 请享受这个旅程, 而目的地是令人叹为观止的!
Robert Harper Pittsburgh February, 2018
自序 一个程序的类型描述了其行为. 依赖类型 是一个语言的一等公民, 这使得它们比其他种类的类型强大得多. 使用这样一种对待类型和程序的语言允许程序的描述就和其所描述的程序同等强大.
如果你会编写程序, 那么你就能够编写证明. 这或许令人惊讶——毕竟对于大部分人而言, 这两种活动似乎就像睡觉和骑车一样截然不同. 然而, 实际上我们从编程中所得知的工具, 例如序对, 列表, 函数, 以及递归, 也可以捕获推理的模式. 理解非嵌套列表和非负整数上的递归函数就是你理解本书所需的一切准备了. 具体来说,
第1章 愈是变化, 愈是不变 让我们为一门叫做Pie的新语言擦拭并更新一些我们的旧玩具. 以下东西是一个Atom
, 这显然吗?'atom
2 一点也不, Atom
是什么意思?
要成为一个Atom
, 就是要成为一个原子(atom) . 3 那么, 'atom
是一个Atom
是因为'atom
是一个原子.
以下东西是一个Atom
, 这显然吗?'ratatouille
4 是的, 因为'ratatouille
也是一个原子.但是, 精确地说, 什么是一个原子呢?
原子是由一个单引号 (tick mark) 后跟着一个或更多的字母或者连字符而来的. 5 因此, 那么'is-it-obvious-that-this-is-an-atom
显然是一个Atom
吗?
当然了, 因为原子可以包含连字符. 那么'---
和---
和'
如何, 它们是原子吗? 6 '---
是一个原子, 因为连字符可以出现在一个原子里的任何位置;---
不是一个原子, 因为它缺少了单引号;'
不是一个原子, 因为它的后面既没有跟着一个字母, 也没有跟着一个连字符.
'Atom
是一个Atom
吗?7 是的, 即便'Atom
也是一个Atom
, 因为它是一个单引号后面跟着一个或更多的字母或者连字符.
'at0m
是一个Atom
吗?8 不是, 因为根据第1章第5框 , 原子只能包含字母或者连字符, 而字符0
并不是一个字母, 它是数位零.
'cœurs-d-artichauts
是一个Atom
吗?9 是的, 因为œ
是一个字母.
'ἄτομον
是一个Atom
吗?10 That's Greek to me!但是希腊字母也是字母, 所以它必然是一个Atom
.
单引号之律
一个单引号后直接跟着一个或更多的字母或者连字符则为一个Atom
.
诸如'ratatouille
是一个Atom
和'cœurs-d-artichauts
是一个Atom
这样的句子被称为判断(judgment) . 11 判断的要义在于什么?
一个判断是一个人对于一个表达所采取的一个态度. 当我们得以获知什么东西时, 我们就在作出一个判断. 关于Atom
和'courgette
, 可以判断什么? 12 'courgette
是一个Atom
.
一个判断形式(form of judgment) 是一个带有空白的观察, 例如____是一个____.
13 还有其他的判断形式吗?
判断 (judgment) 的另一种形式是判断 (judgement) .14 十分有趣.
'ratatouille
和'ratatouille
是相同的Atom
吗?15 是的.它们之所以是相同的Atom
, 是因为单引号之后有着相同的字母.
'ratatouille
和'courgette
是相同的Atom
吗?16 不是.单引号之后, 它们有着不同的字母.
单引号之诫
两个表达式为相同的Atom
, 如果它们的值是单引号后跟着完全等同的字母和连字符.
第二种判断形式为____和____是相同的____.
17 因而'citron
和'citron
是相同的Atom
是一个判断.
这的确是一个判断, 而且我们有理由去相信.
'pomme
和'orange
是相同的Atom
是判断吗? 18 诚然它是判断, 但是我们没有理由去相信它. 毕竟, 我们不该比较苹果 (apple) 和橙子.
(cons 'ratatouille 'baguette)
是一个(Pair Atom Atom)
这显然吗?19 不, 完全不是.成为一个
(Pair Atom Atom)
是什么意思呢?
成为一个(Pair Atom Atom)
就是要成为一个序对, 其car
是一个Atom
, 例如'ratatouille
, 其cdr
也是一个Atom
, 例如'baguette
. 20 cons
, car
, cdr
看上去很眼熟. 不谈以前, 这里它们是什么意思呢? 它们和序对 (pair) 又有什么关系呢?
一个序对以cons
起手, 而以另外两个部分作结, 我们称其为它的car
和cdr
. 21 好吧, 这意味着之所以(cons 'ratatouille 'baguette)
是一个(Pair Atom Atom)
是因为(cons 'ratatouille 'baguette)
是一个序对, 其car
是一个Atom
, 其cdr
也是一个Atom
.那么, cons
是一个Pair
吗?
甚至cons
和Pair
在单独情况下都不是表达式, 它们都需要两个参数.
(cons 'ratatouille 'baguette)
和(cons 'ratatouille 'baguette)
是相同的(Pair Atom Atom)
吗? 22 两个表达式为相同的(Pair Atom Atom)
是什么意思呢?
这意味着它们的car
都是相同的Atom
, 它们的cdr
也都是相同的Atom
. 23 那么的确(cons 'ratatouille 'baguette)
和(cons 'ratatouille 'baguette)
是相同的(Pair Atom Atom)
(cons 'ratatouille 'baguette)
和(cons 'baguette 'ratatouille)
是相同的(Pair Atom Atom)
吗?24 (cons 'ratatouille 'baguette)
的car
是'ratatouille
而(cons 'baguette 'ratatouille)
的car
是'baguette
.因此, 我们没有理由去相信它们是相同的(Pair Atom Atom)
.
(cdr
(cons 'ratatouille 'baguette))
可以怎样描述呢?25 它是一个Atom
描述其他表达式的表达式, 例如Atom
, 被称为类型(type) .(Pair Atom Atom)
是一个类型吗?
26 是的, 因为它描述了car
和cdr
均为Atom
的序对.
第三种判断形式为____是一个类型.
27 这意味着Atom
是一个类型
和(Pair Atom Atom)
是一个类型
都是判断.
'courgette
是一个类型.
是一个判断吗?28 它的确是一个判断, 但是我们没有任何理由去相信它, 因为'courgette
并不描述其他表达式.
Atom
和Atom
是相同的类型吗?29 想必如此, 它们看上去就应该是相同的类型.
第四种判断形式, 也是最后一种, 如下____和____是相同的类型.
30 那么, 所以说Atom
和Atom
是相同的类型
是一个判断, 并且我们有理由去相信它.
判断的四种形式
____是一个____. ____和____是相同的____. ____是一个类型. ____和____是相同的类型.
以下是一个判断吗?Atom
和(Pair Atom Atom)
是相同的类型.
31 它是一个判断, 但是没有理由去相信它.
(Pair Atom Atom)
和(Pair Atom Atom)
是相同的类型吗?32 看起来相当可信啊.
判断是获知的行为, 而相信是获知的一部分. 33 判断难道不是句子吗?
句子从理解它们的人那里获得意义. 句子捕获了我们所拥有的思想, 而思想比我们用来表达思想的词语要重要得多. 34 啊, 所以说得以获知(Pair Atom Atom)
和(Pair Atom Atom)
是相同的类型这一行为是一个判断.
的确如此.'pêche
和'pêche
是相同的'fruit
吗?
35 好问题.'pêche
是一个'fruit
吗?
不, 当然不是, 因为'fruit
是一个类型
并不可信.某些形式的判断只有在早前的判断的基础之上才具备意义.
36 有哪些呢?
为了提问一个表达式是否由一个类型描述, 我们必须已经判断过给定的类型的确是一个类型. 为了提问两个表达式是否在一个类型下是相同的, 我们必须首先判断出这两个表达式都由该类型所描述.在提问两个表达式是否是相同的类型之前, 有什么判断是必要的吗? 37 为了提问两个表达式是否是相同的类型, 我们必须先要判断这两个表达式的确都是类型.
(car
(cons 'ratatouille 'baguette))
和'ratatouille
是相同的Atom
吗?38 看起来十分熟悉啊. 想必是这样, 因为car
可以找到一个序对的car
, 所以它们是 相同的.
(cdr
(cons 'ratatouille 'baguette))
和'baguette
是相同的Atom
吗?39 诚然如此, 因为这个序对的cdr
是'baguette
.
于是(car
(cons
(cons 'aubergine 'courgette)
'tomato))
是一个... 40 ... (Pair Atom Atom)
, 因为(cons 'aubergine 'courgette)
是一个序对, 其car
是Atom
'aubergine
, 其cdr
是Atom
'courgette
.
(car
(cdr
(cons
'ratatouille
(cons 'baguette 'olive-oil))))
和'baguette
是相同的Atom
吗?41 是的, 的确如此.
正如从第1章第39框 到第1章第41框 所展现的那样, 写法不同的表达式或许可能是相同的. 其中一种写法要比其他写法都更加直接. 42 'baguette
的确看上去比(car
(cdr
(cons
'ratatouille
(cons 'baguette 'olive-oil))))
更加直接.
一个表达式的规范形式(normal form) 是书写该表达式最直接的方式. 任何两个相同的表达式都有着等同的规范形式, 并且任何两个有着等同规范形式的表达式都是相同的. 43 'olive-oil
是(cdr
(cdr
(cons
'ratatouille
(cons 'baguette 'olive-oil))))
的规范形式吗?
这个问题是不完整的.相同总是相对于某个类型而言的, 因此规范形式也由类型决定.
44 'olive-oil
是Atom
(cdr
(cdr
(cons
'ratatouille
(cons 'baguette 'olive-oil))))
的规范形式吗?
是的, 的确如此.(cons 'ratatouille 'baguette)
是一个规范的(Pair Atom Atom)
吗? 45 是的, (cons 'ratatouille 'baguette)
的确是规范的.每个表达式都具有一个规范形式吗?
如果不刻画一个表达式的类型, 那么提问其是否具有规范形式也是没有意义的.然而, 给定一个类型, 每个由该类型描述的表达式的确都有一个由该类型确定的规范形式.
46 如果了两个表达式根据它们的类型是相同的, 那么它们就具有等同的规范形式. 因此, 这必然意味着我们可以通过比较两个表达式的规范形式来判断 (check) 它们是否相同.
(car
(cons
(cons 'aubergine 'courgette)
'tomato))
的规范形式是什么?47 类型是什么呢?如果类型是
(Pair Atom Atom)
那么规范形式为(cons 'aubergine 'courgette)
干得好!之前我们对于什么是一个
(Pair Atom Atom)
的描述其实是不完整的, 而完整的描述应该是... 48 ...成为一个序对, 其car
是一个Atom
, 其cdr
也是一个Atom
, 或者是与这样一个序对相同的一个表达式.
规范形式和类型
相同总是根据一个类型来的, 因而规范形式也由类型决定.
(car
(cons
(cons 'aubergine 'courgette)
'tomato))
和(cons 'aubergine 'courgette)
是相同的(Pair Atom Atom)
吗?49 是的, 之所以这两个表达式是相同的(Pair Atom Atom)
, 是因为(car
(cons
(cons 'aubergine 'courgette)
'tomato))
的规范形式是(cons 'aubergine 'courgette)
为什么(cons 'aubergine 'courgette)
和(cons 'aubergine 'courgette)
是相同的(Pair Atom Atom)
呢? 50 这看起来非常显然?
是的, 但是不是每个看上去 显然的东西实际上 都是显然的.第1章第23框 描述了何谓一个表达式和另一个表达式是相同的
(Pair Atom Atom)
51 (cons 'aubergine 'courgette)
和(cons 'aubergine 'courgette)
的顶层都是cons
, 'aubergine
和'aubergine
是相同的Atom
, 'courgette
和'courgette
是相同的Atom
.这两个表达式具有相同的car
和相同的cdr
, 因而它们是相同的
(Pair Atom Atom)
cons
之第一诫
两个cons
表达式是相同的(Pair A D )
, 如果它们的car
是相同的A 而它们的cdr
是相同的D , 其中A 和D 代表任意的类型.
很好.(Pair
(cdr
(cons Atom 'olive))
(car
(cons 'oil Atom)))
的规范形式是什么呢? 52 我猜是(Pair 'olive 'oil)
, 是这样吗?
实际上, 表达式(Pair
(cdr
(cons Atom 'olive))
(car
(cons 'oil Atom)))
既不由某个类型刻画, 本身也不是一个类型, 因此提问其规范形式是毫无意义的. 53 为什么呢?
因为Pair
在其参数为实际原子时并非类型.只有在其参数均为类型 (例如Atom
) 时, 它才是一个表达式.
54 这是不是意味着Pair
不能和car
与cdr
一起使用呢?
不, 完全不是.(Pair
(car
(cons Atom 'olive))
(cdr
(cons 'oil Atom)))
的规范形式是什么? 55 它的类型是什么呢? 规范形式总是相对于某个类型而言的.
类型本身也有规范形式. 如果两个类型有着等同的规范形式, 那么它们就是相同的类型. 如果两个类型是相同的类型, 那么它们就具有等同的规范形式. 56 类型(Pair
(car
(cons Atom 'olive))
(cdr
(cons 'oil Atom)))
的规范形式必然是(Pair Atom Atom)
, 因为(car
(cons Atom 'olive))
的规范形式为Atom
, 并且(cdr
(cons 'oil Atom))
的规范形式也是Atom
.
类型的规范形式
每个为类型的表达式都具有一个规范形式, 其是书写该类型最直接的方式. 如果两个表达式是相同的类型, 那么它们有着等同的规范形式. 如果两个类型有着等同的规范形式, 那么它们是相同的类型.
就是这样. 现在我们知道(cons 'ratatouille 'baguette)
也是一个(Pair
(car
(cons Atom 'olive))
(cdr
(cons 'oil Atom)))
因为... 57 ...(Pair
(car
(cons Atom 'olive))
(cdr
(cons 'oil Atom)))
的规范形式为(Pair Atom Atom)
而(cons 'ratatouille 'baguette)
是一个(Pair Atom Atom)
另一种说法是(Pair
(car
(cons Atom 'olive))
(cdr
(cons 'oil Atom)))
和(Pair Atom Atom)
是相同的类型. 58 如果一个表达式是一个(Pair
(car
(cons Atom 'olive))
(cdr
(cons 'oil Atom)))
那么它也是一个(Pair Atom Atom)
因为这两个类型是相同的类型.
类似地, 如果一个表达式是一个(Pair Atom Atom)
那么它也是一个(Pair
(car
(cons Atom 'olive))
(cdr
(cons 'oil Atom)))
因为这两个类型是相同的类型. 59 对于(Pair
Atom
(cdr
(cons 'oil Atom)))
也是类似的, 因为它和前面两个也是相同的类型.
'6
是一个Atom
吗?60 不是, 我们没有理由去相信'6
是一个Atom
因为数位6
既非字母也非连字符, 不是吗?
的确如此.(cons '17 'pepper)
是一个(Pair Atom Atom)
吗? 61 不, 因为(cons '17 'pepper)
的car
是'17
, 它并非 一个Atom
.尽管如此, 拥有数字应该也是很自然的事情才对.
数字当然是很方便的. 除了Atom
和Pair
, 我们还可以判断某个东西是否是一个Nat
. 62 让我们来试一下.
1729
是一个Nat
吗?64 是的, 1729
是一个Nat
. 它不仅是一个Nat
, 还很著名!
正数是Nat
. 67 啊, 那么-23
不是一个Nat
?
我们更喜欢采取积极 (positive) 的态度.最小的Nat
是什么? 68 0
不是一个自然数吗?
啊, 所以我们也不能总是很积极. 我们该如何获得剩下来的Nat
呢? 69 我们可以使用我们的老朋友add1
. 如果n 是一个Nat
, 那么(add1 n )
也是一个Nat
, 并且这个新的Nat
永远是正数, 即便n 是0
.有多少个Nat
呢?
并不, 因为我们总是可以... 71 ...使用add1
来加上一?
的确是这样!0
和26
是相同的Nat
吗?
72 显然不是.
(+ 0 26)
和26
是相同的吗?73 这个问题没有意义. 但是, 我们可以问它们是否是相同的Nat
吗?
当然可以.(+ 0 26)
和26
是相同的Nat
吗?
74 是的, 这是因为(+ 0 26)
的规范形式为26
, 而26
和26
当然是相同的.
zero
的意思是什么呢?75 zero
和0
是相同的吗?
在Pie里, zero
和0
不过是书写同一个Nat
的不同方式而已.one
和1
是相同的Nat
吗?
76 嗯, 如果zero
和0
是相同的Nat
的话, 那么这似乎很合理.
实际上, one
没有意义. 但是, (add1 zero)
是书写数字1
的另一种方式.通过定义 使得one
为(add1 zero)
的确是可行的.
(define one
(add1 zero))
77 为什么这个定义周围用虚线框住了呢?
虚线框意味着这个定义有点问题, 以至于它不能在之后使用. 78 这个定义有什么问题呢?看上去很正常啊.
当定义一个名字时, 有必要先claim
这个名字具有一个类型, 而one
是一个Nat
.(claim one Nat)
(define one
(add1 zero))
79 因此, two
可以被定义成(claim two Nat)
(define two
(add1 one))
定义前先声明
使用define
将一个名字和一个表达式联系起来之前需要使用claim
将名字和表达式的类型联系起来.
如果1
是书写(add1 zero)
的另一种方式, 那么书写4
的另一种方式是什么呢? 80 难道不应该是(add1
(add1
(add1
(add1 zero))))
吗? 我们不能定义four
来指代这个表达式吗?
当然可以了.(claim four Nat)
(define four
(add1
(add1
(add1
(add1 zero)))))
那么, 再问一下书写8
有另外的方式吗? 81 那必然是(add1
(add1
(add1
(add1
(add1
(add1
(add1
(add1 zero))))))))
8
是规范的吗?82 似乎如此, 但是为什么8
是规范的呢?
之所以8
是规范 的, 是因为其顶add1
是一个构造子(constructor) , 并且塞在顶add1
下面的参数, 即7
, 也是规范的. 83 为什么7
, 亦写作(add1
(add1
(add1
(add1
(add1
(add1
(add1 zero)))))))
是规范的呢?
7
是规范的完全是同理可得.84 这意味着zero
必然是规范的, 不然的话(add1 zero)
就不是规范的了.
之所以zero
是规范的, 是因为顶zero
是一个构造子, 并且其没有参数.(add1
(+ (add1 zero)
(add1
(add1 zero))))
是规范的吗? 86 不是, 因为+
不是构造子.
一个以构造子为顶的表达式被称为一个值(value) .即便
(add1
(+ (add1 zero)
(add1
(add1 zero))))
不是规范的, 它的确是一个值. 87 这个表达式不是规范的, 是因为(+ (add1 zero)
(add1
(add1 zero)))
并非书写3
的最直接方式.
现在我们给出另一个并非规范的表达式.(+ (add1
(add1 zero))
(add1 zero))
这是书写3
的最直接方式吗? 88 肯定不是.准确来说, 构造子究竟是什么呢?
某些表达式是类型, 例如Nat
和(Pair Nat Atom)
.对于新类型的解释的一部分是要说明其构造子为何. 构造子表达式是构造具有该类型的表达式的直接方式.
89 构造子的例子有哪些呢?
Nat
的构造子是zero
和add1
, 而Pair
的构造子是cons
.90 值和规范形式之间有何关系?
在一个值里, 顶层的构造子的参数不必是规范的. 但如果这些参数的确是规范的, 那么整个构造子表达式就具有规范形式.所有的值都是规范的吗?
91 显然不是.(add1
(+ (add1 zero)
(add1
(add1 zero))))
和(add1
(+ (add1 zero) (add1 one)))
都是值, 但是它们都不是规范的.
值和规范形式
不是所有的值都具有规范形式. 这是因为构造子的参数不必是规范的. 每个表达式只有一个规范形式, 但是有时可能将其写成值的方式不止一种.
以下空白方框里填什么会使得整个表达式不 是一个Nat
值呢?(add1 )
92 'aubergine
怎么样?
诚然如此.(add1 'aubergine)
并非一个Nat
值, 因为'aubergine
是一个Atom
而不是一个Nat
.当填充这样的方框时, 我们的期望是作为结果的表达式由某个类型刻画.
93 然而, 若置于该方框里的是任何一个Nat
表达式, 那么整个表达式就是 一个值. 这一整个表达式以add1
为顶, 而add1
是一个Nat
构造子.
找出一个与某起始表达式相同的值被称为求值(evaluation) . 94 类型呢? 毕竟, 相同需要类型.
有时当我们提及相同时, 我们并不显式提及类型. 然而, 总是存在一个意图的类型, 并且这个类型可以通过仔细阅读找到. 95 难道求值指的不是找到一个表达式的意义(meaning) 吗? 这不只是某个更简单的表达式.
我们这里的含义不一样. 表达式并不指向某种外部的意义概念——在Pie中, 除了表达式和我们对于表达式的判断之外, 别无其他. 96 这是一种看待求值的新方式.为什么规范形式和值之间要做区分呢?
每个东西都是一个表达式
在Pie中, 值也是表达式. Pie中的求值是寻找一个表达式, 而不是其他别的什么东西.
规范表达式没有可供求值的剩余机会了. 通常而言, 规范的表达式更容易理解. 然而, 往往找到一个值就足够了, 因为顶层的构造子可以用来判断接下来必然发生的事情. 97 如果找到一个值经常就足够了的话, 难道说这意味着我们可以自由地去寻找值, 并且然后可以想停就停呢?
是这样的, 只要关于构造子的参数的信息从没有用到即可.(add1
(+ (add1 zero)
(add1
(add1 zero))))
和four
是相同的Nat
吗? 98 这里给出一个可能的回答.它们不是相同的Nat
, 因为
(add1
(+ (add1 zero)
(add1
(add1 zero))))
是一个值, 而且它当然长得不像变量four
. 找到four
的值也无济于事, 因为four
的值看起来非常不同.
算是好的尝试.但是, 实际上它们是相同的Nat
.
99 怎么能这样呢?
两个不是值的Nat
表达式相同, 如果它们的值相同. 恰存在两种Nat
值可以相同的方式: 每种构造子一个.如果两个都是zero
, 那么它们就是相同的Nat
.
100 那么两个值都以add1
为顶的情况呢?
如果每个add1
的参数是相同的Nat
, 那么两个add1
表达式是相同的Nat
值.为什么
(add1 zero)
和(add1 zero)
是相同的Nat
101 这两个表达式都是值. 这两个值都以add1
为顶, 因此它们的参数应该是相同的Nat
.这两个参数都是zero
, zero
是一个值, 而且zero
和zero
是相同的Nat
值.
add1
之诫
如果n 和k 是相同的Nat
, 那么(add1 n )
和(add1 k )
是相同的Nat
.
为什么(add1 (+ 0 1))
和(add1 (+ 1 0))
是相同的Nat
102 这两个Nat
都以add1
为顶, 于是它们都是值.它们之所以相同, 是因为
(+ 0 1)
和(+ 1 0)
是相同的Nat
为什么(+ 0 1)
和(+ 1 0)
是相同的Nat
? 103 这些Nat
并非值, 因而为了判断它们是否相同, 第一步应该是找出它们的值.这两个表达式都以(add1 zero)
为其一个值, 而第1章第101框 解释了为什么
(add1 zero)
和(add1 zero)
是相同的Nat
很对. 104 是否这意味着four
本可以按照以下方式定义?(define four
(add1
(+ (add1 zero)
(add1
(add1 zero)))))
为什么有虚线框呢? 105 four
已经被定义了, 因而不能被再次定义.
定义是永恒的
一旦一个名字被claim
了, 那么它就不能被重新claim
. 一旦一个名字被define
了, 那么它就不能被重新define
.
不过当然了, 一开始four
的确可以像那样定义.实际上, 其他表达式都不能分辨出four
的两种定义之间的不同, 因为这两个定义了four
为相同的Nat
.
106 cons
是一个构造子吗?
是的, cons
构造Pair
. 107 为了对于car
表达式求值, 是否有必要对于car
的参数求值?
的确. 为了找出一个car
表达式的值, 我们从找出其参数的值开始.关于这参数的值, 我们能说什么呢?
108 这参数的值以cons
为顶.
在找出参数的值之后, 接下来应该做什么呢? 109 整个表达式的值是cons
的第一个参数.
(car
(cons (+ 3 5) 'baguette))
的值是什么?110 cons
的第一个参数是(+ 3 5)
这并非一个值.
为了找出一个car
表达式的值, 首先找出其参数的值, 这应该是(cons a d )
, 而(car
(cons a d ))
的值然后就是a 的值 .如何找出一个cdr
表达式的值呢?
111 就和car
一样, 我们从对于cdr
的参数求值开始, 直至其变为(cons a d )
, 然后(cdr
(cons a d ))
的值就是d 的值.所有的构造子都有参数吗?
当然不是, 回忆一下, 第1章第86框 里的zero
是一个构造子.两个表达式为相同的(Pair Atom Nat)
是什么意思?
112 这必然意味着每个表达式的值都以cons
为顶, 并且它们的car
是相同的Atom
而cdr
是相同的Nat
.
原子'bay
是一个构造子, 因而原子'leaf
也是一个构造子. 114 所有 原子都是构造子吗?
是的, 每个原子都构造其自身.这是不是意味着每个原子都是值?
115 的确, 因为解释为什么Atom
是一个类型
就是在说原子是Atom
值.
嗯.在表达式zero
中, 顶层构造子是什么?
116 那必然是zero
, 因为zero
是没有参数的构造子.
对于表达式'garlic
而言, 什么是顶层的构造子? 117 原子'garlic
是仅有的构造子, 所以它必然就是顶层的构造子.那么, Nat
是一个构造子吗?
不是, Nat
并非一个构造子. zero
和add1
是创造数据 的构造子, 而Nat
描述 了特定的数据, 其要么就是zero
, 要么以add1
为顶, 且以另一个Nat
为其参数.Pair
是一个构造子吗?
118 不是, 因为Pair
表达式是在描述以cons
为顶的表达式. 构造子创建数据 , 而不是类型.那么, Pair
应该叫做什么呢?
Pair
是一个类型构造子(type constructor) , 因其构造了一个类型. 类似地, Nat
和Atom
也是类型构造子.(cons zero 'onion)
是一个(Pair Atom Atom)
吗?119 不是.难道它不应该是一个
(Pair Nat Atom)
吗?
的确如此! 但是(cons 'zero 'onion)
是一个(Pair Atom Atom)
请问(cons 'basil
(cons 'thyme 'oregano))
的类型是什么? 120 基于我们的所见所闻, 它必然是一个(Pair Atom
(Pair Atom Atom))
诚然如此. 121 好吧, 暂时就那么多了, 我的脑袋要炸了!
或许你应该再一次阅读这一章. 判断, 表达式, 类型是本书最重要的概念. 122 在完全读完这一章之后, 或许应该来点新鲜蔬菜.
第2章 从心所欲, 道法自然 ratatouille如何? 1 很好(très bien) , 谢谢提问.
第1 章里有构造子和类型构造子, 分别构造值和类型.然而, car
既不是构造子也不是类型构造子.
2 那么, car
是什么呢?
car
是一个消去子(eliminator) . 消去子将构造子构造的值拆开来.另一个消去子是什么呢?
3 如果car
是一个消去子, 那么显然cdr
也是一个消去子.
构造子和消去子
构造子构造值, 而消去子将构造子构造的值拆开来.
另一种看待(消去子和构造子的)不同的方式在于值包含信息, 而消去子允许我们去利用这样的信息. 4 存在兼作构造子和消去子的东西吗?
不, 并不存在.可以定义一个函数(function) , 其表达力相当于car
和cdr
的联合.
5 怎么做呢?
这需要请出我们的老朋友λ
. 6 这是什么? 我有点陌生.
Oops! 它也被称为lambda
. 7 好吧, λ
可以构造函数.这是不是意味着λ
是一个构造子呢?
是的, 的确如此, 因为每个长得像(λ (x 0 x … ) body )
的表达式都是一个值.这种值的消去子是什么呢?
8 我们唯一能对函数做的事情就是将其应用于参数上.这样的话函数怎么拥有消去子呢?
(λ (flavor)
(cons flavor 'lentils))
的值是什么?10 它自λ
起, 因而它已经是一个值了.
是的.((λ (flavor)
(cons flavor 'lentils))
'garlic)
的值是什么呢? 11 那必然是(cons 'garlic 'lentils)
, 如果λ
和lambda
的行为一致且cons
是一个构造子的话.但是这难道不意味着即便cons
表达式已经是一个值了, cons
的第一个参数仍然会被求值吗?
不, 并非如此, 但是这是个非常好的问题. 替换这个λ
表达式的flavor
行为之发生是因为该λ
表达式被应用于了一个参数, 而不是因为cons
.这个λ
表达式的体的每个flavor
都会被替换为'garlic
, 不论环绕flavor
的表达式是什么. 12 因此, 也就是说((λ (root)
(cons root
(cons (+ 1 2) root)))
'potato)
的值应该是(cons 'potato
(cons (+ 1 2) 'potato))
对吗?
为什么前一个框里的(+ 1 2)
不需要计算呢? 13 整个表达式以cons
为顶, 故其已是值.
第2章第12框 里有点夸大其词. 如果以下λ
表达式的体中的root
出现在另一个同名的λ
之下的话, 那么它就不会被替换了.((λ (root)
(cons root
(λ (root) root)))
'carrot)
的值是什么?14 那必然是(cons 'carrot
(λ (root) root))
因为内层的root
出现在一个同名的λ
表达式之下.
λ
的确和lambda
行为一致, 因而这的确是正确的答案.成为一个
(→ Atom (Pair Atom Atom))
就是要成为一个λ
表达式, 当其应用于一个Atom
作为参数时, 则求值至一个(Pair Atom Atom)
15 那以这样的λ
表达式为值的表达式呢?
是的, 那些表达式也是(→ Atom (Pair Atom Atom))
因为当给出一个Atom
作为其参数时, 它们也会变成(Pair Atom Atom)
16 它们也是(→ (car (cons Atom 'pepper))
(Pair (cdr (cons 'salt Atom))
Atom))
吗?
的确如此, 因为(car (cons Atom 'pepper))
是Atom
而(cdr (cons 'salt Atom))
也是Atom
. 17 提问何谓两个表达式为相同的Nat
, Atom
, (Pair Nat Atom)
都是有意义的.提问何谓两个表达式为相同的
(→ Nat Atom)
或者相同的(→ (Pair Atom Nat) Nat)
也是有意义的吗?
是的, 的确如此. 两个表达式为相同的(→ Nat Atom)
当它们的值是相同的(→ Nat Atom)
18 它们的值是λ
表达式. 两个λ
为相同的(→ Nat Atom)
是什么意思呢?
两个接受 (expect) 相同数目的参数的λ
是相同的, 如果它们的体是相同的. 例如, 两个λ
表达式是相同的(→ Nat (Pair Nat Nat))
如果它们的体是相同的(Pair Nat Nat)
19 这意味着(λ (x) (cons x x))
和(λ (y) (cons y y))
不是相同的(→ Nat (Pair Nat Nat))
吗?
这两个表达式有什么不同之处呢? 20 参数的名字是不同的. 尽管如此, 这通常无关紧要. 现在紧不紧要呢?
两个λ
表达式也是相同的, 如果可以通过一致地对于参数换名使得它们的体变得相同.一致地对于变量换名不会改变任何东西的意义. 21 (λ (a d) (cons a d))
和(λ (d a) (cons a d))
是相同的(→ Atom Atom (Pair Atom Atom))
吗?
应用之始律
如果f 是一个
(→ Y X )
而
arg 是一个
Y , 那么
(f arg )
是一个
X .
λ
始第一诫
两个接受 (expect) 相同数目的参数的λ
是相同的, 如果在一致地对于它们的变量进行换名之后, 它们的体是相同的.
λ
始第二诫
如果f 是一个
(→ Y X )
那么
f 和
(λ (y ) (f y ))
是相同的
(→ Y X )
只要
y 不出现在
f 中.
不, 并非如此, 因为对于第二个λ
表达式中的变量进行一致换名以匹配第一个λ
表达式的参数将会产生(λ (a d) (cons d a))
而(cons d a)
和(cons a d)
并非相同的(Pair Atom Atom)
. 22 (λ (y) (car (cons y y)))
呢? 它和(λ (x) x)
是相同的(→ Nat Nat)
吗?
对于变量换名之律
一致地对于变量换名不会改变任何东西的意义.
首先, 一致地将y 换名为x . 现在, 问题就变为(car (cons x x))
和x
是否是相同的Nat
. 23 恰有两种方式可以使得两个表达式成为相同的Nat
. 一种是它们的值都是zero
. 另一种是它们的值都以add1
为顶, 而这两个add1
的参数是相同的Nat
.这些表达式并非Nat
值, 因为它们既不以add1
为顶, 也不是zero
.
x
的值尚不可知, 因为这个λ
表达式还没有被应用于一个参数. 但是, 当这个λ
表达式已被应用于一个参数时, x
的值仍然是 一个Nat
, 因为...24 ...因为这个λ
表达式是一个(→ Nat Nat)
故参数x
不可能是任何其他什么东西.
并非值且因为变量的缘故还 不能被求值的表达式被称为中立(neutral) 的. 25 这意味着(cons y 'rutabaga)
是中立的咯?
不, 它并非中立, 因为(cons y 'rutabaga)
是一个值.如果x
是一个(Pair Nat Atom)
, 那么
(cdr x)
是一个值吗? 26 不是, 因为cdr
是一个消去子, 而消去子把值拆散.在不知道x
的值的情况下, 没有办法找出(cdr x)
的值, 故(cdr x)
是中立的.
中立表达式使得我们有必要扩展对于何谓相同的看法. 每个变量与其自身都是相同的, 不管其类型如何. 这是因为变量只能被一致 地替换, 所以说一个变量的两次出现不可能被代之以不同的值. 27 因此, 如果我们假定y
是一个Nat
, 那么(car (cons y 'rutabaga))
和y
是相同的Nat
, 这是因为该car
表达式的规范形式是y
, 而y
和y
是相同的Nat
.
的确如此, 并且类似地, 我们有(λ (x) (car (cons x x)))
和(λ (x) x)
是相同的(→ Nat Nat)
28 是的, 因为中立表达式x
和x
是相同的Nat
.
(λ (x) (car x))
和(λ (y) (car y))
是相同的(→ (Pair Nat Nat) Nat)
吗?29 我们应该会这样认为, 但是理由是什么呢?
第一步应该是一致地将y
换名为x
.
(λ (x) (car x))
和(λ (x) (car x))
是相同的(→ (Pair Nat Nat) Nat)
吗? 30 是的, 如果假定(car x)
和(car x)
是相同的Nat
.但是(car x)
并不是一个变量, 在知道x
的值之前我们都不可能找出(car x)
的值.
如果两个表达式以等同的消去子为顶, 并且两个消去子的参数是相同的, 那么这两个表达式也是相同的. 字面上等同的中立表达式是相同的, 不管它们的类型如何. 31 因而(car x)
和(car x)
是相同的Nat
, 若假定x
是一个(Pair Nat Nat)
.
中立表达式之诫
字面上等同的中立表达式是相同的, 不管它们的类型如何.
(λ (a d) (cons a d))
是一个(→ Atom Atom (Pair Atom Atom))
吗?32 →
后面跟着更多的表达式意味着什么呢?
→
后面跟着的表达式, 除了最后一个, 都是参数的类型. 最后一个则是值的类型.33 好吧, 那么(λ (a d) (cons a d))
的确是一个(→ Atom Atom (Pair Atom Atom))
这些表达式不可避免地正在逐渐变长.
一种缩短它们的方式是小心地使用define
, 如第1章第77框 那样, 其总是允许我们用简短的名字来代替冗长的表达式. 34 好想法.
设构造子cons
被应用于'celery
和'carrot
, 我们可以称这个值为vegetables
.(claim vegetables
(Pair Atom Atom))
(define vegetables
(cons 'celery 'carrot))
从现在开始, 每当名字vegetables
被使用, 它就和(cons 'celery 'carrot)
是相同的(Pair Atom Atom)
因为这就是vegetables
如何被define
的. 35 为什么claim
后面有写着(Pair Atom Atom)
呢?
define
之律和诫
遵照
(claim name X )
和(define name expr )
如果
expr 是一个X
那么
name 是一个X
并且
name 和expr 是相同的X
(Pair Atom Atom)
描述了我们可以怎样使用vegetables
. 例如, 我们知道(car vegetables)
是一个Atom
, 而(cons 'onion vegetables)
是一个(Pair Atom (Pair Atom Atom))
36 啊, 懂了.
vegetables
和(cons (car vegetables)
(cdr vegetables))
是相同的(Pair Atom Atom)
吗?37 的确如此, 因为每个表达式的值都是一个序对, 并且其car
是'celery
而cdr
是'carrot
.
实际上, 每当p 是一个(Pair Atom Atom)
时, 那么p 和(cons (car p ) (cdr p ))
是相同的(Pair Atom Atom)
找出(car p )
和(cdr p )
的值是没有必要的. 38 这看起来很合理.
cons
之第二诫
如果p 是一个(Pair A D )
, 那么它和(cons (car p ) (cdr p ))
是相同的(Pair A D )
.
以下定义可以允许吗?(claim five Nat)
(define five (+ 7 2))
39 什么鬼?
尽管可能是个愚蠢的想法, 但它是可以被允许的.(+ five 5)
的规范形式是什么? 40 那必然是10
, 因为五 (five) 加5 等于十.
再想想. 请记得five
的奇怪定义... 41 ...哦, 好吧, 那应该是14
, 因为five
被定义成了9
.
的确如此. 42 这个 定义可以允许吗? 似乎它看上去不那么蠢.(claim zero Nat)
(define zero 0)
虽然和将five
定义为9
相比没那么愚蠢, 但是这个定义也是不被允许的.已经被使用了的名字, 不论是用于构造子, 消去子, 还是之前的定义, 都不适合再与claim
或者define
一起使用了.
43 好.
定义中的名字
在Pie语言中, 只有没有用过的名字, 才能和claim
或者define
一起使用, 不论是用作构造子, 消去子, 还是之前的定义.
Nat
有一个消去子可以区分值为zero
的Nat
和值以add1
为顶的Nat
. 这个消去子被称为which-Nat
.44 which-Nat
到底是如何辨别应该是哪一种Nat
的呢?
一个which-Nat
表达式具有三个参数: target , base , step :(which-Nat target
base
step )
which-Nat
判断target 是否是zero
, 如果是, 那么整个which-Nat
表达式的值即base 的值. 否则的话, 如果target 是(add1 n )
, 那么整个which-Nat
表达式的值即(step n )
的值. 45 因此, which-Nat
既要判断一个数字是否是zero
, 又要在数字并非zero
时去除其顶的add1
.
的确如此.(which-Nat zero
'naught
(λ (n )
'more))
的规范形式是什么呢? 46 必然是'naught
, 因为其target zero
是zero
, 故整个which-Nat
表达式的值是base , 即'naught
.为什么n
是黯淡的?
黯淡是用来指出n
在那个λ
表达式的体里没有被使用. 没有被使用的名字都将以黯淡的形式出现. 47 为什么没有被使用呢?
which-Nat
提供了使用更小的Nat
的可能性, 但是它并不要求一定使用. 当然, 为了提供这种可能性, which-Nat
的最后一个参数必须要接受一个Nat
.48 好.
黯淡的名字
没有被使用的名字是黯淡的, 但是它们的确有必要呆在那里.
(which-Nat 4
'naught
(λ (n )
'more))
的值是什么?49 必然是'more
, 因为4
是另一种书写(add1 3)
的方式, 而其以add1
为顶.((λ (n ) 'more) 3)
的规范形式为'more
which-Nat
之律
如果target 是一个Nat
, base 是一个X , 并且step 是一个
(→ Nat X )
那么
(which-Nat target
base
step )
是一个
X .
which-Nat
之第一诫
如果
(which-Nat zero
base
step )
是一个
X , 那么其和
base 是相同的
X .
which-Nat
之第二诫
如果
(which-Nat (add1 n )
base
step )
是一个
X , 那么其和
(step n )
是相同的
X .
(which-Nat 5
0
(λ (n)
(+ 6 n)))
的规范形式是什么?50 难道说是11
, 因为((λ (n) (+ 6 n)) 5)
是11
?
这个的规范形式应该是10
, 因为一个which-Nat
表达式的值是将藏在其target底下的Nat
作为参数传给其step得到的. 51 啊, 所以说规范形式为10
是因为((λ (n) (+ 6 n)) 4)
是10
.
请定义一个叫做gauss
的函数, 使得(gauss n )
是从zero
到n 的所有Nat
之和.gauss
的类型应该是什么?
52 将多个Nat
加起来当然应该是一个Nat
.(claim gauss
(→ Nat Nat))
第一步是挑选一个样例参数. 好的选择大概应该在5
至10
之间, 这样的话既足够大而有趣, 也足够小而可控. 54 5
怎么样, 然后呢?
听上去不错.(gauss 5)
的规范形式应该是什么? 55 应该是0 + 1 + 2 + 3 + 4 + 5 , 即15 .
接下来的一步应该是收缩参数.(gauss 4)
和(gauss 5)
差不多, 前者是10
, 后者是15
.
白框框住的是未知的代码, 而白纸黑字的部分则是其中的已知部分. 在这被框住的内容之中, 我们该如何从
(gauss 4)
得到(gauss 5)
呢? (gauss 4)
56 我们必须给(gauss 4)
加上5
, 而和为15
. (+ 5 (gauss 4) )
接下来, 我们要使得这种手段对于任意的以add1
为顶的Nat
成立.如果n 是一个Nat
, 那么该如何从
(gauss n )
得到(gauss (add1 n ))
呢? (gauss n )
请记得5
是书写(add1 4)
的另一种方式.
57 找出(gauss (add1 n ))
的方法在于将前一个框中的答案里的4
替换成n . (+ (add1 n ) (gauss n ) )
zero
怎么样呢?
现在让我们来定义gauss
.请记得白色的框和白纸黑字的部分.
59 小菜一碟! 名字n-1
暗示了其代表着一个脱去了n
的一层add1
的Nat
, 或者说比n
小一的Nat
.(define gauss
(λ (n)
(which-Nat n
0
(λ (n-1)
(+ (add1 n-1) (gauss n-1) ) ))))
很好的尝试. 如果递归可以作为一个选项, 那么这就不需要用虚线框住了. 可惜, 递归不是 一个选项. 60 为什么不让其成为一个选项呢?
因为递归不是一个选项. 61 为什么不让其成为一个选项呢?
因为递归不是一个选项. 62 好吧, 请解释为什么递归不是一个选项.
递归之所以不是一个选项, 是因为每个表达式都必须具有一个值. 一些递归定义使得我们可以写下没有值的表达式. 63 举个例子呢? 一个递归定义和一个没有值的表达式.
forever
就是一个这样的例子.(claim forever (→ Nat Atom))
(define forever
(λ (and-ever)
(forever and-ever)))
(forever 71)
的值是什么?64 好问题.为什么它被虚线框住了?
递归不是一个选项, 故像forever
这样的递归定义将永远被虚线框住. 65 但是, 对于像gauss
这种需要递归的定义该怎么办呢?
递归定义存在着一种安全的替代品, 其允许我们写下gauss
而不需要包括gauss
这个名字, 对于其他诸多类似的定义也是如此. 66 这里是gauss
的安全替代版本定义的起点.(define gauss
(λ (n)
gauss不是一个选项! ))
就目前的走向来看, 还是正确的. 要义在于gauss
不能出现在其自身的定义里. 67 现在递归不是一个选项 这句话的含义是清晰的了.难道说这意味着Pie中不可能写下gauss
吗?
Pie中写下gauss
是 可能的, 但是which-Nat
和define
还不足以让我们能够应对这个任务. 我们需要不同的消去子, 但是时机还远未成熟. 68 耐心是一种美德.
给诸如(Pair Nat Nat)
这样的表达式定义更加简短的名字也是可能的. 69 这种情况下的claim
是什么样的呢?
另一个好问题!诸如Atom
, Nat
, (Pair Atom Nat)
这样的表达式是类型, 而每一个这样的类型都是一个U .
70 类型是值吗?
有的类型是值.一个为类型的表达式是一个值, 当其以某个类型构造子为顶时. 到目前为止, 我们见过的类型构造子有Nat
, Atom
, →
, 和U .
71 所有的类型都是值吗?
类型值
一个由某个类型描述的表达式是一个值, 当其以某个构造子为顶. 类似地, 一个为类型的表达式是一个(类型)值, 当其以某个类型构造子为顶.
并非如此.(car (cons Atom 'prune))
是一个类型, 但不是一个值, 因为car
既不是一个构造子, 也不是一个类型构造子. 72 什么样的表达式由(car (cons Atom 'prune))
描述呢?
因为(car (cons Atom 'prune))
和Atom
是相同的类型, 所以(car (cons Atom 'prune))
所描述的表达式和Atom
是同样的. 73 类型构造子和构造子有什么区别?
类型构造子构造类型, 而构造子 (或者说数据 构造子) 构造由那些类型描述的值.作出一个表达式是一个类型的判断需要知道其构造子. 但是, U 的意义并不由知道所有的类型构造子给出, 因为可以引入新的类型.
74 (cons Atom Atom)
是一个U 吗?
不是, 但(cons Atom Atom)
是一个(Pair U U )
一个原子, 例如'plum
, 是一个Atom
. 从另一方面来说, Atom
不是一个Atom
, 而是一个由U 刻画的类型. 75 让我们来思考(Pair Atom Atom)
的事情.(cons Atom Atom)
是一个(Pair Atom Atom)
吗?
不是, 这是因为Atom
是一个类型, 而不是一个Atom
. 76 U 是一个U 吗?
不是, 但U 的确是一个类型. 没有表达式可以成为其自身的类型. 77 每个为U 的表达式是否也都是一个类型呢?
是的, 如果X 是一个U , 那么X 是一个类型. 78 是否每个类型都由U 刻画呢?
每个U 都是一个类型
每个由U 刻画的表达式都是一个类型, 但是不是每个类型都由U 刻画.
每个由U 刻画的表达式都是一个类型, 但是不是每个为类型的表达式都由U 刻画. 79 (cons Atom Nat)
是一个(Pair U U )
吗?
的确如此.定义Pear
, 其意指Nat
的序对的类型.
80 那必然是(claim Pear U )
(define Pear
(Pair Nat Nat))
从现在开始, Pear
的意思是(Pair Nat Nat)
这个名字只有四个字符, 而其代表的类型有十四个字符.
每当Pear
出现时, 是不是它就和(Pair Nat Nat)
是相同的类型? 81 的确, 根据define
之诫.
(cons 3 5)
是一个Pear
吗?82 是的, 因为(cons 3 5)
是一个(Pair Nat Nat)
而Pear
恰被定义 为这个类型.
不是. 由define
定义的名字既不是类型构造子也不是构造子, 故并非值.Pear
有消去子吗?
84 你说的消去子是不是指的是将类型Pear
的值拆开的那种东西?
是的.Pear
的消去子必然要使得具有类型Pear
的值中的信息能够被使用.
85 什么叫做使得信息能够被使用?
使得任意Pear
中的信息能够被使用的Pear
的消去子是这样的一种东西, 其可以应用函数于Pear
的两个Nat
参数. 86 好吧.
什么样的函数可以应用到两个作为参数的Nat
上呢? 87 一个例子: +
.
交换Nat
的表达式是什么样的呢? 88 (λ (a d)
(cons d a))
如何?
非常好. 从一个Pear
里提取出第一个Nat
的表达式是什么样的呢? 89 那必然是(λ (a d) a)
非常接近了. 实际上, 应该是(λ (a d ) a)
90 你真无聊. 但是, 没有这个黯淡的效果, 表达式本身也应该是正确的吧?
是这样的. 为了从一个Pear
中得到一个类型为X 的值, 我们必然需要有一个类型为(→ Nat Nat X )
的表达式. +
的类型是什么? 91 其取两个Nat
而产生一个Nat
, 故类型必然是(→ Nat Nat Nat)
很好.当a
和d
都为Nat
时,
(λ (a d)
(cons d a))
的类型是什么? 92 显然是(→ Nat Nat Pear)
其和(→ Nat Nat (Pair Nat Nat))
相同.一个λ
表达式该怎么和一个Pear
一起使用呢?
定义是没有必要的
没有定义也可以完成一切事情, 但是定义的确有助于理解.
试试这个:(claim Pear-maker U )
(define Pear-maker
(→ Nat Nat Pear))
(claim elim-Pear
(→ Pear Pear-maker Pear))
(define elim-Pear
(λ (pear maker)
(maker (car pear)
(cdr pear))))
是否可以写下elim-Pear
的claim
而不使用Pear
或Pear-maker
呢? 93 可以, 只需要将Pear-maker
和Pear
替换为相应的定义即可.(claim elim-Pear
(→ (Pair Nat Nat)
(→ Nat Nat
(Pair Nat Nat))
(Pair Nat Nat)))
名字Pear-maker
和Pear
从不是必要的. 名字elim-Pear
是必要的吗?
什么时候定义是必要的呢? 94 什么时候都不是必要的!
是这样的. elim-Pear
和它定义里的那个λ
表达式是相同的.(elim-Pear
(cons 3 17)
(λ (a d)
(cons d a)))
的值是什么? 95 ((λ (pear maker)
(maker (car pear) (cdr pear)))
(cons 3 17)
(λ (a d)
(cons d a)))
怎么样?
一个好的开始, 但这还不是值. 96 值是(cons 17 3)
, 这是因为elim-Pear
和其定义中的那个λ
表达式相同, 并且(car (cons 3 17))
和3
是相同的Nat
, 而(cdr (cons 3 17))
和17
是相同的Nat
, 最后((λ (a d)
(cons d a))
3 17)
和(cons 17 3)
是相同的Pear
.
将两个Pear
加起来会是什么意思呢? 97 难道说是将每个Pear
的第一个Nat
和第二个Nat
分别加起来吗?
很好的猜测.这种pearwise 加法的类型是什么?
98 (→ Pear Pear Pear)
对吗?
怎么用elim-Pear
定义pearwise加法? 99 好难啊, 是不是两个Pear
都要消去, 因为这些Nat
都是结果的一部分?
当然了.请定义pearwise+
, 以使得
(pearwise+
(cons 3 8)
(cons 7 6))
和(cons 10 14)
是相同的Pear
. 100 首先, 将anjou
和bosc
各自拆成其相应的部分, 然后再将它们的第一部分和第二部分分别加起来.(claim pearwise+
(→ Pear Pear Pear))
(define pearwise+
(λ (anjou bosc)
(elim-Pear
anjou
(λ (a1 d1 )
(elim-Pear
bosc
(λ (a2 d2 )
(cons
(+ a1 a2 )
(+ d1 d2 ))))))))
或许现在应该好好休息一下, 然后回来重读本章. 101 是的, 的确是个好想法.但是, 我们该如何到达第3 章呢?
课间: 一叉子Pie 尽管派 (pie) 的确是一种美味的食物, 但是Pie是一种语言, 玩玩而已无伤大雅. 2 让我们开始吧.
使用Pie很像是对话: 其接受声明, 定义和表达式, 并回之以反馈. 3 什么样的反馈呢?
对于声明和定义而言, 反馈是判断它们是否具备意义. 对于表达式而言, 反馈是表达式的类型和规范形式. 4 不具备意义会怎么样呢?
Pie会解释它们出了什么问题, 有时添加有用的提示. 5 表达式可能会出什么问题呢?
在吃Pie前请吃蔬菜.尝试输入
'spinach
看看会发生什么. 6 Pie回之以(the Atom 'spinach)
这里的the
是什么意思?
其意即'spinach
是一个Atom
.在Pie里, 一个表达式要么是一个类型, 要么由一个类型描述. Pie可以自己找出诸多表达式的类型, 包括原子.
7 (car 'spinach)
怎么样呢?
这个表达式并不由某个类型刻画, 因为'spinach
不是一个序对. 8 Pie总能判断描述了表达式的类型吗?
不能, 有时Pie需要帮助.此时, 使用the
表达式来告诉Pie意图的类型.
9 例如?
Pie不能确定单独的cons
表达式的类型. 10 为什么呢?(cons 'spinach 'cauliflower)
是一个(Pair Atom Atom)
这难道不是显然的吗?
这对于我们来说是显然的, 但是之后cons
将变得更加壮丽 (magnificent), 提升了的表达力 (power) 使得其类型不再能被自动确定. 11 那么, 怎样让Pie确定(cons 'spinach 'cauliflower)
是一个序对呢?
试试这个:(the (Pair Atom Atom)
(cons 'spinach 'cauliflower))
12 因此, 一个the
表达式将一个表达式与其类型联系起来, 不仅是在Pie的反馈里, 也可以出现在我们写下来的表达式里.
the
之律
如果X 是一个类型而e 是一个X , 那么
(the X e )
是一个
X .
Pie中存在两种表达式: 一种Pie可以自行判断其类型, 另一种对于Pie而言则需要我们的帮助. 13 还有其他帮助Pie判断类型的方式吗?
的确是有的. 在第1 章里, claim
在其相应的define
之前出现是必要的, 而这告诉了Pie对于定义的意义而言使用何种类型. 14 为什么我们不在每次Pie不能确定一个表达式的类型时就使用claim
和define
呢?
原则上是可以的, 但总是把名字都写出来令人厌倦. 15 还有什么其他方法能够帮助Pie找到类型吗?
的确还有一种方法. 如果某处使用了一个表达式而此处只有一个类型能够成立 (make sense), 那么Pie就会使用这个类型. 16 举个例子呢?
当检查(the (Pair Atom
(Pair Atom Atom))
(cons 'spinach
(cons 'kale 'cauliflower)))
(是否)由一个类型描述时, Pie使用(Pair Atom Atom)
作为(cons 'kale 'cauliflower)
的类型. 17 这里, 内层的cons
不需要一个the
是因为其类型是来源于外层的cons
的类型的.以the
为顶的表达式都是值吗?
并非如此.(the X e )
的值是e 的值. 18 那么(car
(the (Pair Atom Nat)
(cons 'brussels-sprout 4)))
的值是什么?
the
之诫
如果X 是一个类型而e 是一个X , 那么
(the X e )
和
e 是相同的
X .
值是很小一点的'brussels-sprout
.现在试试这个:
U
19 Pie说:U
为什么不是(the U U)
呢?
U 是一个类型, 但是其不具备 类型. 这是因为没有表达式可以是其自身的类型, 正如第2章第77框 所写的那样.当一个表达式是一个类型但不具备类型时, Pie只回以其规范形式.
20 还有其他别的什么类型不具备类型U 的吗?
当然有了, 例如(Pair U U )
(Pair Atom U )
(→ U U )
都是类型, 但是它们不以U 作为它们的类型. 21 还有其他什么关于Pie的方面是我们应该知道的呢?
暂时就这么多了. 之后还有机会. 22 下一步是什么?
尽情玩乐. 23 好主意. (Sounds like a plan!)
第3章 消去所有的自然数! 以下是来源于第2章第59框 的gauss
的虚框定义.(define gauss
(λ (n)
(which-Nat n
0
(λ (n-1)
(+ (add1 n-1) (gauss n-1))))))
现在是时候以不使用显式递归的方式来正确地定义gauss
了. 1 难道说我们将要像下面这样来定义gauss
吗?(define gauss
(λ (n)
...这里没有gauss? ))
为什么递归定义不是一个选项? 2 因为递归定义不是一个选项 .
确实.但是, 某些递归定义总是能产生一个值.
3 比如说gauss
, 不是吗?
的确如此.(gauss 0)
的规范形式是什么?
4 是zero
.
(gauss 1)
的值是什么?5 是1
, 因为| (gauss (add1 zero))
相同于 | (+ 1 (gauss zero))
相同于 | (add1 (gauss zero))
这就是值吗? 6 不是还有更多要做的吗?| (add1 (gauss zero))
| (add1 zero)
相同性
如果一个相同于 图表可以用来表明两个表达式是相同的, 那么这个事实可以在任何地方使用而不需要进一步的澄清. 相同于 图表只是为了辅助构建理解的.
实际上(add1 (gauss zero))
已经 是一个值了. 为什么? 7 好吧, 那是因为构造子add1
就在顶上.
诚然如此.(gauss 1)
的规范形式是什么?
8 是(add1 zero)
.
为什么(gauss 2)
具有规范形式? 9 因为(gauss 2)
的规范形式只依赖于(gauss 1)
的规范形式 (其的确有 一个规范形式) 和+
的规范形式.+
有规范形式吗?
一旦定义了+
, 它的确具有规范形式. 暂时, 假定+
具有规范形式. 10 好吧.
为什么(gauss 3)
具有规范形式? 11 因为(gauss 3)
的规范形式只依赖于(gauss 2)
的规范形式 (其的确有一个规范形式) 和+
的规范形式. 暂时, 我们假定+
具有规范形式.
为什么对于任意的Nat
k , (gauss (add1 k ))
都有规范形式? 12 因为(gauss (add1 k ))
的规范形式仅依赖于(gauss k )
的规范形式, k 的值, 以及+
的规范形式.k 的值要么是zero
, 要么以add1
为顶. 我们已经知道了(gauss 0)
具有规范形式, 并且我们刚刚验证了, 对于任意的Nat
k , (gauss (add1 k ))
都有规范形式.
为每个 可能的参数都赋有一个值的函数被称为完全函数(total function) .+
和gauss
都是完全的.
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
表达式长得像这样:(iter-Nat target
base
step )
和which-Nat
一样, 当target 是zero
时, 整个iter-Nat
表达式的值即base 的值. 18 iter-Nat
和which-Nat
不一样的地方在哪里?
和which-Nat
不一样的是, 当target 是(add1 n )
时, 整个iter-Nat
表达式的值是(step
(iter-Nat n
base
step ))
的值. 19 所以说target 的值中的每一个add1
都将被代之以一个step , 而zero
会被代之以base .
iter-Nat
之律
如果target 是一个Nat
, base 是一个X , 而step 是一个
(→ X X )
那么
(iter-Nat target
base
step )
是一个
X .
iter-Nat
之第一诫
如果
(iter-Nat zero
base
step )
是一个
X , 那么它和
base 是相同的
X .
iter-Nat
之第二诫
如果
(iter-Nat (add1 n )
base
step )
是一个
X , 那么它和
(step
(iter-Nat n
base
step ))
是相同的
X .
是的.(iter-Nat 5
3
(λ (smaller)
(add1 smaller)))
的规范形式是什么? 20 是8
, 因为add1
相继应用于3
五次是8
:(add1
(add1
(add1
(add1
(add1 3)))))
整个iter-Nat
表达式的类型和base 的类型相同吗? 21 必然如此, 因为当target 是zero
时, 整个iter-Nat
表达式的值是base 的值.
让我们使用X 作为base 的类型的一个名字.step 的类型如何?
22 step 被应用于base , 其也被应用于由step 产生的几乎是答案的东西 (almost-answer), 故step 必然是一个(→ X X )
就和第2章第45框 中的which-Nat
一样, 名字target , base , step 是引用iter-Nat
的参数的便利方式.以下的iter-Nat
表达式里, target, base, step分别是什么?
(iter-Nat 5
3
(λ (k)
(add1 k)))
23 target是5
base是3
step是(λ (k)
(add1 k))
到目前为止, 我们引用+
就好像其已被完全理解了一样, 并且我们还假定其具有规范形式, 然而我们还没有定义+
.+
的类型应该是什么?
24 +
接受两个Nat
而返回一个Nat
.(claim +
(→ Nat Nat Nat))
的确如此.若是递归可以成为一个选项, 那么以下将会是一个正确的定义.
(define +
(λ (n j)
(which-Nat n
j
(λ (n-1)
(add1 (+ n-1 j) ) ))))
那么, 该如何利用iter-Nat
来定义+
呢? 25 使用iter-Nat
定义+
需要一个base和一个step. base应该是j
, 鉴于以下相同于 图表:| (+ zero j)
| j
有什么好的办法找到step是什么吗?
step基于+
的递归版本的白框里的内容, 其描述了如何将一个几乎是答案的东西+n-1
变为一个真正的答案.将白纸黑字的部分 (其包含有递归) 替换以献给step的几乎是答案的参数, 记得保留白框里其他内容.
26 以下就是我们想要的step了.(claim step-+
(→ Nat Nat))
(define step-+
(λ (+n-1 )
(add1 +n-1 ) ))
除非类型和定义里的所有名字都已定义, 不然我们无法定义一个新的名字. 27 而+
需要引用step-+
, 当然其既已定义.(define +
(λ (n j)
(iter-Nat n
j
step-+)))
是的, 现在+
就定义好了.(+ (add1 zero) 7)
是什么?
28 是8
, 因为| (+ (add1 zero) 7)
| (iter-Nat (add1 zero)
7
step-+)
| (step-+
(iter-Nat zero
7
step-+))
| (add1
(iter-Nat zero
7
step-+))
| (add1 7)
即8
.
iter-Nat
可以用来定义gauss
吗?29 iter-Nat
展现了一种反复消去add1
下面藏着的更小的Nat
的方式.消去更小的Nat
... 这听起来的确像是gauss
所遵照的方法.
是挺接近的, 但是step没有足够的信息. gauss
需要一个结合了which-Nat
和iter-Nat
的表达力的消去子. 这个消去子叫做rec-Nat
. 30 rec-Nat
是什么?
rec-Nat
的step会被应用到两个参数上: 一个是add1
下面藏着的那个更小的Nat
, 另一个是这更小的Nat
上的递归答案. 这是第2章第59框 里的gauss
定义所使用的方法.此即所谓rec-Nat
模式.
31 如何使用rec-Nat
定义gauss
呢?
这个框里有两种gauss
的定义: 一个是来源于第2章第59框 虚框版本, 另一个则是使用rec-Nat
的版本.(define gauss
(λ (n)
(which-Nat n
0
(λ (n-1)
(+ (add1 n-1) (gauss n-1) ) ))))
(define gauss
(λ (n)
(rec-Nat n
0
(λ (n-1 gaussn-1 )
(+ (add1 n-1) gaussn-1 ) ))))
有什么区别呢? 32 三个区别:which-Nat
代之以rec-Nat
,内层的λ
表达式多了一个变量gaussn-1
, 以及 递归(gauss n-1)
代之以几乎是答案的gaussn-1
.
名字n-1
和gaussn-1
暗示了其意义, 尽管它们只是变量名而已.rec-Nat
的参数和iter-Nat
有着同样的特殊名字: 其总被称为target , base , step .
33 我们该如何确定一个rec-Nat
表达式的值呢?
和iter-Nat
一样, 如果target是zero
, 那么整个rec-Nat
表达式的值即base 的值. 34 如果target以add1
为顶呢?
which-Nat
应用其step于藏在add1
下的这更小的Nat
.iter-Nat
应用其step于一个新的iter-Nat
表达式, 其和原本的表达式带有相同的base和step, 但是以藏在add1
下的这更小的Nat
为新的target.
这两者该如何结合起来呢?
35 我猜一下.step将被应用到这更小的Nat
上. 而且, step也会被应用于一个新的rec-Nat
表达式, 其和原本的表达式带有相同的base和step, 但是以恰好相同的这更小的Nat
为target.
很好的想法. 当rec-Nat
和作为target的非zero
的Nat
一起使用时, target将藉由每次移除一个add1
收缩. 而且和iter-Nat
一样, base和step是不会改变的.(rec-Nat (add1 zero)
0
(λ (n-1 almost)
(add1
(add1 almost))))
的值是什么? 36 其为step应用于zero
和新的rec-Nat
表达式, 即((λ (n-1 almost)
(add1
(add1 almost)))
zero
(rec-Nat zero
0
(λ (n-1 almost)
(add1
(add1 almost)))))
前一个框中作为结果的表达式并不是一个值, 但是其和原本的表达式是相同的.那么值是什么呢?
37 (add1
(add1
(rec-Nat zero
0
(λ (n-1 almost)
(add1
(add1 almost))))))
其是一个值, 因为其以add1
为顶.
其规范形式如何? 38 (add1
(add1 0))
target是zero
, 而base是0
.
一个rec-Nat
表达式只在target为Nat
时是一个表达式. 39 base和step的类型应该是什么呢?
base必然具有某个类型, 让我们再次称其为X . X 可以是任意的类型, 但是整个rec-Nat
表达式必然和base有着相同的类型, 即X . 40 这就是所有要说的了吗?
还没有说完.如果base是一个X , 那么step必然是一个
(→ Nat X X )
为什么这是step的合适类型呢? 41 step会被应用于两个参数: 第一个参数是一个Nat
, 因为其为target里藏在add1
下的那个东西; 第二个参数是almost
, 其为X 是因为其也由rec-Nat
产生.
那么, 这是如何与which-Nat
和iter-Nat
里的step的类型联系起来的呢? 42 就像which-Nat
, rec-Nat
的step接受藏在target的add1
下的那个更小的Nat
作为参数. 就像iter-Nat
, 其也接受递归的几乎是答案的结果.
以下是一个函数, 其判断一个Nat
是否是zero
.(claim step-zerop
(→ Nat Atom Atom))
(define step-zerop
(λ (n-1 zeropn-1 )
'nil))
(claim zerop
(→ Nat Atom))
(define zerop
(λ (n)
(rec-Nat n
't
step-zerop)))
43 为什么要使用递归性的rec-Nat
来定义实际上只需要判断顶层构造子是zero
还是add1
的过程呢? 毕竟, which-Nat
就已经够用了.
which-Nat
很容易解释, 但是rec-Nat
可以做到任何which-Nat
(和iter-Nat
) 可以完成的事情.为什么step-zerop
中的λ
变量被称为n-1
和zeropn-1
呢?
44 之所以选取n-1
这个暗示比n
小一的名字, 还是因为其比target Nat
小一, target Nat
即正被消去的那个Nat
表达式. 名字zeropn-1
暗示着(zerop n-1)
.
step仅仅是一个λ
表达式, 故其他任意未被使用的变量名亦可运用, 但是我们往往在step中使用这种命名变量的风格.step-zerop
的两个参数皆未被使用, 故其为黯淡的. 因此, 这个定义只是看上去像递归的, 实则并非如此.
45 不使用其参数的λ
表达式意义何在?
rec-Nat
的step总是接受两个参数, 尽管其并不总是使用它们.(zerop 37)
的值是什么?
46 让我们看看.| (zerop (add1 36))
| (rec-Nat (add1 36)
't
step-zerop)
| (step-zerop 36
(rec-Nat 36
't
step-zerop))
| 'nil
值是立刻确定的. 对于36
(也就是(add1 35)
) 而言的zerop
值并不需要, 故我们没有找到它的理由.
在表达式的值实际变得必要之前, 我们都无需对于表达式求值. 否则的话, 对于step-zerop
的参数(rec-Nat 36
't
step-zerop)
求值就太花工夫了, 以至于若记录在相同于 图表中则需另费至少105行. 47 有时懒惰 (laziness) 是一种美德.
(zerop 37)
和(zerop 23)
相同吗?48 是的, 的确.| 'nil
| (step-zerop 22
(rec-Nat 22
't
step-zerop))
| (rec-Nat (add1 22)
't
step-zerop)
| (zerop (add1 22))
以下是gauss
的step.(claim step-gauss
(→ Nat Nat
Nat))
(define step-gauss
(λ (n-1 gaussn-1 )
(+ (add1 n-1) gaussn-1 )))
49 这个定理使用了来源于第3章第44框 的命名约定.
是的, 的确如此.定义step的另一个好处在于我们需要显式地写下其类型, 而不是任其由其于rec-Nat
中的使用方式被推断出来.
50 显式类型使得阅读和理解定义更为容易.
诸如zeropn-1
和gaussn-1
这样的step中的λ
变量几乎(almost) 就是答案了, 其意见于第2章第56框 . 51 好.gauss
的正式定义是什么?
以下就是了.(define gauss
(λ (n)
(rec-Nat n
0
step-gauss)))
base是什么? 52 base即rec-Nat
的第二个参数. 这个情况下, 是0
, 一个Nat
.
的确如此. 54 根据这个定义, (gauss zero)
是什么?
是0
, 因为(rec-Nat zero
0
step-gauss)
和rec-Nat
的第二个参数相同, 即0
.以下是我们找出(gauss (add1 zero))
的值的开始.
| (gauss (add1 zero))
| (step-gauss zero
(rec-Nat zero
0
step-gauss))
| (+ (add1 zero)
(rec-Nat zero
0
step-gauss))
现在请继续下去以找到值. 55 出发.| (iter-Nat (add1 zero)
(rec-Nat zero
0
step-gauss)
step-+)
| (step-+
(iter-Nat zero
(rec-Nat zero
0
step-gauss)
step-+))
| (add1
(iter-Nat zero
(rec-Nat zero
0
step-gauss)
step-+))
其已是值, 鉴于其以add1
为顶.
这个值是规范的吗? 56 不是, 但是以下图表找出了其规范形式.| (add1
(rec-Nat zero
0
step-gauss))
| (add1 0)
这的确是规范的了.
为什么rec-Nat
使用起来总是安全的? 57 这是个好问题.当target以add1
为顶时, rec-Nat
是递归性的. 既然递归不是一个选项, 为什么这是可以接受的?
如果step和第3章第43框 的情况一样不依赖于几乎是答案的参数, 那么我们就已经抵达了一个值. 如果step的确依赖于几乎是答案的参数, 那么我们可以保证递归抵达base, 其总是一个值或者是一个可以成为值的表达式. 58 我们是怎么知道的呢?
因为每个target Nat
要么和zero
相同, 要么和(add1 n )
相同, 其中n 是一个更小的Nat
. 59 我们怎么知道n 更小的?
使得n 相同或者更大的方式是假设target Nat
由无限多的add1
构造而成. 然而, 因为每个函数都是完全的, 我们没有办法做到这点. 类似地, 没有步骤 (step) 可以不是完全的, 因为这里所有 的函数都是完全的, 而每一步骤不过就是应用一个函数. 60 所以说, 为什么我们不能将这种推理风格应用于任意的递归函数呢?
这种推理风格无法以我们的工具表达. 但是, 一旦我们相信step完全的rec-Nat
是一种消去任意作为target的Nat
的方法, 那么每个新定义都是完全的了. 61 还有什么使用rec-Nat
的更有趣的例子吗?
其可以用来定义*
, 意即乘法.换言之, 如果n
和j
是Nat
, 那么
(* n j)
应该是n
和j
之积. 62 *
接受两个Nat
并返回一个Nat
, 故以下是*
的类型.(claim *
(→ Nat Nat
Nat))
对于每一个步骤 (step), +
给当时的答案 (the answer so far) 加上一. *
的每一步会做什么呢? 63 *
会将j
加到第二个参数上去, 也就是几乎是答案的那个参数.
以下是make-step-*
, 其对于每个给定的j
产生一个step函数.(claim make-step-*
(→ Nat
(→ Nat Nat
Nat)))
(define make-step-*
(λ (j)
(λ (n-1 *n-1 )
(+ j *n-1 ))))
64 这看起来和之前的step不太一样.
不论j
为何, make-step-*
都构造了一个合适的step
. 这个step接受两个参数, 因为和rec-Nat
一起使用的step都要接受两个参数, 就和第3章第46框 中的step-zerop
一样.现在定义*
.
65 好.make-step-*
的参数是j
, 其在每一步里被加到积上去. base为0
是因为乘zero
等于0
.
(define *
(λ (n j)
(rec-Nat n
0
(make-step-* j))))
看起来好像make-step-*
在干什么新鲜的事情. 它是一个λ
表达式, 并且产生一个λ
表达式. 其实无需两步过程, 我们可以将嵌套的λ
合并为一个.(claim step-*
(→ Nat Nat Nat
Nat))
(define step-*
(λ (j n-1 *n-1 )
(+ j *n-1 )))
make-step-*
对于任意给定的j
产生一个step. 而且, 尽管看起来不同, make-step-*
和step-*
实际上有着相同的定义 . 66 不可能是相同的定义呀, step-*
明明是一个具有三个参数的λ
表达式.
实际上, 所有的λ
表达式都恰接受一个参数.(λ (x y z)
(+ x (+ y z)))
不过是(λ (x)
(λ (y)
(λ (z)
(+ x (+ y z)))))
的缩写而已. 67 是不是(→ Nat Nat Nat
Nat)
也是某种东西的缩写呢?
其是(→ Nat
(→ Nat
(→ Nat
Nat)))
的缩写. 68 如果一个函数接受三个参数, 那么可以只应用函数于其中一个.可以仅仅应用这个函数于两个参数吗?
如果f 是一个(→ Nat Nat Nat
Nat)
那么(f x y z )
不过是((f x y ) z )
的缩写, 而这又是(((f x ) y ) z )
的缩写. 69 是不是每个函数都恰接受一个参数呢?
诚然如此, 每个函数恰接受一个参数.定义多参数函数为嵌套的单参数函数的过程被称为Currying .
70 以下是*
的正式定义.(define *
(λ (n j)
(rec-Nat n
0
(step-* j))))
尽管step-*
看上去像一个三参数的λ
表达式, 它可以只接受一个参数. rec-Nat
期望其step
是一个恰接受 (get) 两个参数的函数.
以下是计算(* 2 29)
的规范形式的图表的前五行.| (* 2 29)
| ((λ (n j)
(rec-Nat n
0
(step-* j)))
2 29)
| (rec-Nat (add1
(add1 zero))
0
(step-* 29))
| ((step-* 29)
(add1 zero)
(rec-Nat (add1 zero)
0
(step-* 29)))
| ((λ (n-1 *n-1 )
(+ 29 *n-1 ))
(add1 zero)
(rec-Nat (add1 zero)
0
(step-* 29)))
现在, 继续找出规范形式. 71 啊, Currying也有参与.| (+ 29
(rec-Nat (add1 zero)
0
(step-* 29)))
| (+ 29
((step-* 29)
zero
(rec-Nat zero
0
(step-* 29))))
| (+ 29
(+ 29
(rec-Nat zero
0
(step-* 29))))
| (+ 29
(+ 29 0))
| 58
这个图表有遗漏什么步骤吗?
rec-Nat
之律
如果target 是一个Nat
, base 是一个X , step 是一个
(→ Nat X X )
那么
(rec-Nat target
base
step )
是一个
X .
rec-Nat
之第一诫
如果
(rec-Nat zero
base
step )
是一个
X , 那么其和
base 是相同的
X .
rec-Nat
之第二诫
如果
(rec-Nat (add1 n )
base
step )
是一个
X , 那么其和
(step n
(rec-Nat n
base
step ))
是相同的
X .
当然有了, 比如说使得(+ 29 0)
以及作为结果的(+ 29 29)
规范的过程. 72 谢谢.从一开始, 这种图表似乎就会变得很乏味.
就是这样.(claim step-
(→ Nat Nat
Nat))
(define step-
(λ (n-1 almost)
(* (add1 n-1) almost)))
(claim
(→ Nat
Nat))
(define
(λ (n)
(rec-Nat n
0
step- )))
这个定义取什么名字比较合适呢? 73 这个函数总是返回0
.
非常善于观察啊.像Nat
这样的类型的一个缺陷在于其不能说明哪个(which) Nat
是我们所意图的. 之后, 我们将遇到更为强大的类型, 其允许我们讨论特定 的Nat
.
74 那么, 更为强大的类型可以阻止我们像第2章第39框 里那样将five
定义成是9
吗?
当然不行了.类型并不能阻止将five
定义成是9
的愚蠢行径. 但是, 我们可以将我们的一些 想法写成类型.
75 非常有趣.
第4章 小菜一碟, 简单如Pie 在第2章第80框 里, 我们定义Pear
为(claim Pear U )
(define Pear
(Pair Nat Nat))
Pear
的消去子是由car
和cdr
定义的. 1 并且...
Pear
的一个消去子必须要做什么呢?2 这个消去子必须要暴露 (或者说解包) 一个Pear
中的信息.
Pair
的消去子呢? 它必须要做什么?3 Pair
的一个消去子必然要暴露一个Pair
中的信息.
那很接近了. 正如第1章第22框 所言, Pair
单独不是一个表达式, 然而(Pair Nat Nat)
是一个表达式, 并且它有消去子.(Pair Nat Atom)
也有消去子. 4 再次尝试:(Pair Nat Nat)
的一个消去子必然要暴露一个特定(Pair Nat Nat)
中的信息, 一个(Pair Nat Atom)
的消去子必然要暴露一个特定(Pair Nat Atom)
中的信息.
但是, 这意味着Pair
需要许多消去子, 因为就像第2章第36框 一样, 更深的嵌套总是可能的. 5 听起来要记得好多名字的样子.
本会如此!实际上, 还有更好的方式. 我们可以提供一个(Pair A D )
的消去子, 不论A 和D 为何.
6 不论为何? 即便A 是'apple-pie
吗?
好吧, 当然不是绝对任意的.根据第1章第54框 , 除非A 和D 是类型 . 也就是说, A 必须是一个类型且D 必须是一个类型.
7 哇! 那这消去子看起来长什么样呢?
给个例子.(claim kar
(→ (Pair Nat Nat)
Nat))
(define kar
(λ (p)
(elim-Pair
Nat Nat
Nat
p
(λ (a d )
a))))
因为elim-Pair
还未被定义, 所以kar
的定义才会用虚线框起来, 不是别的什么原因. 8 为什么elim-Pair
有这么多参数啊?
在这个定义里, elim-Pair
的前三个参数都是类型Nat
. 前两个参数刻画了要被消去的Pair
的car
部分和cdr
部分的类型. 这第三个参数Nat
描述了内层的λ
表达式的结果类型. 9 内层的λ
表达式的意图是什么?
内层的λ
表达式描述了如何使用p
的值中的信息. 所谓的信息即p
的car
和cdr
部分. 10 为什么d
是黯淡的?
参数名d
之所以是黯淡的, 是因为其出现在内层的λ
表达式里, 却未被使用, 就和第2章第47框 一样.现在请定义一个类似的函数kdr
, 其找出一个Nat
序对的cdr
.
11 几乎和kar
一模一样.(claim kdr
(→ (Pair Nat Nat)
Nat))
(define kdr
(λ (p)
(elim-Pair
Nat Nat
Nat
p
(λ (a d)
d))))
这次之所以a
是黯淡的, 是因为其在内层的λ
表达式中未被使用, 而d
是正常的. 鉴于elim-Pair
还没有定义, kdr
也被虚框包裹, 就和kar
一样.
的确如此.请编写一个叫做swap
的函数, 其交换一个(Pair Nat Atom)
的car
和cdr
.
12 以下是swap
的类型.(claim swap
(→ (Pair Nat Atom)
(Pair Atom Nat)))
现在定义swap
. 13 以下是swap
的定义. 就和kar
和kdr
一样, 其也被包裹在虚框里.(define swap
(λ (p)
(elim-Pair
Nat Atom
(Pair Atom Nat)
p
(λ (a d)
(cons d a)))))
一般说来, elim-Pair
的用法如下:(elim-Pair
A D
X
p
f )
其中p 是一个(Pair A D )
, 而f 根据p 的car
和cdr
来确定整个表达式的值. 这个值必然具有类型X .elim-Pair
的类型是什么?
14 以下是一个猜测, 可能是(→ A D
X
(Pair A D)
(→ A D
X)
X)
因为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框 中的那个表达式实际上并非一个类型.
的确如此.然而, 这种思维过程的确言之成理. 回忆一下, 成为一个
(→ Y X )
是什么意思. 18 一个(→ Y X )
是一个λ
表达式, 当接受一个Y 时, 产生一个X . 它也可以是一个值为这样的λ
表达式的表达式. 我说的对不对呢?
Y 和X 都是类型吗?19 必然如此, 否则(→ Y X )
就不是一个类型了.
在之前我们提出的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 听上去→
没法完成任务.
以下是一个例子.(claim flip
(Π ((A U )
(D U ))
(→ (Pair A D)
(Pair D A))))
(define flip
(λ (A D )
(λ (p)
(cons (cdr p) (car p)))))
24 是不是这意味着一个λ
表达式的类型可以是一个Π
表达式?
好问题.的确可以.
25 如果说Π
和→
都可以用来描述λ
表达式, 那么它们有什么区别呢?
(flip Nat Atom)
的值是什么?26 那必然是λ
表达式(λ (p)
(cons (cdr p) (car p)))
这是因为flip
被定义为是一个λ
表达式, 而其被应用于了两个参数, Nat
和Atom
.
((flip Nat Atom)
(cons 17 'apple))
的值是什么?27 其是(cons 'apple 17)
一个(Pair Atom Nat)
Π
和→
的不同在于一个函数应用于其参数这样的表达式的类型.(flip Nat Atom)
的类型为(→ (Pair Nat Atom)
(Pair Atom Nat))
这是因为当一个由Π
表达式所描述的表达式被应用时, 参数表达式将代替Π
表达式的体 里的参数名 .28 Π
表达式的体和λ
表达式的体有着怎样的联系呢?
Π
表达式和λ
表达式都会引入参数名, 而体是这些名字可以使用的地方.29 什么是参数名 ?
(Π ((A U )
(D U ))
(→ (Pair A D)
(Pair D A)))
在这个Π
表达式里, 参数名是A
和D
. Π
表达式可以有一个或更多的参数名, 而这些参数名可以出现在Π
表达式的体中.30 Π
表达式的体 是什么?
(Π ((A U )
(D U ))
(→ (Pair A D)
(Pair D A)))
在这个Π
表达式里, 体是(→ (Pair A D)
(Pair D A))
这个是(flip
所代表的)λ
表达式的体的类型, 这个体由Π
表达式的体所描述.31 Π
表达式的体里的A
和D
指的是什么?
应用之中律
如果f 是一个
(Π ((Y U )) X )
而
Z 是一个
U , 那么
(f Z )
是一个
X , 其中每个
Y 都已被一致地替换为了
Z .
体里的A
和D
指的是尚不可知的特定类型. 不论哪两个类型A 和D 作为由Π
表达式所描述的λ
表达式的参数, 应用这λ
表达式的结果总是一个(→ (Pair A D )
(Pair D A ))
32 是不是这意味着(flip Atom (Pair Nat Nat))
的类型为(→ (Pair Atom
(Pair Nat Nat))
(Pair (Pair Nat Nat)
Atom))
呢?
对的.但为什么会是如此呢?
33 变量A
和D
被替换以其相应的参数: Atom
和(Pair Nat Nat)
.
(Π ((A U )
(D U ))
(→ (Pair A D)
(Pair D A)))
和(Π ((Lemon U )
(Meringue U ))
(→ (Pair Lemon Meringue)
(Pair Meringue Lemon)))
是相同的类型吗?34 的确如此, 因为正如第2章第21框 所言, 一致地对于变量换名不会改变任何东西的意义.
(Π ((A U )
(D U ))
(→ (Pair A D)
(Pair D A)))
和(Π ((A U )
(D U ))
(→ (Pair
(car
(cons A D))
(cdr
(cons A D)))
(Pair D A)))
是相同的类型吗?35 是的, 因为(car
(cons A D))
和A
是相同的类型, 而(cdr
(cons A D))
和D
是相同的类型.
我们可以这样定义flip
吗?(claim flip
(Π ((A U )
(D U ))
(→ (Pair A D)
(Pair D A))))
(define flip
(λ (C A )
(λ (p)
(cons (cdr p) (car p)))))
36 以下是我的猜测.在这个定义里, 外层的λ
表达式中的(参数)名字和Π
表达式中的名字不同. 似乎这个定义不应该能够成立. A
出现在错误的位置, 而C
既不是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 是的, 其类型应该是(Π ((A U )
(D U )
(X U ))
(→ (Pair A D)
(→ A D
X)
X))
这看起来很像第4章第14框 里的那个类型.
的确如此.elim-Pair
的定义是什么?
41 (claim elim-Pair
(Π ((A U )
(D U )
(X U ))
(→ (Pair A D)
(→ A D
X)
X)))
(define elim-Pair
(λ (A D X )
(λ (p f)
(f (car p) (cdr p)))))
现在kar
不需要虚框了.(define kar
(λ (p)
(elim-Pair
Nat Nat
Nat
p
(λ (a d )
a))))
42 kdr
也是.(define kdr
(λ (p)
(elim-Pair
Nat Nat
Nat
p
(λ (a d)
d))))
swap
也是.43 是的.(define swap
(λ (p)
(elim-Pair
Nat Atom
(Pair Atom Nat)
p
(λ (a d)
(cons d a)))))
尽管一个Π
表达式可以拥有任意数目的参数名, 还是首先描述单参数Π
表达式为类型的情形最为简单.成为一个
(Π ((Y U )) X )
即是成为一个λ
表达式, 当且被应用于一个类型T 时, 将会产生一个表达式, 其类型是将X 中的每个Y 一致地替换以T 的结果. 44 是不是忘了什么?
也可以是一个表达式, 其值是 这样的λ
表达式. 45 不要忘记求值是重要的.这是对于Π
表达式的完整描述了吗?
不, 还不完全.基于单参数的Π
表达式
(Π ((Y U ) (Z U )) X )
该怎样理解呢? 46 它应该意味着一个λ
表达式 (或者能求值至这样的λ
表达式), 当其被应用于两个类型T 和S 时, 将会产生一个表达式, 其类型可由一致地将X 中的每个Y 替换以T 然后再将这新的X 中的每个Z 替换以S 获得.
Π
表达式可以拥有任意数目的参数, 而其所描述的λ
表达式有着相同数目的参数.什么表达式具有类型
(Π ((A U ))
(→ A (Pair A A)))
呢?47 比如说以下这个?(λ (A )
(λ (a)
(cons a a)))
以下我们为熟悉的表达式取了个名字.(claim twin-Nat
(→ Nat
(Pair Nat Nat)))
(define twin-Nat
(λ (x)
(cons x x)))
(twin-Nat 5)
的值是什么? 48 是(cons 5 5)
以下是一个非常类似的定义.(claim twin-Atom
(→ Atom
(Pair Atom Atom)))
(define twin-Atom
(λ (x)
(cons x x)))
(twin-Atom 'cherry-pie)
的值是什么? 49 是(cons 'cherry-pie 'cherry-pie)
这些定义有什么问题? 为什么它们被虚线框起来了?
之于(λ (a)
(cons a a))
Nat
和Atom
没什么特别之处. 因此, 与其对于每个类型写下一个新的定义, 我们不如使用Π
构建一个一般目的性的twin
, 其可以对于任意 的类型成立. 50 以下即是一般目的性的twin
.(claim twin
(Π ((Y U))
(→ Y
(Pair Y Y))))
(define twin
(λ (Y)
(λ (x)
(cons x x))))
(twin Atom)
的值是什么?51 (twin Atom)
是(λ (x)
(cons x x))
(twin Atom)
的类型是什么?52 一致地将(→ Y
(Pair Y Y))
中的每个Y
替换以Atom
就得到了(→ Atom
(Pair Atom Atom))
twin-Atom
的类型和(twin Atom)
的类型之间有什么联系?53 twin-Atom
的类型和(twin Atom)
的类型是相同的类型.
接着, 使用一般性的twin
来定义twin-Atom
.(claim twin-Atom
(→ Atom
(Pair Atom Atom)))
54 可以使用来自于第4章第27框 的技巧.(define twin-Atom
(twin Atom))
(twin-Atom 'cherry-pie)
和((twin Atom) 'cherry-pie)
是相同的(Pair Atom Atom)
吗?55 是的, 并且其值 (也是规范形式) 为(cons 'cherry-pie 'cherry-pie)
对于甜点而言, 这就是双倍了!
第5章 表, 表, 更多的表 Π
怎么样?1 美味至极. 尽管如此, 使用餐巾可以使得用餐不那么狼狈.
在我们开始之前, 你有没有烹饪普罗旺斯杂烩, 吃完两个樱桃派, 尝试使用带图画的餐巾清理一下, 理解rec-Nat
, 以及 休息安睡好 呢? 2 这俨然是期望清单.
是的, 不过这些是很好的期望.(claim expectations
(List Atom))
(define expectations
(:: 'cooked
(:: 'eaten
(:: 'tried-cleaning
(:: 'understood
(:: 'slept nil))))))
3 这段代码有些令我困惑, 在于以下几个方面:::
还没有描述,类型构造子List
还没有描述, 以及 原子'nil
已经作为step-zerop
的一部分被使用.
'nil
和第5章第3框 里的nil
是相同的吗?4 不是, 因为第5章第3框 里的nil
并非一个Atom
, 其不以单引号开头.nil
是一个表达式吗?
List
是一个类型构造子. 如果E 是一个类型, 那么(List E )
是一个类型.5 那么, 成为一个(List E )
的意思是什么呢?
List
之律
如果E 是一个类型, 那么(List E )
是一个类型.
nil
是一个(List Atom)
吗?6 在第5章第3框 里nil
看起来扮演着空列表的角色.
是的, nil
的确是一个(List Atom)
.nil
是一个(List Nat)
吗?
7 似乎不是这样, 因为nil
已经是一个(List Atom)
了.
实际上, nil
也是一个(List Atom)
.nil
是一个(List (List Atom))
吗?
8 是的, 因为(List Atom)
是一个类型, 所以(List (List Atom))
也是一个类型. (List (Pair Nat Atom))
怎么样呢?是不是nil
可以具有任意以上类型之一呢?
的确如此. 9 这意味着nil
也是一个(List 'potato)
咯?
不, 并非如此, 因为'potato
不是一个类型. 10 是不是这和第1章第52框 中(Pair 'olive 'oil)
并非类型出于相同的原因?
是的.之所以(List 'potato)
不是一个类型, 是因为'potato
是一个Atom
, 而不是一个类型.
11 好吧. 这意味着如果E 是一个类型, 那么(List E )
是一个类型, 对吗?
并且, 如果(List E )
是一个类型, 那么nil
是一个(List E )
. 12 好吧.nil
是一个构造子吗?
是的, nil
的确是一个构造子.猜猜(List E )
还有什么其他的构造子.
13 根据expectations
, ::
是另一个构造子.
::
和cons
有什么区别?构造子::
构建一个List
...
14 ..., 但是构造子cons
构建一个Pair
.
可以有序对的列表, 也可以有列表的序对.何时(:: e es )
是一个(List E )
?
15 嗯, es 必须要是一个(List E )
. es 可以是nil
, 而nil
是一个(List E )
.
当然不行! 再试试. 17 我猜一下: e 必须要是一个E , 不然的话E 还没有任何用处.
正确的答案, 错误的原因.之所以e 必须要是一个E , 是因为为了能够使用(List E )
的一个消去子, 我们需要保证这样的列表的每个元素都是一个E .
请将rugbrød
定义为丹麦黑麦面包的成分.
18 那么成分有什么呢?
rugbrød 里的成分有:全黑麦面粉, 黑麦籽粒, 浸泡直至变软, 纯净水, 活跃的酵种, 以及 盐. 19 rugbrød
应该具有什么类型?
(List Atom)
, 因为每个成分都是一个Atom
.20 好的, 以下就是了.(claim rugbrød
(List Atom))
(define rugbrød
(:: 'rye-flour
(:: 'rye-kernels
(:: 'water
(:: 'sourdough
(:: 'salt nil))))))
非常好. 21 是的, rugbrød
非常美味! 尽管如此, 顶上还需要加点什么才好.
让我们回到正题.rugbrød
和5
有什么不同之处?
22 似乎看起来它们就没有相同的地方. 5
由add1
和zero
构成. 而且, 5
也不好吃.
不仅只需要五种成分, rugbrød
甚至不需要揉面. 24 那么, ::
是不是和add1
有什么关系?
::
使得一个列表更大, 而add1
使得一个Nat
更大.nil
是不是和zero
有什么关系?
25 nil
是最小的列表, 而zero
是最小的自然数.列表的消去子和Nat
的消去子是不是看起来差不多?
nil
之律
nil
是一个(List E )
, 不论类型E 为何.
::
之律
如果e 是一个E 而es 是一个(List E )
, 那么(:: e es )
是一个(List E )
.
是的.(rec-Nat target
base
step )
的类型是什么? 26 当target 是一个Nat
,base 是一个X , 且step 是一个(→ Nat X X )
时, rec-Nat
表达式是一个X .
(List E )
的消去子写作(rec-List target
base
step )
并且当target 是一个(List E )
,base 是一个X , 且step 是一个(→ E (List E ) X X )
时, 其是一个X .这和rec-Nat
有什么不同之处?
27 rec-List
的step 比rec-Nat
的step 多一个参数——其接受e , 列表里的一个元素.
说的很好!在这两种情况下, step都接受相应构造子的每个参数, 并且也接受对于更小的值的递归性消去.
28 消去子暴露了值中的信息.
base也暴露了不少关于一个rec-List
的结果的信息. 能给出两个以0
为base的rec-List
用例吗? 29 一个是找出列表的长度, 另一个是找出一个(List Nat)
中的所有Nat
之和.
的确是两个不错的例子.(claim step-
(→ Atom (List Atom) Nat
Nat))
(define step-
(λ (e es n)
(add1 n)))
以此定义,(rec-List nil
0
step- )
之值为何? 30 必然是0
, 因为0
是base, 而base之值必然是rec-List
对于nil
之值.
是的.一个kartoffelmad
是带有toppings
和condiments
的rugbrød
.
(claim toppings
(List Atom))
(define toppings
(:: 'potato
(:: 'butter nil)))
(claim condiments
(List Atom))
(define condiments
(:: 'chives
(:: 'mayonnaise nil)))
31 听起来非常地美味(lækkert) !
rec-List
之律
如果target 是一个(List E )
, base 是一个X , 而step 是一个
(→ E (List E ) X X )
那么
(rec-List target
base
step )
是一个
X .
rec-List
之第一诫
如果
(rec-List nil
base
step )
是一个
X , 那么它和
base 是相同的
X .
rec-List
之第二诫
如果
(rec-List (:: e es )
base
step )
是一个
X , 那么它和
(step e es
(rec-List es
base
step ))
是相同的
X .
的确美味!(rec-List condiments
0
step- )
的值是什么? 32 让我们看看.| (rec-List (:: 'chives
(:: 'mayonnaise
nil))
0
step- )
| (step-
'chives
(:: 'mayonnaise nil)
(rec-List (:: 'mayonnaise
nil)
0
step- ))
| (add1
(rec-List (:: 'mayonnaise
nil)
0
step- ))
规范形式是什么? 不要对于省略中间的表达式保有负担. 33 规范形式为(add1
(add1 zero))
或者更为人知的版本是2
.
这个rec-List
表达式将condiments
中的每个::
都替换为了add1
, 而nil
则被替换为了0
.方框里填什么名字好呢?
34 length
似乎比较恰当.(claim step-length
(→ Atom (List Atom) Nat
Nat))
(define step-length
(λ (e es lengthes )
(add1 lengthes )))
那么以下必然是length
的定义了.(claim length
(→ (List Atom)
Nat))
(define length
(λ (es)
(rec-List es
0
step-length)))
35 但是(:: 17
(:: 24
(:: 13 nil)))
的长度是多少?
那简单, 只需将Atom
替换以Nat
.(claim step-length
(→ Nat (List Nat) Nat
Nat))
(define step-length
(λ (e es lengthes )
(add1 lengthes )))
36 而以下是对于Nat
列表而言的length
.(claim length
(→ (List Nat)
Nat))
(define length
(λ (es)
(rec-List es
0
step-length)))
列表可以包含任意类型的元素, 不仅是Atom
和Nat
.什么可以用来作成一个对于所有类型成立的step-length
版本呢?
37 It's as easy as Π
.(claim length
(Π ((E U ))
(→ (List E)
Nat)))
这个claim
需要一个step.(claim step-length
(Π ((E U ))
(→ E (List E) Nat
Nat)))
38 每个步骤 (step) 时, 长度通过add1
增长.(define step-length
(λ (E )
(λ (e es lengthes )
(add1 lengthes ))))
这个定义使用了和第3章第66框 中的step-*
相同的技巧, 以将step-length
应用于E
.现在定义length
.
39 将E
传递给step-length
将导致其(结果)取三个参数.(define length
(λ (E)
(λ (es)
(rec-List es
0
(step-length E)))))
为什么step-length
中的e
是黯淡的? 40 因为列表中的特定元素在计算列表长度时无需使用.
(length Atom)
的值是什么?41 其为(λ (es)
(rec-List es
0
(step-length Atom)))
这是将内层λ
表达式的体中的每个E
替换以Atom
得到的.
请定义length
的一个特化版本, 其找出一个(List Atom)
的元素数目. 42 这会使用和第4章第54框 中的twin-Atom
定义相同的技巧.(claim length-Atom
(→ (List Atom)
Nat))
(define length-Atom
(length Atom))
这是一个很实用的技巧.现在是时候将一片面包, toppings
以及condiments
组装为一个美味的kartoffelmad .
请定义一个函数, 其合并两个列表.
43 这个定义的类型应该是什么呢?
我们可以将一个(List Nat)
和一个(List (Pair Nat Nat))
合并吗? 44 不能.一个列表中的所有元素都必须具有相同的类型.
列表的元素之类型
一个列表中的所有元素都必须具有相同的类型.
只要两个列表包含相同的元素类型, 它们就可以被合并, 不论这个类型是什么.这是否暗示了append
定义中的类型呢?
45 其类型必然是一个Π
表达式.(claim append
(Π ((E U ))
))
的确如此.剩下来的参数应该是什么呢?
46 这里必须有两个(List E)
参数. 而且, 结果应该也是一个(List E)
. 据此, append
必然是一个λ
表达式.
以下是claim, 现在请开始定义.(claim append
(Π ((E U ))
(→ (List E) (List E)
(List E))))
47 它应该是一个λ
表达式, 但是其体仍然成谜.(define append
(λ (E)
(λ (start end)
)))
方框里应该是什么呢? 48 应该是某种rec-List
.
(append Atom
nil
(:: 'salt
(:: 'pepper nil)))
的值是什么?49 显然应该是(:: 'salt
(:: 'pepper nil))
(append Atom
(:: 'cucumber
(:: 'tomato nil))
(:: 'rye-bread nil))
的规范形式又应该是什么呢?50 那必然是(:: 'cucumber
(:: 'tomato
(:: 'rye-bread nil)))
(append E nil end)
的值应该是end
的值. 因此, append
的最后一个参数end
应该是base.51 step呢?
step的类型由rec-List
之律确定. 其应该能够对于任意的元素类型成立. 52 比如说这个?(claim step-append
(Π ((E U ))
(→ E (List E) (List E)
(List E))))
以之前的框为例, 填充step-append
的剩余部分.(define step-append
(λ (E)
(λ (e es appendes )
)))
(define append
(λ (E)
(λ (start end)
(rec-List start
end
(step-append E)))))
53 如果appendes
是nil
那么step-append
应该产生(:: 'rye-bread nil)
如果appendes
是(:: 'rye-bread nil)
那么step-append
应该产生(:: 'tomato
(:: 'rye-bread nil))
最后, 如果appendes
是(:: 'tomato
(:: 'rye-bread nil))
那么step-append
应该产生(:: 'cucumber
(:: 'tomato
(:: 'rye-bread nil)))
这是很好的推理.所以正确的定义是什么?
54 现在append
就不应该用虚线框住了.(define step-append
(λ (E)
(λ (e es appendes )
(:: e appendes ))))
(define append
(λ (E)
(λ (start end)
(rec-List start
end
(step-append E)))))
append
的定义很像+
.55 是不是有一个iter-List
, 就像是iter-Nat
, 而我们可以用它来定义append
?
没有什么能够阻止我们定义iter-List
, 但是又没有必要, 因为iter-List
能做的, rec-List
也能做, 就像iter-Nat
和which-Nat
能做的, rec-Nat
也能做. 56 好的, 这里我们就用更富表达力的消去子吧.
实际上还可以用另外的方式定义append
, 其将::
替换成别的什么东西. 57 真的能行吗?
当然可以. 除了使用::
以将第一个列表的元素cons 到结果的开头, 也可以使用snoc
将第二个列表的元素加到结果的末尾.例如,
(snoc Atom toppings 'rye-bread)
的值为(:: 'potato
(:: 'butter
(:: 'rye-bread nil)))
snoc
的类型是什么?
58 snoc
的类型是(claim snoc
(Π ((E U ))
(→ (List E) E
(List E))))
step必须要做什么呢?
这个step必须要将列表的当前元素cons 到结果上. 59 啊, 所以说这就像step-append
.
现在请定义snoc
. 60 以下是snoc
.(define snoc
(λ (E)
(λ (start e)
(rec-List start
(:: e nil)
(step-append E)))))
干得好.现在请定义concat
, 其行为与append
类似, 但是在其step中使用了snoc
.
(claim concat
(Π ((E U ))
(→ (List E) (List E)
(List E))))
concat
的类型和append
的类型相同, 因为它们做相同的事情. 61 除了使用snoc
而不是List
的cons ::
, concat
必须要消去的是其第二个(作为参数的)列表.(claim step-concat
(Π ((E U ))
(→ E (List E) (List E)
(List E))))
(define step-concat
(λ (E)
(λ (e es concates )
(snoc E concates e))))
(define concat
(λ (E)
(λ (start end)
(rec-List end
start
(step-concat E)))))
一个列表也可以通过使用snoc
得以反转.reverse
的类型应该是什么?
62 reverse
接受单独一个列表作为参数.(claim reverse
(Π ((E U ))
(→ (List E)
(List E))))
每一步骤 (step) 该做什么呢? 63 对于每个步骤, e
应该通过snoc
添加到反转了的es
的末尾.(claim step-reverse
(Π ((E U ))
(→ E (List E) (List E)
(List E))))
现在请定义step-reverse
和reverse
. 64 以下就是了.(define step-reverse
(λ (E)
(λ (e es reversees )
(snoc E reversees e))))
(define reverse
(λ (E)
(λ (es)
(rec-List es
nil
(step-reverse E)))))
现在是时候来点lækkert 的东西了.(claim kartoffelmad
(List Atom))
(define kartoffelmad
(append Atom
(concat Atom
condiments toppings)
(reverse Atom
(:: 'plate
(:: 'rye-bread nil)))))
kartoffelmad
的规范形式是什么? 65 即(:: 'chives
(:: 'mayonnaise
(:: 'potato
(:: 'butter
(:: 'rye-bread
(:: 'plate nil))))))
我们问的是规范形式, 而非值, 这是很好的. 否则的话, 你原本可能就要在吃的时候自己组转除了'chives
的一切了. 66 反转列表是令人发饿的工作.
第6章 究竟到底是多少? ... 1 在吃了那么多三明治之后, 吃点Π
也是好的.
我们很高兴你问了... 2 我很擅长预测你想要我提出的问题.
当然了, 让我们开始吧.让我们定义一个函数first
, 其找出任意 List
的第一个元素.
3 那不是很简单吗?
之所以不可能, 是因为nil
没有第一个元素... 5 ...因而first
不是完全的.
写一个last
函数怎么样, 它不是找出第一个元素, 而是找出一个List
的最后一个元素? 6 函数last
也不是完全的, 因为nil
也没有最后一个元素.
为了能够写下一个完全函数first
, 我们必须使用一个比List
更加特化 (specific) 的类型. 这样一个更加特化的类型被称为Vec
, 其是向量 (vector) 的缩写, 但是它真的只是带有长度的列表而已.当E 是一个类型而k 是一个Nat
时, 表达式(Vec E k )
是一个类型. 这个Nat
给出了列表的长度.
(Vec Atom 3)
是一个类型吗?
7 类型可以包含不是类型的表达式吗?
正如类型可以是对于某个表达式求值的结果一样 (见第1章第55框 ), 某些类型可以包含其他并非类型的表达式. 8 那么, 之所以(Vec Atom 3)
是一个类型, 是因为Atom
是一个类型而3
显然是一个Nat
.
(Vec
(cdr
(cons 'pie
(List (cdr (cons Atom Nat)))))
(+ 2 1))
是一个类型吗?9 必然是的, 因为(cdr
(cons 'pie
(List (cdr (cons Atom Nat)))))
和(List Nat)
是相同的类型, 且(+ 2 1)
和3
是相同的Nat
. 这意味着该表达式和(Vec (List Nat) 3)
是相同的, 而其 (指后者) 显然是一个类型.
(Vec E zero)
唯一的构造子是vecnil
.10 这是因为vecnil
的长度为zero
吗?
恰是如此.vec::
是
(Vec E (add1 k ))
唯一的构造子. 11 这里的k 是什么?
这里, k 可以是任意的Nat
.当e 是一个E 而es 是一个(Vec E k )
时, (vec:: e es )
是一个(Vec E (add1 k ))
.
12 如果一个表达式是一个(Vec E (add1 k ))
那么其值至少拥有一个元素, 因而有可能定义first
和last
, 是不是这样呢?
对的.(vec:: 'oyster vecnil)
是一个(Vec Atom 1)
吗? 13 是的, 因为'oyster
是一个Atom
而vecnil
是一个(Vec Atom zero)
Vec
之律
如果E 是一个类型而k 是一个Nat
, 那么(Vec E k )
是一个类型.
vecnil
之律
vecnil
是一个(Vec E zero)
.
vec::
之律
如果e 是一个E 而es 是一个(Vec E k )
, 那么(vec:: e es )
是一个(Vec E (add1 k ))
.
(vec:: 'crimini
(vec:: 'shiitake vecnil))
是一个(Vec Atom 3)
吗?14 不是, 因为其并非恰有三个原子的列表.
这和第6章第12框 是怎样联系起来的呢? 15 之所以其并非(Vec Atom 3)
是因为(vec:: 'shiitake vecnil)
不是一个(Vec Atom 2)
为什么(vec:: 'shiitake vecnil)
不是一个(Vec Atom 2)
16 如果它真的是一个(Vec Atom 2)
的话, 那么基于第6章第12框 中的描述vecnil
就会是一个(Vec Atom 1)
为什么不能是这样呢? 17 因为vecnil
是一个(Vec Atom zero)
而1
和zero
不是相同的Nat
.
为什么1
和zero
不是相同的Nat
呢? 18 根据第1章第100框 的解释, 两个Nat
相同, 当其值相同; 而其值相同, 当其均为zero
或者均以add1
为顶(且add1
的参数是相同的Nat
).
现在可以定义first-of-one
了, 其获取一个(Vec E 1)
的第一个元素. 19 但是这可能吗? 到目前为止, 我们还没有Vec
的消去子.
很好的论点. Vec
的两个消去子是head
和tail
. 20 head
和tail
是什么意思呢?
当es
是一个(Vec E (add1 k ))
时,(head es )
是一个E
es 的值可以具有怎样的形式? 21 它不可能是vecnil
, 因为vecnil
只有zero
个元素. 因此, es 以vec::
为顶.
表达式(head
(vec:: a d ))
和a 是相同的E . 22 tail
怎么样呢?
当es
是一个(Vec E (add1 k ))
时,(tail es )
是一个(Vec E k )
23 es 以vec::
为顶.(tail
(vec:: a d ))
和d
是相同的E
吗?
不是, 但(tail
(vec:: a d ))
和d
是相同的(Vec E k )
现在定义first-of-one
. 24 first-of-one
使用head
来找出这仅有的元素.(claim first-of-one
(Π ((E U ))
(→ (Vec E 1)
E)))
(define first-of-one
(λ (E )
(λ (es)
(head es))))
(first-of-one Atom
(vec:: 'shiitake vecnil))
的值是什么?25 是'shiitake
.
(first-of-one Atom vecnil)
的值是什么?26 这个问题是没有意义的, 因为(first-of-one Atom vecnil)
并不由某个类型刻画, 而这又是因为vecnil
并非一个(Vec Atom 1)
完全正确, 这的确是一个毫无意义的问题.现在请定义first-of-two
.
27 那必然非常类似于first-of-one
.(claim first-of-two
(Π ((E U ))
(→ (Vec E 2)
E)))
(define first-of-two
(λ (E )
(λ (es)
(head es))))
(first-of-two Atom
(vec:: 'matsutake
(vec:: 'morel
(vec:: 'truffle vecnil))))
的值是什么?28 这个列表上的都是蘑菇珍品.然而, 问题本身并不意义, 因为这个蘑菇珍品的列表放了三个蘑菇, 而不是恰有两个蘑菇.
很好的论点.是时候定义first-of-three
了.
29 存在first
对于任意长度成立吗?
不行, 因为长度为zero
时并不存在first
的元素. 但是, 可以定义first
, 使其找出任意至少拥有一个元素 的列表的第一个元素. 30 听起来有点困难.
实际上, 并不那么困难.事实上, 简单如...
31 ...Π
?
一人食的蘑菇派. (A mushroom pot pie, for one.) 33 什么是更加灵活的Π
表达式呢?
以下是first
的声明.(claim first
(Π ((E U )
(l Nat))
(→ (Vec E (add1 l ))
E)))
这里有什么新奇之处吗? 34 参数名l
后面跟着的, 是Nat
. 而之前, Π
表达式里参数名后面跟着的总是U .
(→ (Vec E (add1 l ))
E)
中的E
指的是不论什么作为first
的第一个参数的U . 是不是这意味着(add1 l )
中的l
指的是不论什么作为first
的第二个参数的Nat
呢?
Π
之律
表达式
(Π ((y Y )) X )
是一个类型, 当
Y 是一个类型, 且若
y 是一个
Y ,
X 是一个类型.
完全正确. (add1 l )
保证了作为first
的第三个参数的列表至少拥有一个元素.现在请定义first
.
35 以下就是了.(define first
(λ (E l )
(λ (es)
(head es))))
(first Atom 3
(vec:: 'chicken-of-the-woods
(vec:: 'chantrelle
(vec:: 'lions-mane
(vec:: 'puffball vecnil)))))
的值是什么?36 是'chicken-of-the-woods
.但是, 为什么元素的数目是
(add1 l )
而非仅仅是l
呢?
vecnil
中不能找到第一个元素, 因其只有zero
个元素.不论l
如何, (add1 l )
和zero
永远不可能是相同的Nat
, 于是vecnil
不是一个(Vec E (add1 l ))
.
37 我们通过使用更加特化的类型以排除不想要的(实际)参数, 这避免了试图定义一个并非完全的函数.
使用更加特化的类型
通过使用更加特化的类型以排除不想要的参数来使得函数变成完全的.
相同的定义本可写成两个嵌套的Π
表达式的形式.(claim first
(Π ((E U ))
(Π ((l Nat))
(→ (Vec E (add1 l ))
E))))
(define first
(λ (E )
(λ (l )
(λ (es)
(head es)))))
为什么这会是相同的定义? 38 答案是, 因为具有多个参数名的Π
表达式不过就是嵌套的单参数名Π
表达式的简略写法而已.
实际上, 这个定义本也可以写成三个嵌套的Π
表达式的形式.(claim first
(Π ((E U ))
(Π ((l Nat))
(Π ((es (Vec E (add1 l ))))
E))))
(define first
(λ (E )
(λ (l )
(λ (es)
(head es)))))
为什么这 也是相同的定义? 39 真的是相同的定义吗?前一个定义有→
, 但这个定义没有.
实际上, →
是当参数名不在Π
表达式的体中出现时的对于Π
表达式的一种简略写法. 40 啊, 好吧.
→
和Π
类型
(→ Y X )
是对于
(Π ((y Y )) X )
的简略写法, 当
y 在
X 中没有被用到时.
λ
之终律
若当y 是一个Y 时x 是一个X , 那么
(λ (y ) x )
是一个
(Π ((y Y )) X )
应用之终律
如果f 是一个
(Π ((y Y )) X )
而
z 是一个
Y , 那么
(f z )
是一个
X , 其中每个
y 都已被一致地替换为了
z .
λ
终第一诫
如果两个λ
表达式可以通过一致换名使其成为相同的
(Π ((y Y )) X )
那么它们就是相同的.
λ
终第二诫
如果f 是一个
(Π ((y Y )) X )
而
y 不出现在
f 中, 那么
f 和
(λ (y ) (f y ))
是相同的.
类型(Π ((es (Vec E (add1 l ))))
E)
本可以写成(→ (Vec E (add1 l )) E)
因为es
在E
中没有被用到.实际上, 我们本也可以将first
的声明写成一个单独的Π
表达式而不使用→
.
41 之前最后一个版本的first
也可以写成这样:(claim first
(Π ((E U )
(l Nat)
(es (Vec E (add1 l ))))
E))
(define first
(λ (E l es)
(head es)))
这是因为嵌套的Π
表达式本就可以写成单独一个Π
表达式的形式.
更加特化的类型使得我们能够定义first
, 这是我们自己的类型化版本的head
.若是要定义rest
, 这是我们自己的版本的tail
, 是不是也需要更加特化的类型呢?
42 当然如此, 因为(tail vecnil)
和(head vecnil)
同样地毫无意义.
这更特化的类型是什么呢? 43 参数必然以vec::
为顶.因为head
不是tail的一部分, 因此作为结果的Vec
变得更短了.
(claim rest
(Π ((E U )
(l Nat))
(→ (Vec E (add1 l ))
(Vec E l ))))
head
和tail
都是函数, 而所有函数都是完全的. 这意味着它们不可能与List
一起使用, 因为List
无法排除nil
.现在请定义rest
.
44 以下就是了.(define rest
(λ (E l )
(λ (es)
(tail es))))
第7章 完全取决于动机 我们的蘑菇派需要少许豌豆搭配. 是时候定义peas
了, 其产生所需数目的豌豆.什么样的类型表达了这种行为呢?
1 类型是(→ Nat (List Atom))
因为peas
能够产生任意数目的豌豆.
peas
到底应该产生多少豌豆呢?2 看情况咯. (It depends.)
依赖于什么呢? (What does it depend on?) 3 其依赖于豌豆所需要的数目, 即参数.
第7章第1框 中的类型(→ Nat (List Atom))
不够特化. 它没有表达出peas
精确地 产生了其被索取的豌豆数目.4 豌豆的数目是Nat
参数. 以下的类型有用吗?(claim peas
(Π ((how-many-peas Nat))
(Vec Atom how-many-peas)))
是的, 这个类型表达了作为peas
的参数的豌豆数目依赖于其被索取的数目. 这样的类型被称为依赖类型(dependent type) .peas
可以用rec-Nat
写出来吗?
5 当然了.(define peas
(λ (how-many-peas)
(rec-Nat how-many-peas
vecnil
(λ (l-1 peasl-1 )
(vec:: 'pea peasl-1 )))))
依赖类型
由某个不是类型的东西所确定的类型被称为依赖类型(dependent type) .
这peas
的定义并非表达式. 为了能够使用rec-Nat
, base和step的参数peasl-1
必须具有相同的类型. 然而, 这里的peasl-1
可以是一个(Vec Atom 29)
, 但是vecnil
是一个(Vec Atom 0)
.换言之, 当类型依赖于作为target的Nat
时, rec-Nat
就不能使用了.
6 iter-Nat
如何呢?
rec-Nat
可以做任何iter-Nat
可以做的事情.7 有什么更强大的东西可用吗?
那被称为ind-Nat
, 这是induction on Nat
的缩写. 8 什么是ind-Nat
?
ind-Nat
和rec-Nat
很像, 除了其允许base和step中几乎是答案的参数 (这里是peasl-1
) 的类型包括作为target的Nat
.换言之, ind-Nat
用于依赖类型.
9 这里有一个被称为how-many-peas
的Nat
包含在类型(Vec Atom how-many-peas)
里, 它是一个依赖类型吗?
是的, 它依赖于Nat
how-many-peas
.为了与依赖类型打交道, ind-Nat
需要额外的参数: 为了使用ind-Nat
, 有必要陈述base和step几乎是答案的参数的类型是如何 依赖于作为target的Nat
的.
10 这个额外的参数长什么样呢?
这个额外的参数, 被称为动机(motive) , 可以是任意的(→ Nat U )
一个ind-Nat
表达式的类型是动机应用于作为target的Nat
的结果. 11 所以说动机是一个函数, 其体是一个U .
的确如此. 动机解释了为什么 target要被消去.peas
的动机是什么?
12 这是个好问题.不过, 至少其类型是清晰的.
(claim mot-peas
(→ Nat U ))
对于依赖类型应使用ind-Nat
当rec-Nat
或者ind-Nat
表达式的类型依赖于作为target的Nat
时, 应使用ind-Nat
而非rec-Nat
. ind-Nat
表达式的类型是动机 (motive) 应用于target的结果.
以下就是mot-peas
了.(define mot-peas
(λ (k)
(Vec Atom k)))
(mot-peas zero)
的值是什么? 13 它应该是一个U , 因而也是类型, 即(Vec Atom zero)
peas
的base必然具有什么类型呢?14 当然其类型必然为(Vec Atom zero)
因为base的值是当zero
为target时的值.
peas
的base应该是什么呢?15 其必然是vecnil
, 因为vecnil
是仅有的(Vec Atom zero)
这个(类型)也是(mot-peas zero)
.rec-Nat
中的step的目的是什么?
16 在rec-Nat
里, step的参数是n-1
和几乎是答案的东西, 其为消去n-1
得到的值.给定n-1
和几乎是答案的参数, step确定了(add1 n-1)
时的值.
ind-Nat
里的step的参数也是n-1
和几乎是答案的东西.那么, 几乎是答案的东西的类型是什么?
17 几乎是答案的东西的类型是动机应用于n-1
的结果, 因为几乎是答案的东西是target为n-1
时的值.
对于target (add1 n-1)
而言, 值的类型是什么? 18 一个ind-Nat
表达式的类型动机应用于target的结果.
如果动机是mot , 那么step的类型为(Π ((n-1 Nat))
(→ (mot n-1)
(mot (add1 n-1))))
19 举一个ind-Nat
的step的例子呢?
以下是peas
的step.(claim step-peas
(Π ((l-1 Nat))
(→ (mot-peas l-1 )
(mot-peas (add1 l-1 )))))
(define step-peas
(λ (l-1 )
(λ (peasl-1 )
(vec:: 'pea peasl-1 ))))
20 为什么mot-peas
在step-peas
的类型里出现了两次?
好问题.(mot-peas l-1 )
的值是什么?
21 是(Vec Atom l-1 )
.
ind-Nat
之律
如果target 是一个Nat
, mot 是一个
(→ Nat U )
base 是一个
(mot zero)
, 而
step 是一个
(Π ((n-1 Nat))
(→ (mot n-1)
(mot (add1 n-1))))
那么
(ind-Nat target
mot
base
step )
是一个
(mot target )
.
ind-Nat
之第一诫
ind-Nat
表达式
(ind-Nat zero
mot
base
step )
和
base 是相同的
(mot zero)
.
ind-Nat
之第二诫
ind-Nat
表达式
(ind-Nat (add1 n )
mot
base
step )
和
(step n
(ind-Nat n
mot
base
step ))
是相同的
(mot (add1 n ))
.
这是peasl-1
的类型, 其描述了包含l-1
个豌豆的列表.
(mot-peas (add1 l-1 ))
的值如何, 其又意味着什么? 22 其是(Vec Atom (add1 l-1 ))
其描述了包含(add1 l-1 )
个豌豆的列表.
自然数上的归纳
通过给出零时的值以及将n 时的值转换为n + 1 时的值的方法来构造对于任意自然数的值被称为自然数上的归纳 .
step必须要能够根据对于l-1
的值构造出对于(add1 l-1 )
的值.再次观察step-peas
的类型, 其在文中到底为何意?
23 不论l-1
是什么Nat
, step-peas
总是接受一个(Vec Atom l-1 )
然后产生一个(Vec Atom (add1 l-1 ))
这是通过cons 一个'pea
到前端完成的.
base将zero
替换以vecnil
因为vecnil
是仅有的(Vec Atom zero)
step-peas
将一个add1
替换成什么呢? 24 step-peas
将每个add1
替换以一个vec::
, 就像第5章第34框 中的length
将一个列表中的每个::
替换以add1
.
现在定义peas
是可能的了, 只需使用mot-peas
和step-peas
. 25 以下是我们的定义.(define peas
(λ (how-many-peas)
(ind-Nat how-many-peas
mot-peas
vecnil
step-peas)))
(peas 2)
的值是什么?以下是最初的三个计算步骤.
| (peas
(add1
(add1 zero)))
| (ind-Nat (add1
(add1 zero))
mot-peas
vecnil
step-peas)
| (step-peas (add1 zero)
(ind-Nat (add1 zero)
mot-peas
vecnil
step-peas))
现在, 请找出其值. 记得参数(有时)无需被求值.26 以下就是了.| (vec:: 'pea
(ind-Nat (add1 zero)
mot-peas
vecnil
step-peas))
而且, 我们最终可以找出其规范形式.| (vec:: 'pea
(step-peas zero
(ind-Nat zero
mot-peas
vecnil
step-peas)))
| (vec:: 'pea
(vec:: 'pea
(ind-Nat zero
mot-peas
vecnil
step-peas)))
| (vec:: 'pea
(vec:: 'pea vecnil))
这就是规范形式了.
如果动机的参数是黯淡的, 那么说明ind-Nat
表现得就像是rec-Nat
. 现在请定义一个函数also-rec-Nat
, 其使用ind-Nat
, 而行为正如rec-Nat
.(claim also-rec-Nat
(Π ((X U))
(→ Nat
X
(→ Nat X X)
X)))
27 因为类型并不依赖于target, 所以k
是黯淡的.(define also-rec-Nat
(λ (X)
(λ (target base step)
(ind-Nat target
(λ (k ) X)
base
step))))
就像first
可以找出一个列表的第一个元素, last
可以找出最后一个元素.last
的类型应该是什么?
28 此列表必然是非空的, 这意味着我们可以应用和first
的类型相同的想法.(claim last
(Π ((E U )
(l Nat))
(→ (Vec E (add1 l ))
E)))
如果一个列表只包含一个Atom
, 那么哪个Atom
是最后一个呢? 29 显然只有一种可能.
(last Atom zero
(vec:: 'flour vecnil))
的规范形式是什么?30 以下是我的猜测. 这个问题没有意义, 因为列表包含的是一个元素而不是零个元素.
(last Atom zero)
的类型是什么?请记得Currying.
31 (last Atom zero)
的类型为(→ (Vec Atom (add1 zero))
Atom)
因此, 前一个框中的问题, 实际上是有意义的!
(last Atom zero
(vec:: 'flour vecnil))
的规范形式是什么?32 那必然是'flour
.
的确如此.使用这个洞察, base-last
的类型是什么?
33 base在(作为target的)Nat
为zero
时使用.(claim base-last
(Π ((E U ))
(→ (Vec E (add1 zero))
E)))
base-last
的定义是什么?34 其使用head
以获得一个(Vec E (add1 zero))
中的唯一元素.(define base-last
(λ (E )
(λ (es)
(head es))))
这是我们第一次遇到base是一个函数的情况. 根据动机, base和step的几乎是答案的参数都是函数.当base是一个函数而step将一个几乎是答案的函数转换为另一个函数时, 整个ind-Nat
表达式当然也是在构造一个函数.
35 λ
表达式是值吗?
ind-Nat
表达式的类型是动机应用于target的结果, 这个target是要被消去的Nat
.当抵达base时, 作为target的Nat
是什么?
37 应该是zero
, 这就是base的意义所在.
动机应用于zero
的结果是base的类型.请找出一个可以用作动机的表达式.
38 (Π ((E U )
(k Nat))
(→ (Vec E (add1 k ))
E ))
怎么样呢? 将E 填上列表元素的类型而k 填上zero
就得到了base的类型.
ind-Nat
的base的类型
在ind-Nat
之中, base的类型是动机应用于作为target的zero
的结果.
很接近了, 但并不那么正确.ind-Nat
的动机会被应用于zero
, 但是应用一个Π
表达式并无意义. ind-Nat
的动机应该是一个函数, 而非函数的类型.
39 啊, 所以说那必然是(λ (E k)
(→ (Vec E (add1 k))
E))
其可以被应用于列表元素的类型和zero
以得到base的类型.
现在定义last
的动机.(claim mot-last
(→ U Nat
U ))
40 以下就是了.(define mot-last
(λ (E k)
(→ (Vec E (add1 k))
E)))
(mot-last Atom)
的类型和值分别是什么?41 类型是(→ Nat U )
而值为(λ (k)
(→ (Vec Atom (add1 k))
Atom))
这就像什么? 42 第4章第54框 里的twin-Atom
. 应用mot-last
于一个U 将产生一个适合用于ind-Nat
的动机.
此时base的类型的值是什么? 这个类型即(mot-last Atom zero)
43 应该是类型(→ (Vec Atom (add1 zero))
Atom)
(mot-last Atom (add1 l-1 ))
的值是什么?44 应该是(→ (Vec Atom (add1
(add1 l-1 )))
Atom)
last
的step的目的何在?45 last
的step将l-1
时的几乎答案转换为对于(add1 l-1 )
的答案.换言之, last
的step将一个获取一个
(Vec E (add1 l-1 ))
中的最后一个元素的函数变为一个获取一个(Vec E (add1 (add1 l-1 )))
中的最后一个元素的函数. 为什么这里有两个add1
?
外层的add1
作为类型的一部分是为了保证送给last
的列表至少拥有一个元素. 内层的add1
来源于将(add1 l-1 )
传递给mot-last
. 46 外层的add1
使得函数完全, 而内层的add1
是出于ind-Nat
之律.
step的类型是什么? 47 step的类型必然是(→ (→ (Vec E (add1 l-1 ))
E)
(→ (Vec E (add1
(add1 l-1 )))
E))
因为step必须要根据一个(mot-last E l-1 )
构造出一个(mot-last E (add1 l-1 ))
这个类型如何以文字解释? 48 step将一个对于(add1 l-1 )
而言的last
函数转换为一个对于(add1 (add1 l-1 ))
而言的last
函数.
ind-Nat
的step的类型
在ind-Nat
之中, step必须要接受两个参数: 某个类型为Nat
的n
和一个几乎是答案的东西, 其类型是动机mot 应用于n
的结果. step返回的答案的类型是动机应用于(add1 n)
的结果. step的类型是:
(Π ((n Nat))
(→ (mot n)
(mot (add1 n))))
以下是step-last
的声明.(claim step-last
(Π ((E U)
(l-1 Nat))
(→ (mot-last E l-1 )
(mot-last E (add1 l-1 )))))
现在请定义step-last
. 49 lastl-1
是几乎正确的函数, 但是只是对于拥有(add1 l-1 )
个元素的列表而言的, 因而其接受拥有(add1 (add1 l-1 ))
个元素的列表的tail
作为参数.(define step-last
(λ (E l-1 )
(λ (lastl-1 )
(λ (es)
(lastl-1 (tail es))))))
内层的λ
表达式的参数es
的类型是什么? 50 es
是一个(Vec E (add1 (add1 l-1 )))
为什么这是es
的类型呢? 51 整个这内层的λ
表达式的类型为(mot-last E (add1 l-1 ))
而这个类型和(→ (Vec E (add1
(add1 l-1 )))
E)
是相同的类型. 因此, 该λ
表达式的参数, 即es
, 应该是一个(Vec E (add1 (add1 l-1 )))
聪明.(tail es)
的类型是什么?
52 (tail es)
的类型为(Vec E (add1 l-1 ))
其是几乎准备好的函数的适切参数的类型.
第7章第49框 中的较外层λ
表达式里的lastl-1
的类型是什么?53 lastl-1
的类型为(→ (Vec E (add1 l-1 ))
E)
即(mot-last E l-1 )
之值.
现在是时候定义last
了, 其claim
出现在第7章第28框 之中. 54 以下就是了.(define last
(λ (E l )
(ind-Nat l
(mot-last E)
(base-last E)
(step-last E))))
(last Atom 1
(vec:: 'carrot
(vec:: 'celery vecnil)))
的规范形式是什么?以下是我们计算的开始.
| (last Atom (add1 zero)
(vec:: 'carrot
(vec:: 'celery vecnil)))
| ((ind-Nat (add1 zero)
(mot-last Atom)
(base-last Atom)
(step-last Atom))
(vec:: 'carrot
(vec:: 'celery vecnil)))
| ((step-last Atom zero
(ind-Nat zero
(mot-last Atom)
(base-last Atom)
(step-last Atom)))
(vec:: 'carrot
(vec:: 'celery vecnil)))
55 感谢帮助. 以下是更多的计算.| ((λ (es)
((ind-Nat zero
(mot-last Atom)
(base-last Atom)
(step-last Atom))
(tail es)))
(vec:: 'carrot
(vec:: 'celery vecnil)))
| ((ind-Nat zero
(mot-last Atom)
(base-last Atom)
(step-last Atom))
(tail
(vec:: 'carrot
(vec:: 'celery
vecnil))))
| (base-last Atom
(tail
(vec:: 'carrot
(vec:: 'celery
vecnil))))
这是规范形式吗? 56 并非如此, 还需要更多的步骤.| ((λ (es)
(head es))
(tail
(vec:: 'carrot
(vec:: 'celery
vecnil))))
| (head
(tail
(vec:: 'carrot
(vec:: 'celery
vecnil))))
| (head
(vec:: 'celery vecnil))
| 'celery
漂亮.现在休息一下, 或许可以来点养生的蘑菇派.
57 听起来很不错的样子.
猜猜drop-last
的意思是什么. 58 想必是丢掉一个Vec
里的最后一个元素.
猜得不错!(drop-last Atom 3 vecnil)
是什么?
59 它不由某个类型描述, 正如(first Atom 3 vecnil)
(last Atom 3 vecnil)
(rest Atom 3 vecnil)
也不由类型描述一样.这个类型必然包含一个其中带有add1
的Vec
.
这是坚实的思考方式.drop-last
的类型是什么?
60 drop-last
使得列表的长度收缩了一.(claim drop-last
(Π ((E U )
(l Nat))
(→ (Vec E (add1 l ))
(Vec E l ))))
base-drop-last
是什么?61 base应该找出拥有一个元素的列表的drop-last
也就是vecnil
因为最后一个元素就是那唯一的元素.(claim base-drop-last
(Π ((E U ))
(→ (Vec E (add1 zero))
(Vec E zero))))
(define base-drop-last
(λ (E )
(λ (es )
vecnil)))
以下对于base-drop-last
的定义也能成立吗?(define base-drop-last
(λ (E )
(λ (es)
(tail es))))
62 这个定义总是能产生相同的值, 但是没能同样清晰地传达想法.我们的意图在于base-drop-last
总是忽略列表的最后一个元素.
听起来很对.不过为什么上面的定义要用虚线框住呢?
63 只是得到正确的答案是没有价值的, 如果我们不知道 为什么正确的话. 理解答案至少和拥有正确答案同等重要.
可读的表达式
只是得到正确的答案是没有价值的, 如果我们不知道 为什么正确的话. 理解答案至少和拥有正确答案同等重要.
看来某人一直在认真听讲!mot-drop-last
是什么?
64 mot-drop-last
需要表达drop-last
总是在构造长度小一的Vec
.(claim mot-drop-last
(→ U Nat
U ))
(define mot-drop-last
(λ (E k)
(→ (Vec E (add1 k))
(Vec E k))))
太快了, 请解释一下. 65 在ind-Nat
之中, 应用动机于zero
得到的就是base的类型. 这意味着我们可以反过来将第7章第61框 中base的类型里的zero
替换成参数k
以mot-drop-last
.
这是一个敏锐的观察. 这种方法并不总是成立, 但的确是一个好的起点.将特定的常量替换以变量并用该变量的λ
包裹的行为被称为抽象出常量(abstracting over constants) , 并且我们经常使用这种方法. 这里, 动机对于base-drop-last
里的zero
进行抽象.
66 step-drop-last
的类型遵循ind-Nat
之律.(claim step-drop-last
(Π ((E U )
(l-1 Nat))
(→ (mot-drop-last E l-1 )
(mot-drop-last E (add1 l-1 )))))
那么step-drop-last
该如何定义? 67 想出step-drop-last
需要动动脑子.(define step-drop-last
(λ (E l-1 )
(λ (drop-lastl-1 )
(λ (es)
(vec:: (head es)
(drop-lastl-1
(tail es)))))))
这是令人熟悉的归纳模式:step-drop-last
将一个对于(Vec E (add1 l-1 ))
成立的drop-last
转换为一个对于(Vec E (add1 (add1 l-1 )))
成立的drop-last
这个转换是如何工作的呢? 68 正如step-last
使用其几乎是答案的参数, 即lastl-1
, 以找出它自己的(tail es)
的last
,step-drop-last
也使用其几乎是答案的参数, 即drop-lastl-1
, 以找出它自己的(tail es)
的drop-last
.根据mot-drop-last
, 由step-drop-last
产生的函数必须还要给那个列表添加一个元素. 因此, 第7章第67框 里的最内层的λ
表达式使用vec::
将es
的head
cons 到
(drop-lastl-1 (tail es))
上去.
drop-last
的claim
出现在第7章第60框 里.现在请定义drop-last
.
69 这个事情只是把我们已经得到的碎片拼接起来.(define drop-last
(λ (E l )
(ind-Nat l
(mot-drop-last E)
(base-drop-last E)
(step-drop-last E))))
是的, drop-last
现在已得到定义.有时找出之后会被用到的函数是方便的. 例如, (drop-last Atom 2)
可以找出任意由三个Atom
构成的列表的前两个元素.
请找出
(drop-last Atom
(add1
(add1 zero)))
之值以阐明这是如何成立的. 70 以下是找出其值的图表.| (drop-last Atom
(add1
(add1 zero)))
| (ind-Nat (add1
(add1 zero))
(mot-drop-last Atom)
(base-drop-last Atom)
(step-drop-last Atom))
| (step-drop-last
Atom (add1 zero)
(ind-Nat (add1 zero)
(mot-drop-last Atom)
(base-drop-last Atom)
(step-drop-last Atom)))
| (λ (es)
(vec:: (head es)
((ind-Nat (add1 zero)
(mot-drop-last Atom)
(base-drop-last Atom)
(step-drop-last Atom))
(tail es))))
很好——λ
表达式的确也是值. 为了找出规范形式, 则需要更多的步骤. 以下则是第一步.| (λ (es)
(vec:: (head es)
((step-drop-last Atom zero
(ind-Nat zero
(mot-drop-last Atom)
(base-drop-last Atom)
(step-drop-last Atom)))
(tail es))))
现在轮到你来找出规范形式了. 71 在第6步里, es
被一致地换名为了ys
, 这是为了明确这内层的λ
表达式有着自己的变量.| (λ (es)
(vec:: (head es)
((λ (ys)
(vec:: (head ys)
((ind-Nat zero
(mot-drop-last Atom)
(base-drop-last Atom)
(step-drop-last Atom))
(tail ys))))
(tail es))))
将es
换名为ys
其实当然是没有必要的, 因为变量名总是由包裹它的最内层λ
所绑定, 不过使得表达式更容易理解总归是一个想法.以下是新的两步.
| (λ (es)
(vec:: (head es)
(vec:: (head (tail es))
((ind-Nat zero
(mot-drop-last Atom)
(base-drop-last Atom)
(step-drop-last Atom))
(tail (tail es))))))
| (λ (es)
(vec:: (head es)
(vec:: (head (tail es))
(base-drop-last Atom
(tail (tail es))))))
72 几乎就要到终点了.| (λ (es)
(vec:: (head es)
(vec:: (head (tail es))
((λ (ys ) vecnil)
(tail (tail es))))))
| (λ (es)
(vec:: (head es)
(vec:: (head (tail es))
vecnil)))
规范形式显然要比最初的表达式更容易理解!
C'est magnifique! 想必你已经累了.73 的确如此, 而且也很饿.
课间: 一次吃一块 有时该怎么写下Pie表达式并不是显而易见的. 1 那就是空白方框的作用, 是吧?
的确如此. 然而, 绝大多数键盘都不容易输入空白方框.与其输入空白方框, 不如使用TODO
形式来保留表达式里之后要完成的部分.
2 什么是TODO
?
TODO
是一个表达式, 它是代表某个其他表达式的占位符. 一个TODO
可以具有任意的类型, 而Pie会追踪哪个TODO
应该具有哪个类型.3 TODO
可以怎么用呢?
每个TODO
都来自于某个特定的位置. 这里, 我们以框号引用它们. 在本书之外使用Pie时, 则有其他合适的手段.试着输入
(claim peas
TODO)
看看会发生什么. 4 Pie会回之以Frame 4:2.3: TODO: U
而其所提及的TODO
的确是出现在第4框的表达式的第2行第3列位置上的一个U .
现在试试(claim peas
(Pi ((n Nat))
TODO))
其更接近于第7 章里的peas
的类型. 5 Pie会回之以Frame 5:3.5: TODO:
n : Nat
--------------
U
但是这个水平线是什么意思呢?
当Pie回之以TODO
所被期望具有的类型时, 它也会包括可以在TODO
的位置上使用的变量的类型. 6 水平线上面的n : Nat
的意思是变量n
是一个Nat
.
是这样的.现在试试
(claim peas
(Pi ((n Nat))
(Vec Atom n)))
(define peas
TODO)
其中的TODO
出现在定义的位置. 7 Pie会回之以Frame 7:5.3: TODO:
(Π ((n Nat))
(Vec Atom n))
这也就是被claim
的那个类型.
当TODO
被λ
包裹时, Pie会如何回应呢?(claim peas
(Pi ((n Nat))
(Vec Atom n)))
(define peas
(λ (n)
TODO))
8 水平线上会有和n
相关的一行.
试试看看. 9 以下是发生的事情.Frame 8:6.5: TODO:
n : Nat
--------------
(Vec Atom n)
然后该怎么写呢? 10 因为'pea
的数量依赖于n
, 所以需要ind-Nat
.
Pie对于以下版本的peas
会作何回应呢?(claim peas
(Pi ((n Nat))
(Vec Atom n)))
(define peas
(λ (n)
(ind-Nat n
(λ (k)
(Vec Atom k))
TODO
TODO)))
11 每一个TODO
都具有ind-Nat
所期望的类型.Frame 11:9.7: TODO:
n : Nat
--------------
(Vec Atom 0)
Frame 11:10.7: TODO:
n : Nat
--------------------
(Π ((n-1 Nat))
(→ (Vec Atom n-1)
(Vec Atom
(add1 n-1))))
那么该将这里的两个TODO
替换成什么呢? 12 第一个TODO
是一个(Vec Atom 0)
, 故vecnil
是合适的. 第二个TODO
应该是一个二参数的函数, 以λ
构造, 应该使用vec::
来给n-1
个豌豆添加一个'pea
.
很好的选择, 那么Pie对于以下版本该作何回应?(claim peas
(Pi ((n Nat))
(Vec Atom n)))
(define peas
(λ (n)
(ind-Nat n
(λ (k)
(Vec Atom k))
vecnil
(λ (n-1 peas-of-n-1)
(vec:: TODO TODO)))))
13 vec::
之律确定了每个TODO
的类型.Frame 13:11.16: TODO:
n : Nat
n-1 : Nat
peas-of-n-1 : (Vec Atom n-1)
------------------------------
Atom
Frame 13:11.21: TODO:
n : Nat
n-1 : Nat
peas-of-n-1 : (Vec Atom n-1)
------------------------------
(Vec Atom n-1)
现在请将最后的两个TODO
替换. 14 以下即是最终的定义.(claim peas
(Pi ((n Nat))
(Vec Atom n)))
(define peas
(λ (n)
(ind-Nat n
(λ (k)
(Vec Atom k))
vecnil
(λ (n-1 peas-of-n-1)
(vec:: 'pea
peas-of-n-1)))))
第8章 选一个数字, 任意的数字 蘑菇派怎么样? 1 很好吃, 就是有点胀人. 这里有什么不那么胀人的吃的吗?
(sandwich 'hoagie)
怎么样呢?2 那应该还是可控的.
现在开始.| (+ (add1 zero))
| (λ (j)
(iter-Nat (add1 zero)
j
step-+))
这已经是一个值了. 4 规范形式需要一点更多的工作.| (λ (j)
(step-+
(iter-Nat zero
j
step-+)))
| (λ (j)
(add1
(iter-Nat zero
j
step-+)))
| (λ (j)
(add1 j))
以下是一个定义.(claim incr
(→ Nat Nat))
(define incr
(λ (n)
(iter-Nat n
1
(+ 1))))
(incr 0)
的规范形式是什么? 5 只需三个步骤.| (incr zero)
| (iter zero
1
(+ 1))
| 1
规范形式为1
, 即(add1 zero)
.
(incr 3)
的规范形式是什么?6 计算该规范形式需要更多的步骤. 以下是为了找出其值的最初几步.| (iter-Nat 3
1
(+ 1))
| (+ 1
(iter-Nat 2
1
(+ 1)))
| (add1
(iter-Nat 2
1
(+ 1)))
这的确是值. 但是规范形式又是什么呢? 再走几步.| (add1
(+ 1
(iter-Nat (add1 zero)
1
(+ 1))))
| (add1
(add1
(iter-Nat (add1 zero)
1
(+ 1))))
7 规范形式是4
.| (add1
(add1
(+ 1
(iter-Nat zero
1
(+ 1)))))
| (add1
(add1
(add1
(iter-Nat zero
1
(+ 1)))))
| (add1
(add1
(add1
1)))
(+ 1)
和incr
之间有什么关系呢?8 它们总能得到相同的答案, 不论参数为何.
这意味着(+ 1)
和incr
是相同的(→ Nat Nat)
咯? 9 它们是相同的, 如果它们有着相同的规范形式.(+ 1)
的规范形式为
(λ (n)
(add1 n))
而incr
的规范形式为(λ (n)
(iter-Nat n
1
(λ (j)
(add1 j))))
因此它们并非相同.
是这样的.即便它们并非相同, 它们总是能够得到相同答案的事实其实可以写成一个类型.
10 但是相同性难道不是判断的一种嘛? 这也不是类型啊.
相同性的确是一种判断. 但是, 通过一个新的类型构造子, 类型可以表达被称为相等性(equality) 的新想法.将
incr
和(+ 1)
总是能够得到相同的答案 写成一个类型是很消耗能量的. 你最好先吃这个(sandwich 'grinder)
使你能量满满. 11 另一个三明治?好吧.
一个表达式(= X from to )
是一个类型, 如果X
是一个类型, 且from
是一个X , 且to
是一个X . 12 这是另一种构造依赖类型的方式吗?
=
之律
一个表达式
(= X from to )
是一个类型, 如果
X 是一个类型,
from 是一个
X , 而
to 也是一个
X .
是的, =
是另一种构造依赖类型的方式, 因为from 和to 无需是类型.因为from 和to 是方便的名字, 一个=
表达式的相应部分被称为from 和to .
13 好的.
from 和to 应理解为名词
因为from 和to 是方便的名字, 一个=
表达式的相应部分被称为from 和to .
(= Atom 'kale 'blackberries)
是一个类型吗?14 是的, 因为Atom
是一个类型, 而'kale
和'blackberries
都是Atom
.
(= Nat (+ 1 1) 2)
是一个类型吗?15 是的, 因为Nat
是一个类型而(+ 1 1)
和2
都是Nat
.
(= (car (cons Nat 'kale))
17
(+ 14 3))
是一个类型吗?16 是的, 的确如此, 因为(car (cons Nat 'kale))
和Nat
是相同的类型, 而from 和to 都是Nat
.
(= (car (cons Nat 'kale))
15
(+ 14 3))
是一个类型吗?17 是的, 的确如此. 第8章第12框 只是要求from 和to 都是Nat
, 但无需是相同的Nat
.但是=
的目的何在?
为了理解=
, 首先需要理解看待类型的另一种视角.类型也可以读作陈述(statement) .
18 (= Atom 'apple 'apple)
怎么读作陈述呢?
类型(= Atom 'apple 'apple)
可以这么读:表达式'apple
和'apple
是相等的Atom
.(= Nat (+ 2 2) 4)
怎么读作陈述呢? 19 二加二等于四 怎么样?
是的, 很好. 20 三加四等于七 和(+ 3 4)
和7
是相同的Nat
有什么区别?
陈述三加四等于七 是另一种写下类型(= Nat (+ 3 4) 7)
的方法, 其是 一个表达式, 但是(+ 3 4)
和7
是相同的Nat
是一个关于 表达式的判断.第1章第12框 描述了判断. 一个判断不是一个表达式, 而是一个人在思考表达式时所采取的态度.
21 以下是一个判断:三加四等于七 是一个类型.
你的观察十分敏锐.=
表达式不仅是类型, 还可以读作陈述.
22 还有其他的什么类似的吗?
一个Π
表达式可以读作对于每个 (for every) . 考虑以下例子:(Π ((n Nat))
(= Nat (+ 1 n) (add1 n)))
可以读作对于每个Nat
n , (+ 1 n )
等于(add1 n )
. 23 好的, 但是将类型读作陈述的目的何在?
如果一个类型可以读作一个陈述, 那么判断该陈述为真意味着存在一个具有该类型的表达式. 也就是说, 言称(+ n 0)
和n
是相等的Nat
(为真) 意味着存在一个表达式具有类型(= Nat (+ n 0) n)
. 24 这是不是意味着真性 (truth) 需要证据?
实际上可以走得更远. 真性意味着 拥有证据. 这种证据被称为一个证明(proof) . 25 每个类型都可以读作陈述吗?
在原则上, 的确是可以的. 但是, 许多类型作为陈述并不有趣. 26 什么使得一个陈述有趣呢?
一个人对于一个陈述感兴趣, 那么它就变得有趣了. 但是, 最有趣的陈述来源于依赖类型. Nat
不是一个有趣的陈述, 因为它太容易证明了. 27 Nat
怎么被证明呢?
干得好. 你已经有了一个证明. 29 这并不十分有趣.
另一种思考陈述的方法是将其当作对于证明的期待, 或者是当作要被解决的问题. 31 见到一个claim
之后, 期待一个定义也很正常.
第8章第12框 解释了何时一个=
表达式是一个类型, 但是它没有说这样的类型的值会是什么.32 这里, 值 指的是和证明 相同的东西, 对吗?
完全正确.=
只有一种构造子, 而它叫做same
. same
接受一个参数.
33 same
怎么用呢?
如果e 是一个X , 那么表达式(same e )
是一个(= X e e )
34 举个例子呢?
same
之律
如果e 是一个X , 那么表达式(same e )
是一个(= X e e )
.
表达式(same 21)
是一个(= Nat (+ 17 4) (+ 11 10))
35 这看起来似乎不对.在第8章第34框 里, same
的参数以及=
的参数from 和to 都是应该是等同的, 但是这里的
21
(+ 17 4)
(+ 11 10)
看起来相当不同.
(+ 17 4)
和(+ 11 10)
都与21
是相同的Nat
, 所以它们三个是相同的.36 是不是这意味着(same (incr 3))
是一个(= Nat (+ 2 2) 4)
呢?
是的.(same (incr 3))
是(= Nat (+ 2 2) 4)
的一个证明.same
之律使用了两次e 以要求
from 和to 是相同的X . 以类型构造子=
与其构造子same
, 表达式现在可以陈述之前只能被判断的想法. 37 为什么这如此重要呢?
表达式可以与其他表达式一起使用.通过将Π
和=
组合, 我们可以表达对于任意Nat
为真的陈述, 但是我们只能对于特定的Nat
作出判断. 以下是一个例子:
(claim +1=add1
(Π ((n Nat))
(= Nat (+ 1 n) (add1 n))))
38 +1=add1
的定义显然有一个位于顶层的λ
, 因为其类型的顶层有一个Π
.(define +1=add1
(λ (n)
))
这是一个坚实的开始, 但是方框里应该填什么呢? 39 根据λ
之律,(= Nat (+ 1 n) (add1 n))
是这个λ
表达式的体的类型.
是的, 这个方框里填的应该是一个(= Nat (+ 1 n) (add1 n))
那么, 这个方框的类型的规范形式是什么? 40 方框的类型的规范形式是(= Nat (add1 n) (add1 n))
因为(+ 1 n)
的规范形式为(add1 n)
我知道了, 所以说第8章第38框 的方框里填的表达式应该是(same (add1 n))
正确.现在请完成定义.
41 以下就是了.(define +1=add1
(λ (n)
(same (add1 n))))
+1=add1
证明了什么陈述呢?42 这个陈述是对于每个Nat
n , (+ 1 n )
等于(add1 n )
.
以下是另外一个陈述.对于每个Nat
n , (incr n )
等于(add1 n )
. 请将其翻译为类型. 43 让我们称其为incr=add1
.(claim incr=add1
(Π ((n Nat))
(= Nat (incr n) (add1 n))))
现在请定义incr=add1
. 44 难道这不就像+1=add1
吗?(define incr=add1
(λ (n)
(same (add1 n))))
并非如此.(incr n)
的规范形式是什么? 45 | (incr n)
| (iter-Nat n
1
(+ 1))
| (iter-Nat n
1
(λ (j)
(add1 j)))
这个规范形式和(add1 n)
不 是相同的Nat
.
是这样的. 这个规范形式是中立的.什么是中立表达式?
46 中立表达式描述于第2章第25框 .中立表达式是那些尚不能被求值的表达式.
为什么(iter-Nat n
1
(λ (j)
(add1 j)))
是中立的? 47 因为iter-Nat
在target为zero
时选择base, 而当target以add1
为顶时选择step, 但是n
并不属于这两种情况.
一种更加精确的定义中立表达式 的方式是从最简单的中立表达式开始, 然后由此构建其他的中立表达式.变量是中立的, 除非变量引用定义, 因为一个被定义的名字和其定义是相同的, 见define
之律和诫.
48 好的.
而且, 如果一个消去子表达式的target是中立的, 那么整个表达式就是中立的. 49 因此, 之所以表达式(iter-Nat n
1
(λ (j)
(add1 j)))
是中立的, 是因为iter-Nat
是一个消去子, 而其target n
是一个变量.每个包含变量的表达式都是中立的吗?
中立表达式
没有被define
的变量是中立的. 如果一个消去子表达式的target是中立的, 那么该消去子表达式就是中立的.
并不正确.(λ (x)
(add1 x))
这个λ
表达式的体包含变量x
, 但是λ
表达式是值, 而非中立表达式. 50 但是如果整个 表达式就只是(add1 x)
, 那么它就是中立的了, 因为它包含中立的x
.中立表达式都是规范形式吗?
并不总是如此.一些类型拥有可以将中立表达式变为值的方法, 而在这种情况下, 中立表达式不被认为是规范形式, 因为其可以被变成值.
51 这样的类型有哪些呢?
一个类型以Π
为顶的中立表达式不是规范形式. 这是因为一个中立表达式f 和(λ (x ) (f x ))
是相同的, 而后者是一个值. 52 为什么这意味着f 并非规范呢?
一个表达式为规范形式的含义是什么? 53 根据大框规范形式 所言, 如果两个表达式是相同的, 那么它们有着等同的规范形式.
根据λ
终第二诫 , f 和(λ (x ) (f x ))
是相同的, 但是它们写下来的形式并不 等同. 54 一个包裹在λ
里, 另一个则没有.
这两个之中至多只有一个可以是规范形式. 实际上, 包裹在λ
里的那个即是规范形式. 因为以λ
为顶的表达式是值, 这些表达式并非中立. 中立表达式不以构造子为顶. 55 还有其他这样的类型吗?
是的.因为cons
之第二诫 , 如果p 是一个
(Pair A D )
那么p 和(cons (car p ) (cdr p ))
是相同的. 出于完全相同的原因, 序对的规范形式只可能是以cons
为顶的表达式, 于是并不存在规范的中立序对. 56 中立表达式来源于什么地方呢?
正如第8章第45框 里的(incr n)
的规范形式, 中立表达式经常在=
表达式提及Π
表达式的参数名时出现. 57 那么我们该如何找到incr=add1
的一个定义呢? same
没法完成工作, 毕竟incr=add1
的类型里有一个中立表达式.
判断, 例如(incr n)
和(add1 n)
是相同的Nat
可以使用相当简单的规则进行机械化的验证. 这就是为什么判断是知识的合适基础.然而, 表达式可以编码相当有趣的推理模式, 例如使用归纳以对于中立表达式中的变量尝试每一种可能性. 58 是不是这意味着归纳可以用来证明(incr n)
和(add1 n)
是相等的, 甚至尽管它们并不相同?
是这样的, 之所以使用ind-Nat
, 是因为类型依赖于target.(define incr=add1
(λ (n)
(ind-Nat n
mot-incr=add1
base-incr=add1
step-incr=add1)))
base-incr=add1
的类型是什么? 59 一个ind-Nat
表达式里的base的类型应该是动机应用于zero
的结果. (incr zero)
并非中立, 而其规范形式为(add1 zero)
, 见第8章第5框 , 于是其和(add1 zero)
是相同的Nat
.(claim base-incr=add1
(= Nat (incr zero) (add1 zero)))
(define base-incr=add1
(same (add1 zero)))
现在对于base-incr=add1
的类型里的常量zero
进行抽象以定义mot-incr=add1
. 60 每个zero
都变成了k
.(claim mot-incr=add1
(→ Nat U ))
(define mot-incr=add1
(λ (k)
(= Nat (incr k) (add1 k))))
根据ind-Nat
之律, step-incr=add1
的类型是什么?当下请使用虚线框.
61 应该使用mot-incr=add1
, 但为什么要加上虚线框呢?(claim step-incr=add1
(Π ((n-1 Nat))
(→ (mot-incr=add1 n-1)
(mot-incr=add1 (add1 n-1)))))
在声明或者定义的最终版本还未落实之前都会加上虚线框. 尽管这个是正确的类型, 但是还可以写得更容易理解一些. 62 这更容易理解的写法是什么呢?
以下是step-incr=add1
的类型的另一种写法.(claim step-incr=add1
(Π ((n-1 Nat))
(→ (= Nat
(incr n-1)
(add1 n-1))
(= Nat
(incr
(add1 n-1))
(add1
(add1 n-1))))))
63 为什么这是相同的类型呢?
因为(mot-incr=add1 n-1)
和(= Nat
(incr n-1)
(add1 n-1))
是相同的类型.(mot-incr=add1 (add1 n-1))
的值是什么? 64 其值为(= Nat
(incr
(add1 n-1))
(add1
(add1 n-1)))
这是第8章第63框 中的→
表达式里的另一个类型.
那么第8章第63框 里的step-incr=add1
的类型该如何读作陈述呢? 65 很难说.→
该如何读作陈述呢?
表达式(→ X Y )
可以读作陈述如果X , 那么Y . 这种解释之所以能够成立, 是因为其值是将任何 X 的证明变换为Y 的一个证明的完全函数. 66 那我试试看.step的类型是一个Π
表达式, 这意味着其陈述应该以每个 开始. 之后则是一个→
, 其可以读作如果 和那么 . 另外, =
可以读作等于 .
如果 和那么 作为类型
表达式
(→ X Y )
可以读作陈述
如果X , 那么Y .
那么step-incr=add1
的类型到底该如何读作陈述呢? 67 "对于每个Nat
n , 如果(incr n )
等于(add1 n )
那么(incr (add1 n ))
等于(add1 (add1 n ))
".
和之前的陈述不同的是, 为了证明这个 陈述, 我们必须要观察到一个关于incr
的事实.(incr (add1 n-1))
的规范形式是什么? 68 iter-Nat
会在n-1
上卡住, 但是一个add1
会被加到顶层.| (incr (add1 n-1))
| (iter-Nat (add1 n-1)
1
(+ 1))
| (+ 1
(iter-Nat n-1
1
(+ 1)))
| (add1
(iter-Nat n-1
1
(+ 1)))
| (add1
(iter-Nat n-1
1
(λ (j)
(add1 j))))
换言之, 也就是(incr (add1 n-1))
和(add1 (incr n-1))
是相同的Nat
, 因为(incr n-1)
和(iter-Nat n-1
1
(λ (j)
(add1 j)))
是相同的Nat
.这就是我们所需要的观察.
69 好的, 所以说step-incr=add1
的类型也可以写成以下这样. 和第8章第63框 的版本不同的部分已用白纸黑字标出.(claim step-incr=add1
(Π ((n-1 Nat))
(→ (= Nat
(incr n-1)
(add1 n-1))
(= Nat
(add1
(incr n-1))
(add1
(add1 n-1))))))
我们没有加上虚线框的原因在于现在这个类型很容易看出来为什么成立了. 如果两个Nat
是相等的, 那么给它们都加上一也应该是相等的. 70 的确如此, 但是该如何以证明来得出其为真呢?
关于incr
的观察
不论Nat
n 为何,
(incr (add1 n ))
和
(add1 (incr n ))
是相同的
Nat
.
以下是定义step-incr=add1
的起点.(define step-incr=add1
(λ (n-1)
(λ (incr=add1n-1 )
incr=add1n-1 )))
71 对于n-1
的几乎证明 (almost-proof) 是一个(= Nat
(incr n-1)
(add1 n-1))
白框之内可以用什么将这个几乎证明转换为一个(= Nat
(add1
(incr n-1))
(add1
(add1 n-1)))
的证明呢?
cong
是一个在这里有用的=
的消去子.72 cong
表达式是什么?
First things first. 现在应该坐下来吃个(sandwich 'submarine)
73 又是三明治?有点吃太多了.
回到手头上的问题来,(cong target f )
用于以f 转换target 相等起来的两个表达式.如果f 是一个
(→ X Y )
而target 是一个(= X from to )
那么(cong target f )
是一个(= Y (f from ) (f to ))
74 还有其他看待cong
的方式吗?
以下图表展示了cong
是如何使用的.X Y f (= X from to )
(= Y (f from ) (f to ))
(cong f )
75 cong
该如何用来完成step-incr=add1
的定义呢?
cong
之律
如果f 是一个
(→ X Y )
而
target 是一个
(= X from to )
那么
(cong target f )
是一个
(= Y (f from ) (f to ))
在我们这里的情况下, X 是Nat
, Y 也是Nat
, 而target 是incr=add1n-1
.那么, from 和to 分别是什么呢?
76 因为incr=add1n-1
的类型为(= Nat (incr n-1) (add1 n-1))
故from 是(incr n-1)
而to 是(add1 n-1)
.
什么函数f 可以将(incr n-1)
转换为(add1 (incr n-1))
而(add1 n-1)
转换为(add1 (add1 n-1))
呢? 77 在这两种情形下, 都是一个add1
加到了顶层. 使用add1
作为f 如何?
add1
是一个构造子, 但是若非add1
出现在顶层而下面藏着一个Nat
, 其本身并不是表达式.换言之, 一个add1
表达式必须要有参数.
78 使用incr
作为f 如何?
尽管incr
的确能给参数加上一, 但是在参数是中立表达式的情况下无法立即导致添加add1
. 79 使用(+ 1)
作为f 如何?
真是出色的选择. 现在白框里可以填上一个表达式了. (cong incr=add1n-1 (+ 1))
80 好的.(define step-incr=add1
(λ (n-1 )
(λ (incr=add1n-1 )
(cong incr=add1n-1 (+ 1)))))
现在能够定义incr=add1
了. 81 既然动机, base, step均已定义, 那么之前第8章第59框 里的incr=add1
的定义就不需要加上虚框了.(define incr=add1
(λ (n)
(ind-Nat n
mot-incr=add1
base-incr=add1
step-incr=add1)))
是时候再吃一个三明治了:(sandwich 'hero)
82 又一个!
是的, 再来一个.为什么incr=add1
的定义需要ind-Nat
而+1=add1
的定义却不需要呢?
83 因为(incr n)
的规范形式是中立表达式 (见第8章第45框 ), 但是(+ 1 n)
的规范形式, 根据+
的定义, 是(add1 n)
.
中立表达式是那些尚不能被求值的表达式, 但是替换其变量为值则能允许求值.(incr=add1 2)
的类型是什么? 84 表达式(incr=add1 2)
是一个(= Nat (incr 2) (add1 2))
换言之, 它是一个(= Nat 3 3)
鉴于(incr 2)
并不是中立的.
(incr=add1 2)
的规范形式是什么?85 以下是相同于 图表的起点.| (incr=add1 2)
| (ind-Nat (add1 1)
mot-incr=add1
base-incr=add1
step-incr=add1)
| (step-incr=add1 1
(ind-Nat 1
mot-incr=add1
base-incr=add1
step-incr=add1))
| (cong (ind-Nat (add1 0)
mot-incr=add1
base-incr=add1
step-incr=add1)
(+ 1))
cong
表达式该如何求值呢?
就和其他消去子一样, 对于一个cong
表达式求值的第一步是对于其target求值. 如果target是中立的, 那么整个cong
表达式就是中立的, 因此不再能继续求值. 86 那么若target并非中立呢?
如果target并非中立, 那么其值以same
为顶, 因为same
是=
表达式唯一的构造子.(cong (same x ) f )
的值为(same (f x ))
87 好的, 所以找出规范形式的下一步是是找出cong
的target的值.
既然ind-Nat
的target已经以add1
为顶了, 故下一步是使用step.| (cong (step-incr=add1 0
(ind-Nat zero
mot-incr=add1
base-incr=add1
step-incr=add1))
(+ 1))
88 这接下来的ind-Nat
的target是zero
.| (cong (cong base-incr=add1
(+ 1))
(+ 1))
| (cong (cong (same (add1 zero))
(+ 1))
(+ 1))
| (cong (same ((+ 1) (add1 zero)))
(+ 1))
| (cong (same (add1 (add1 zero)))
(+ 1))
| (same
((+ 1) (add1 (add1 zero))))
| (same
(add1 (add1 (add1 zero))))
cong
之诫
如果x 是一个X 而f 是一个
(→ X Y )
那么
(cong (same x ) f )
和
(same (f x ))
是相同的
(= Y (f x ) (f x ))
判断相同性和陈述相等性之间的交互作用 (interplay) 是与依赖类型打交道的核心. 现在我们的初次尝试还仅仅是停留在表明罢了. 89 但是我的胃怎么办呢? 最多只能再吃得下去一个三明治了.
今天是你的幸运日!(claim sandwich
(→ Atom Atom))
(define sandwich
(λ (which-sandwich )
'delicious))
90 啊, 释然了! 这里实际上只存在 一个三明治:(same 'delicious)
是对于(sandwich 'hoagie)
(sandwich 'grinder)
(sandwich 'submarine)
(sandwich 'hero)
全都相等的一个证明.
第9章 钱数翻倍, 得到两倍 在前一章里, =
只有一个消去子, 叫做cong
.但是, cong
有一项关键性的限制.
1 是什么呢?
cong
表达式的类型是什么?2 根据cong
之律, 如果target 是一个(= X from to )
而f 是一个(→ X Y )
那么(cong target f )
是一个(= Y (f from ) (f to ))
是这样的.不过其和诸如ind-Nat
这样的消去子有着怎样的不同呢?
3 一个ind-Nat
表达式可以具有任意 的类型——完全依赖于动机. 但是, 一个cong
表达式的类型总以=
为顶.
cong
是一个特殊目的的消去子, 但是也存在着一个更加一般性的消去子, 叫做replace
.4 replace
是什么意思?
如果两个表达式是相等的, 那么对于一个为真的任何事情对于另外一个也为真. 我们称这个原则为Leibniz律. 5 这和replace
有什么关系吗?
replace
比cong
更为强大, 因为任何对于cong
的使用都可以改写为对于replace
的使用, 正如任何对于which-Nat
, iter-Nat
, rec-Nat
的使用也可以改写为对于ind-Nat
的使用.6 replace
和cong
有着怎样的不同之处呢?
类似于cong
, replace
的target也是一个(= X from to )
然而, 和cong
不同的是, replace
有一个动机和一个base. 7 那么一个replace
表达式的类型由应用动机于target确定吗?
那是ind-Nat
里动机的用法, 而不是replace
里动机的用法.在replace
里, 动机解释了什么 在Leibniz律下对于两个表达式都成立. 其是一个
(→ X U )
这是因为它解释了如何由一个X 找到一个U (因而也是一个陈述). 8 那base呢?
base是(mot from )
为真的一个证据. 换言之, base的类型为(mot from )
9 那整个replace
表达式呢?
整个replace
表达式是(mot to )
为真的证据. 换言之, 其类型为(mot to )
10 因此, replace
以to 替换了from .
replace
之律
如果target 是一个
(= X from to )
mot 是一个
(→ X U )
而
base 是一个
(mot from )
那么
(replace target
mot
base )
是一个
(mot to )
的确如此.不过其也可以使用replace
定义.
再次问问其claim
是什么?
12 根据第8章第69框 的观察, incr
里面的add1
可以提到外面来, 就好像其已为cong
作了准备.(claim step-incr=add1
(Π ((n-1 Nat))
(→ (= Nat
(incr n-1)
(add1 n-1))
(= Nat
(add1
(incr n-1))
(add1
(add1 n-1))))))
以下是使用了replace
的定义的起点.(define step-incr=add1
(λ (n-1)
(λ (incr=add1n-1 )
(replace incr=add1n-1
))))
target是incr=add1n-1
, 其是这里唯一可用的相等性证明. 13 作为target的incr=add1n-1
是一个(= Nat
(incr n-1)
(add1 n-1))
整个replace
表达式应该是一个(= Nat
(add1
(incr n-1))
(add1
(add1 n-1)))
为了找出动机, 需要检视replace
表达式的类型.先看看target的类型的to 部分.
14 to 是(add1 n-1)
, 其当然可以在replace
表达式的类型里找到.(= Nat
(add1
(incr n-1))
(add1
(add1 n-1) ))
动机是用来找出base和整个replace
表达式这两个的类型的. base的类型可以通过将target的类型的from 置于空白方块里得到, 而整个表达式的类型则是通过将target的类型的to 置于空白方块里得到.(= Nat
(add1
(incr n-1))
(add1
))
15 缺失了一块的表达式可以被写成是λ
表达式.
为了得到动机, 请以λ
抽象出target的类型里的to 部分. 16 这将给出以下表达式:(λ (k)
(= Nat
(add1
(incr n-1))
(add1
k)))
但是, 如果replace
将from 代之以to , 我们为什么要抽象出to 而不是from 呢?
base的类型可以通过应用动机于target的类型的from 得到. 因此, 在这种情况下, 其为| ((λ (k)
(= Nat
(add1
(incr n-1))
(add1
k)))
(incr n-1))
| (= Nat
(add1
(incr n-1))
(add1
(incr n-1)))
17 应用动机于参数其实就和给空白方块填上参数一样.(= Nat
(add1
(incr n-1))
(add1
(incr n-1) ))
既然我们知道了base的类型, 那么base是什么呢? 18 base应该是(same
(add1
(incr n-1)))
将其填入使用replace
的step-incr=add1
的定义的对应白框中则得到(define step-incr=add1
(λ (n-1)
(λ (incr=add1n-1 )
(replace incr=add1n-1
(same (add1 (incr n-1)))))))
现在该来定义动机了. 19 动机以n-1
作为一个参数, 就像step-*
以j
作为一个参数.(claim mot-step-incr=add1
(→ Nat Nat U ))
(define mot-step-incr=add1
(λ (n-1 k)
(= Nat
(add1
(incr n-1))
(add1
k))))
最后, 请完成第9章第17框 里的定义. 20 鉴于step-incr=add1
在第8章中已经定义过了, 虚框仍然保持.(define step-incr=add1
(λ (n-1)
(λ (incr=add1n-1 )
(replace incr=add1n-1
(mot-step-incr=add1 n-1)
(same (add1 (incr n-1)))))))
是的, 每个声明只应该有一个定义.现在请定义double
, 其将一个Nat
里的每个add1
替换为两个add1
.
(claim double
(→ Nat Nat))
21 这是iter-Nat
足以胜任的工作. step应该是(+ 2)
, 因为(+ 2)
的规范形式为(λ (j)
(add1
(add1 j)))
(define double
(λ (n)
(iter-Nat n
0
(+ 2))))
(double n)
是n
的两倍大. 另外一个可以找出相同答案的函数是什么呢? 我们称其为twice
.(claim twice
(→ Nat Nat))
22 比如说这个?(define twice
(λ (n)
(+ n n)))
的确, 恰好对于每个Nat
n , (twice n )
等于(double n )
. 这个陈述该怎么写成一个类型呢? 23 因为这个陈述很可能得到一个证明, 故取个名字.(claim twice=double
(Π ((n Nat))
(= Nat (twice n) (double n))))
非常敏锐.为什么这个声明为真呢?
24 每个Nat
值要么是zero
, 要么以add1
为顶. (twice zero)
和(double zero)
都是zero
.
add1
呢?25 对于add1
而言,(twice (add1 n-1))
和(+ (add1 n-1) (add1 n-1))
是相同的Nat
, 但是(double (add1 n-1))
则和(add1 (add1 (double n-1)))
是相同的Nat
.
(+ (add1 n-1) (add1 n-1))
和(add1 (add1 (double n-1)))
是相同的Nat
吗?26 不, 并不是.但是当然它们必须是相等的.
的确.为了证明twice=double
, 我们需要一个额外的证明. 尽管围绕着+
的第一个 参数的add1
可以被提到+
外面, 围绕着+
的第二个 参数的add1
是不行的——至少没有证明不行.
27 是的, 因为只有第一个参数是+
的定义里的iter-Nat
的target.
尽管(+ n (add1 j ))
和(add1 (+ n j ))
不是相同的Nat
, 但是它们的确是相等的Nat
. 28 虽然它们并不相同, 但是一个可以被replace
为另外一个.
要证明的陈述是add1+=+add1
.(claim add1+=+add1
(Π ((n Nat)
(j Nat))
(= Nat
(add1 (+ n j))
(+ n (add1 j)))))
29 这看起来像是ind-Nat
可以胜任的工作.(define add1+=+add1
(λ (n j)
(ind-Nat n
(mot-add1+=+add1 j)
(same (add1 j))
(step-add1+=+add1 j))))
就和step-*
一样, 动机和step都需要j
, 而base为(same (add1 j))
为什么base是(same (add1 j))
呢? 30 因为(add1 (+ zero j))
和(add1 j)
是相同的Nat
, 而(+ zero (add1 j))
也和(add1 j)
是相同的Nat
.
mot-add1+=+add1
应该是什么呢?31 其是ind-Nat
表达式的类型对于target的抽象. 换言之, add1+=+add1
的声明里的每个n
的出现都会变成k
.(claim mot-add1+=+add1
(→ Nat Nat U ))
(define mot-add1+=+add1
(λ (j k)
(= Nat
(add1 (+ k j))
(+ k (add1 j)))))
以下是step-add1+=+add1
的类型.(claim step-add1+=+add1
(Π ((j Nat)
(n-1 Nat))
(→ (mot-add1+=+add1 j
n-1)
(mot-add1+=+add1 j
(add1 n-1)))))
写下(mot-add1+=+add1 j
(add1 n-1))
的更显式方法是什么? 32 应用mot-add1+=+add1
将给出(= Nat
(add1 (+ (add1 n-1) j))
(+ (add1 n-1) (add1 j)))
而这个类型和(= Nat
(add1 (add1 (+ n-1 j)))
(add1 (+ n-1 (add1 j))))
是相同的类型, 这是因为+
的第一个参数是iter-Nat
的target.
现在请定义step-add1+=+add1
. 33 使用cong
.(define step-add1+=+add1
(λ (j n-1 )
(λ (add1+=+add1n-1 )
(cong add1+=+add1n-1
(+ 1)))))
cong
和(+ 1)
在这个定义中扮演着怎样的角色呢?34 add1+=+add1n-1
是一个(= Nat
(add1 (+ n-1 j))
(+ n-1 (add1 j)))
因而和cong
一道使用的(+ 1)
会给from 和to 都包裹上add1
, 这就给出了第9章第32框 中的那个类型.
现在add1+=+add1
的定义不用加上虚框了, 因为其所提及的每个名字现在都已被定义. 35 以下就是它的定义了.(define add1+=+add1
(λ (n j)
(ind-Nat n
(mot-add1+=+add1 j)
(same (add1 j))
(step-add1+=+add1 j))))
根据第9章第35框 , 以下陈述为真 :对于所有的Nat
n 和j ,(add1 (+ n j ))
等于(+ n (add1 j ))
36 是的.这也意味着
(add1 (+ n-1 n-1))
等于(+ n-1 (add1 n-1))
因为n 和j 可以都是n-1
.
什么表达式具有类型(= Nat
(add1 (+ n-1 n-1))
(+ n-1 (add1 n-1)))
呢? 37 表达式(add1+=+add1 n-1 n-1)
是一个(= Nat
(add1 (+ n-1 n-1))
(+ n-1 (add1 n-1)))
现在请使用(add1 (+ n-1 n-1))
等于(+ n-1 (add1 n-1))
的事实来证明twice=double
. 38 第9章第24框 的内容暗示了我们应该使用ind-Nat
表达式.(define twice=double
(λ (n)
(ind-Nat n
mot-twice=double
(same zero)
step-twice=double)))
mot-twice=double
应该是什么呢?39 只需遵循通常的对于target抽象的方法.(claim mot-twice=double
(→ Nat U ))
(define mot-twice=double
(λ (k)
(= Nat
(twice k)
(double k))))
那么step-twice=double
呢? 40 step-twice=double
的类型和其他的每个step如出一辙.(claim step-twice=double
(Π ((n-1 Nat))
(→ (mot-twice=double n-1)
(mot-twice=double
(add1 n-1)))))
以下是定义的开始.(define step-twice=double
(λ (n-1)
(λ (twice=doublen-1 )
)))
twice=doublen-1
的类型是什么? 41 twice=doublen-1
是一个(= Nat
(twice n-1)
(double n-1))
框里填的表达式的类型应该是(= Nat
(twice (add1 n-1))
(double (add1 n-1)))
而这个类型和(= Nat
(add1
(+ n-1 (add1 n-1)))
(add1
(add1 (double n-1))))
是相同的类型. 42 第9章第25框 解释了为什么(double (add1 n-1))
和(add1
(add1 (double n-1)))
是相同的Nat
.为什么(twice (add1 n-1))
和(add1
(+ n-1 (add1 n-1)))
是相同的Nat
呢?
一个即时的关于+
的观察可以派上用场. 不论Nat
j 和k 为何,| (+ (add1 j ) k )
| (iter-Nat (add1 j )
k
step-+)
| (step-+
(iter-Nat j
k
step-+))
| (add1
(iter-Nat j
k
step-+))
| (add1
(+ j k ))
43 这非常类似于前一章的关于incr
的观察 .
关于+
的观察
不论Nat
j 和k 为何,
(+ (add1 j ) k )
和
(add1 (+ j k ))
是相同的
Nat
.
运用这个关于+
的观察,| (twice (add1 n-1))
| (+ (add1 n-1) (add1 n-1))
| (add1
(+ n-1 (add1 n-1)))
cong
可以胜任证明的工作吗? 44 表达式(cong twice=doublen-1
(+ 2))
是一个(= Nat
(add1
(add1 (+ n-1 n-1)))
(add1
(add1 (double n-1))))
其并非相同的类型.
虽然并非相同的类型, 但是近乎于 相同的类型. 45 将类型中的(add1 (+ n-1 n-1))
替换以(+ n-1 (add1 n-1))
则能解决问题.
鉴于(add1 (+ n-1 n-1))
等于(+ n-1 (add1 n-1))
replace
可以将+
的第二个参数的add1
移到外面来. 46 是的, 因为replace
适用的条件在于某个东西的类型近乎 满足要求, 而不满足要求的部分又等于某个可以使得整个类型满足要求的东西.
在这种情况下,(cong twice=doublen-1
(+ 2))
满足要求的部分是什么? 47 (= Nat
(add1
)
(add1
(add1 (double n-1))))
白色方块之外的一切都正合适.
现在请定义动机.mot-step-twice=double
需要一个额外的参数, 正如step-*
.(claim mot-step-twice=double
(→ Nat Nat U ))
48 白色方块的位置变成了λ
表达式(所绑定)的变量.(define mot-step-twice=double
(λ (n-1 k)
(= Nat
(add1
k)
(add1
(add1 (double n-1))))))
replace
表达式的target是什么呢?49 表达式(add1 (+ n-1 n-1))
应该替换以(+ n-1 (add1 n-1))
故target应为(add1+=+add1 n-1 n-1)
以下是到目前为止的定义.(define step-twice=double
(λ (n-1)
(λ (twice=doublen-1 )
(replace (add1+=+add1 n-1 n-1)
(mot-step-twice=double n-1)
))))
50 base应该是类型近乎正确的表达式, 即(cong twice=doublen-1
(+ 2))
step-twice=double
的完整定义是什么呢?51 (define step-twice=double
(λ (n-1)
(λ (twice=doublen-1 )
(replace (add1+=+add1 n-1 n-1)
(mot-step-twice=double n-1)
(cong twice=doublen-1
(+ 2))))))
最后, 我们应该把twice=double
的虚线框去掉. 52 到目前为止, 每个replace
表达式的类型都以=
为顶.(define twice=double
(λ (n)
(ind-Nat n
mot-twice=double
(same zero)
step-twice=double)))
你说得很对. 不过, 之所以replace
有用, 是因为通过编写合适的动机, 其可以具有任意的类型.请找出
(twice 17)
等于(double 17)
的两个证明.(claim twice=double-of-17
(= Nat (twice 17) (double 17)))
(claim twice=double-of-17-again
(= Nat (twice 17) (double 17)))
53 如果一个陈述对于每个Nat
都为真, 那么其也对于17
为真. 一种证明的方式为应用twice=double
于17
.(define twice=double-of-17
(twice=double 17))
这类似于第4章第54框 里的twin-Atom
.
另一个证明是什么呢? 54 (twice 17)
和(double 17)
已经是相同的Nat
了, 故也可以使用same
.(define twice=double-of-17-again
(same 34))
实际上, (same 34)
甚至是twice=double-of-17
的值.请定义一个叫做twice-Vec
的函数, 其会复制一个Vec
里的每个元素. 例如,
(twice-Vec Atom 3
(vec:: 'chocolate-chip
(vec:: 'oatmeal-raisin
(vec:: 'vanilla-wafer
vecnil))))
的规范形式为(vec:: 'chocolate-chip
(vec:: 'chocolate-chip
(vec:: 'oatmeal-raisin
(vec:: 'oatmeal-raisin
(vec:: 'vanilla-wafer
(vec:: 'vanilla-wafer
vecnil))))))
55 其类型应该是什么呢?
正如名字所暗示的那样, 这个函数将会构造一个具有twice
倍那么多元素的Vec
.(claim twice-Vec
(Π ((E U )
(l Nat))
(→ (Vec E l )
(Vec E (twice l )))))
56 听上去有点困难.
为什么呢? 57 鉴于其类型依赖于一个Nat
, 这暗示了该函数应该使用ind-Nat
定义, 并且step应该使用两次vec::
.为了使用vec::
, 意图的长度必须以add1
为顶. 然而, (作为结果的)这个Vec
的长度将会只有一个add1
位于顶层.
为什么长度的顶层只有一个add1
呢? 58 根据关于+
的观察 ,(twice (add1 n-1))
和(add1 (+ n-1 (add1 n-1)))
是相同的Nat
.
以下是陈述问题的更直接方式.(claim double-Vec
(Π ((E U )
(l Nat))
(→ (Vec E l )
(Vec E (double l )))))
59 这是更容易以ind-Nat
定义的. 以下是base.(claim base-double-Vec
(Π ((E U ))
(→ (Vec E zero)
(Vec E (double zero)))))
(define base-double-Vec
(λ (E )
(λ (es )
vecnil)))
是这样的——doubling一个空的Vec
仍然为空. 动机应该是什么呢?(claim mot-double-Vec
(→ U Nat U ))
60 这可以通过对于base的类型里的zero
抽象来找出.(define mot-double-Vec
(λ (E k)
(→ (Vec E k)
(Vec E (double k)))))
step呢?(claim step-double-Vec
(Π ((E U )
(l-1 Nat))
(→ (→ (Vec E l-1 )
(Vec E (double l-1 )))
(→ (Vec E (add1 l-1 ))
(Vec E
(double (add1 l-1 )))))))
61 这个step将具有l-1
个元素的Vec
的doubler转换为具有(add1 l-1 )
个元素的Vec
的doubler, 并且(double (add1 l-1 ))
和(add1 (add1 (double l-1 )))
是相同的Nat
, 故使用两次vec::
是可以预料到的.(define step-double-Vec
(λ (E l-1 )
(λ (double-Vecl-1 )
(λ (es)
(vec:: (head es)
(vec:: (head es)
(double-Vecl-1
(tail es))))))))
double-Vec
的定义是什么呢?62 所有的部件都已齐备, 故不需要加上虚框.(define double-Vec
(λ (E l )
(ind-Nat l
(mot-double-Vec E)
(base-double-Vec E)
(step-double-Vec E))))
尽管对于所有的Nat
n , (double n )
等于(twice n )
为真, 定义使用它们的依赖类型函数并不同等简单. 定义double-Vec
是容易的, 而定义twice-Vec
则并不那么简单. 63 是这样的.
对于所有的Nat
n , (double n )
等于(twice n )
的证明可以用来以double-Vec
定义twice-Vec
.64 这显然可以节约不少工夫.
先解决容易的问题
如果两个函数产生相等的结果, 那么定义依赖类型函数时使用更简单的那个, 然后使用replace
赋予其想要的类型.
(double-Vec E l es)
的类型为(Vec E (double l ))
(double l )
需要变成(twice l )
.(define twice-Vec
(λ (E l )
(λ (es)
(replace
(λ (k)
(Vec E k))
(double-Vec E l es)))))
target应该填什么呢?65 (twice=double l )
怎么样?
很接近了, 但是(twice=double l )
是一个(= Nat (twice l ) (double l ))
其to 和from 以错误的顺序出现. 66 难道说现在我们需要证明double=twice
吗?
幸运的是, 这并不必要. =
另有一个特别的消去子symm
可以解决这个问题.如果target 是一个
(= X from to )
那么(symm target )
是一个(= X to from )
67 好的, 现在可以定义twice-Vec
了.(define twice-Vec
(λ (E l )
(λ (es)
(replace (symm
(twice=double l ))
(λ (k)
(Vec E k))
(double-Vec E l es)))))
symm
之律
如果target 是一个
(= X from to )
那么
(symm target )
是一个
(= X to from )
symm
之诫
如果x 是一个X , 那么
(symm (same x ))
和
(same x )
是相同的
(= X x x )
第10章 这也依赖于列表 在我们开始之前, 以下是三点期望. 你有没有...明白为什么我们需要归纳, 理解ind-Nat
, 并且 使用归纳构造一个函数呢? 1 更多的期望了! 以下是来源于第5章第2框 的所有期望, 就着三点新的期望一起. 期望在于你已经烹饪了普罗旺斯杂烩, 吃完了两个樱桃派, 尝试了使用带图画的餐巾清理一下, 理解了rec-Nat
, 并且 休息安睡好; 以及 明白为什么我们需要归纳, 理解ind-Nat
, 并且 使用归纳构造一个函数.
似乎这两个列表并不相衬. 第5章的列表没有明显的长度, 而这里的有.(claim more-expectations
(Vec Atom 3))
(define more-expectations
(vec:: 'need-induction
(vec:: 'understood-induction
(vec:: 'built-function
vecnil))))
2 但是append
不可能将List
和Vec
搞混.
的确不行. 合并Vec
是vec-append
的工作, 我们还没有定义. 为了能够在List
上使用vec-append
, 我们必须将其转换为Vec
. 3 但是若要构造Vec
的话, 难道我们不需要元素的数目吗?
还有另一种可能性.之前使用Vec
的定义都接受元素的数目作为参数. 但是通过旧类型上的新把戏 (twist), 我们可以同时构建Vec
和它的长度.
4 这个新把戏是什么呢?
一个值为一个(Pair A D )
是什么意思? 5 一个值是一个(Pair A D )
, 如果其以cons
为顶, 其car
是一个A , 并且 其cdr
是一个D .
如果(cons a d )
是一个(Σ ((x A ))
D )
那么a 的类型是A , 而d 的类型则由一致地将D 中的每个x 替换为a 得到. 6 何时(Σ ((x A ))
D )
是一个类型呢?
表达式(Σ ((x A ))
D )
是一个类型, 若A 是一个类型, 而且当x 是一个A 时, D 也是一个类型. (Σ ((bread Atom))
(= Atom bread 'bagel))
是一个类型吗? 7 是的, 因为Atom
是一个类型, 并且当bread
是一个Atom
时, (= Atom bread 'bagel)
是一个类型.
什么样的表达式具有类型(Σ ((bread Atom))
(= Atom bread 'bagel))
呢? 8 比如说(cons 'bagel (same 'bagel))
?
的确.(Σ ((A U ))
A)
是一个类型吗? 9 U 是一个类型, 而在A
是一个U 时, A
当然是一个类型.
Σ
之律
表达式
(Σ ((x A ))
D )
是一个类型, 如果
A 是一个类型, 而且当
x 是一个
A 时,
D 也是一个类型.
Σ
之诫
如果p 是一个
(Σ ((x A ))
D )
那么
p 和
(cons (car p ) (cdr p ))
是相同的.
请说出三个具有该类型的表达式. 10 Nat
是一个U , 4
是一个Nat
, 故(cons Nat 4)
是一个(Σ ((A U ))
A)
另外两个具有该类型的表达式是(cons Atom 'porridge)
和(cons (→ Nat Nat)
(+ 7))
(cons 'toast
(same (:: 'toast nil)))
是一个(Σ ((food Atom))
(= (List Atom)
(:: food nil)
(:: 'toast nil)))
吗?11 是的, 的确如此, 因为将(= (List Atom)
(:: food nil)
(:: 'toast nil))
中的food
一致地替换为'toast
将得到(= (List Atom)
(:: 'toast nil)
(:: 'toast nil))
因此(same (:: 'toast nil))
是可以接受的.Σ
和Pair
之间有什么关系呢?
(Pair A D )
是书写(Σ ((x A ))
D )
的简短方式, 其中x 在D 里没有被用到.12 这类似于第6章第40框 里所说的一些Π
表达式是如何可以被写成→
表达式的.Σ
是如何将元素的数目和Vec
组合起来的呢?
例如:(Σ ((l Nat))
(Vec Atom l ))
13 什么值具有这个类型?
比如说十七个'pea
:(cons 17 (peas 17))
现在请给出另一个例子. 14 一顿早餐怎么样?(cons 2
(vec:: 'toast-and-jam
(vec:: 'tea vecnil)))
从这顿早餐开始新的一天是很不错的.以→
, Π
, =
构建的类型可以读作陈述, 而具有这样的类型的表达式是证明. 类似地, 以Pair
和Σ
构建的类型也可以读作陈述.
15 (Pair A D )
该如何读作陈述呢?
一个(Pair A D )
既包含了A 的证据, 也包含了D 的证据, 而以cons
为顶. 这意味着(Pair A D )
可以读作A 且 (and) D 因为给出一个且 (and) 的证据在于给出其两个部分的证据.
(Pair (= Nat 2 3)
(= Atom 'apple 'apple))
该如何读作陈述呢? 16 其为陈述2
等于3
且'apple
等于'apple
. 这个陈述没有证据, 因为2
等于3
没有证据, 因此car
的部分放不了任何东西.
(Σ ((x A ))
D )
的证据是一个序对, 其car
是一个A , 而cdr
则是由将D 中的每个x 一致地替换为car
所得到的陈述的证据.17 将Σ
读作陈述会是什么样的呢?
Σ
可以读作存在 (there exists) . 例如,(Σ ((es (List Atom)))
(= (List Atom)
es
(reverse Atom es)))
可以读作存在一个原子的列表, 其等于自身的反转 .18 这个陈述是真的吗?
以下是一个证明:(cons nil (same nil))
19 当然了, 因为反转空表还是空表.
还有其他证明吗? 20 是的, 许多列表从前往后和从后往前是相等的. 以下是另外一个证明:(cons (:: 'bialy
(:: 'schmear
(:: 'bialy nil)))
(same (:: 'bialy
(:: 'schmear
(:: 'bialy nil)))))
以下表达式该如何读作陈述呢?(Σ ((es (List Atom)))
(= (List Atom)
(snoc Atom es 'grape)
(:: 'grape es)))
21 存在一个原子的列表, 将'grape
加到最后或者开头是等效的 .
现在请证明这个陈述. 22 不论将'grape
加到nil
的最后还是开头都是一样的:(cons nil
(same (:: 'grape nil)))
这的确是一个证明.还有其他证明吗?
23 只由'grape
构成的列表其实都可以.以下是另一个证明:
(cons (:: 'grape
(:: 'grape
(:: 'grape nil)))
(same (:: 'grape
(:: 'grape
(:: 'grape
(:: 'grape nil))))))
我们没有办法将一个'grape
和另一个'grape
进行区分, 因此加到开头还是结尾无关紧要.
干得好.将一个List
转换为一个Vec
的函数应该具有什么类型呢?
24 是不是list→vec
的类型需要用到Σ
?(claim list→vec
(Π ((E U ))
(→ (List E)
(Σ ((l Nat))
(Vec E l )))))
这是正确的, 至少暂时是正确的.以下是定义的一部分. 框里应该填上什么呢?
(define list→vec
(λ (E)
(λ (es)
)))
25 框里的表达式必须要检查es
是nil
还是说以::
为顶. rec-List
可以胜任此项任务, 而其target应该是es
.
的确如此.那么, base应该是什么呢?
26 base是es
为nil
时的值. 这显然应该是vecnil
, 而vecnil
具有0
个元素.(define list→vec
(λ (E)
(λ (es)
(rec-List es
(cons 0 vecnil)
))))
为什么(cons 0 vecnil)
是一个(Σ ((l Nat))
(Vec E l ))
呢? 27 因为car
是一个Nat
, 具体来说这里是0
, 而cdr
是一个(Vec E 0)
.
step-list→vec
给一个(Σ ((l Nat))
(Vec E l ))
添加一个元素.那么这更长的Vec
的类型该是什么呢?
28 (Σ ((l Nat))
(Vec E (add1 l )))
如何? 因为这个Vec
的长度多了一.
更好的类型实际上是(Σ ((l Nat))
(Vec E l ))
这是因为这里使用Σ
的要义在于我们需要有一个序对, 其car
部分是整个cdr
部分的长度. 使car
变得更大并不需要改变类型.请定义step.
29 step的类型遵循着rec-List
的通常配方.(claim step-list→vec
(Π ((E U ))
(→ E (List E) (Σ ((l Nat))
(Vec E l ))
(Σ ((l Nat))
(Vec E l )))))
为了定义step-list→vec
, Σ
的消去子是必要的. 是不是car
和cdr
也能消去Σ
呢?
是的. 如果p 是一个(Σ ((x A ))
D )
那么(car p )
是一个A . 30 这就和(Pair A D )
是一样的.
但是, cdr
有一些不同.如果p 是一个
(Σ ((x A ))
D )
那么(cdr p )
的类型是D , 且其中的每个x 都已被一致地替换为了(car p )
. 31 如果D 中没有x , 那么这岂不是和第1章的Pair
如出一辙?
的确如此.如果p 是一个
(Σ ((l Nat))
(Vec Atom l ))
那么(car p )
的类型是什么呢? 32 (car p )
是一个Nat
.
如果p 是一个(Σ ((l Nat))
(Vec Atom l ))
那么(cdr p )
的类型是什么呢? 33 (cdr p )
是一个(Vec Atom (car p ))
.因此, Σ
是另一种构造依赖类型的方式.
以下是step-list→vec
.(define step-list→vec
(λ (E )
(λ (e es list→veces )
(cons (add1 (car list→veces ))
(vec:: e (cdr list→veces ))))))
请解释一下. 34 以下是我的解释.内层的λ
表达式的体以cons
为顶, 因为其必须要构造一个Σ
. 内层λ
表达式的体的car
是(add1 (car list→veces ))
因为step-list→vec
所构造的Vec
比(cdr list→veces )
多一个元素. 内层λ
表达式的体的cdr
比list→veces
的cdr
多一个元素, 即e
, 于是vec::
将其作为新元素加到开头.
现在请给出list→vec
的完整定义. 35 只需给方框填上(step-list→vec E)
即可.(define list→vec
(λ (E)
(λ (es)
(rec-List es
(cons 0 vecnil)
(step-list→vec E)))))
这个版本的list→vec
该怎么总结呢? 36 这个list→vec
将一个列表转换为一个序对, 其car
是这个列表的长度, 而cdr
则是一个具有这么多元素的Vec
.对于nil
而言, 其结果的长度为0
而这Vec
应该是vecnil
. 对于::
而言, 其结果的长度要比被转换了的列表的剩余部分的长度多一, 而vec::
所添加的元素和::
添加的元素相同.
(list→vec Atom
(:: 'beans
(:: 'tomato nil)))
的值是什么?37 让我们一起来看.| (list→vec Atom
(:: 'beans
(:: 'tomato nil)))
| (rec-List (:: 'beans
(:: 'tomato nil))
(cons 0 vecnil)
(step-list→vec Atom))
| (step-list→vec Atom
'beans
(:: 'tomato nil)
(rec-List (:: 'tomato nil)
(cons 0 vecnil)
(step-list→vec Atom)))
| (cons
(add1
(car
(rec-List (:: 'tomato nil)
(cons 0 vecnil)
(step-list→vec Atom))))
(vec:: 'beans
(cdr
(rec-List (:: 'tomato nil)
(cons 0 vecnil)
(step-list→vec Atom)))))
那么规范形式该是什么呢? 相同于 图表的过程可以跳过. 38 其规范形式为(cons 2
(vec:: 'beans
(vec:: 'tomato vecnil)))
list→vec
的定义位于虚框之中.为什么呢?
39 难道说这意味着这个定义有什么问题?
我们对于list→vec
所给出的类型不够特化 (specific).整个Vec
类型的要义在于追踪一个列表中有多少个元素, 但是将其包裹于Σ
之中则会隐藏这样的信息. 在第7章里, 特化的类型用于使函数变得完全. 不过, 特化的类型也能够用来排除愚蠢的定义.
40 但是这个定义是正确的, 不是吗? 起始表达式(:: 'beans
(:: 'tomato nil))
看起来就和预期的规范形式长得差不多. 以下是带有长度的预期形式:(cons 2
(vec:: 'beans
(vec:: 'tomato vecnil)))
为了正确性而使用特化的类型
特化的类型可以排除愚蠢的定义.
以下是list→vec
的类型所允许的一个愚蠢的定义.(define list→vec
(λ (E )
(λ (es )
(cons 0 vecnil))))
41 应用此list→vec
于任何类型和任何列表都将产生(cons 0 vecnil)
.
是这样的.你能给出另一个虽然并不正确但类型允许的定义吗?
42 list→vec
可以是一个总是产生具有52
个元素的Vec
的函数.
这几乎是对了.可是, 当es
为nil
时, 其该怎么产生每个类型均为E
的52
个元素呢?
43 我们不能预先知晓哪个U 会是E
, 即λ
表达式的参数. 因此, 在es
为nil
时, 我们没有办法找到一个具有该类型的元素.list→vec
可以是这样一个函数, 其在es
以::
为顶时产生一个具有52
个元素的Vec
, 而在es
为nil
时产生一个具有0
个元素的Vec
, 我说的对不对呢?
是的, 的确可以是这样.尽管如此, 写52
遍vec::
还是挺累的.
44 一个类似于peas
的定义可以帮助我们.
很好的想法. 让我们称其为replicate
. 和peas
一样, replicate
的定义需要使用ind-Nat
.为什么?
45 之所以replicate
的定义需要使用ind-Nat
, 是因为在replicate
的类型里, Nat
l
是target.(claim replicate
(Π ((E U )
(l Nat))
(→ E (Vec E l ))))
这个Π
表达式的体依赖于 l
, 而ind-Nat
在一个类型依赖于target时使用.
尽管现在已是早餐时间, 第7章并非徒劳无功!base应该是什么?
46 鉴于base是一个(Vec E 0)
因此其必然为vecnil
以下是mot-replicate
的类型.(claim mot-replicate
(→ U Nat U ))
现在请定义mot-replicate
. 47 mot-replicate
可以沿用之前的方法, 就和第7章第66框 一样对于zero
进行抽象.(define mot-replicate
(λ (E k)
(Vec E k)))
下一步是定义step-replicate
. 48 对于每一步骤, step-replicate
应该给列表加上一个元素.那么这个元素该来自何处呢?
正如E
是mot-replicate
的参数, E
和e
都是step-replicate
的参数.这类似于第3章第66框 中step-*
应用于j
的方式.
49 以下是step-replicate
的定义.(claim step-replicate
(Π ((E U )
(e E)
(l-1 Nat))
(→ (mot-replicate E l-1 )
(mot-replicate E (add1 l-1 )))))
(define step-replicate
(λ (E e l-1 )
(λ (replicatel-1 )
(vec:: e replicatel-1 ))))
现在请使用动机, base和step来定义replicate
. 50 只需将这些组件放在一起即可.(define replicate
(λ (E l )
(λ (e)
(ind-Nat l
(mot-replicate E)
vecnil
(step-replicate E e)))))
在第10章第49框 中, mot-replicate
被应用于两个参数, 但是这里其只被应用于一个. 另外, step-replicate
具有四个参数, 但是这里其只被应用于两个.为什么呢?
51 每个ind-Nat
的动机都应该具有类型(→ Nat U )
而根据Currying, (mot-replicate E)
的确就具有这个类型.类似地, 每个ind-Nat
的step都应该被应用于两个参数, 而根据Currying, 应用四参数的函数step-replicate
于头两个参数将产生预期的二参数函数.
replicate
意在用于辅助编写之前所述的list→vec
的另外版本, 即当es
以::
为顶时产生一个具有52
个元素的Vec
, 或者在es
为nil
时产生一个具有0
个元素的Vec
.52 以下copy-52-times
的定义中的cons
是Σ
的构造子, 用于将长度和Vec
绑在一起.(claim copy-52-times
(Π ((E U ))
(→ E
(List E)
(Σ ((l Nat))
(Vec E l ))
(Σ ((l Nat))
(Vec E l )))))
(define copy-52-times
(λ (E)
(λ (e es copy-52-timeses )
(cons 52 (replicate E 52 e)))))
(define list→vec
(λ (E)
(λ (es)
(rec-List es
(cons 0 vecnil)
(copy-52-times E)))))
通过澄清作为参数的List
和作为结果的Vec
之间的关系, list→vec
的类型可以变得更加特化.所以说这个关系是什么呢?
53 Vec
的元素数目等于List
的长度.
说对了, 以下就是这更加特化的类型.(claim list→vec
(Π ((E U )
(es (List E)))
(Vec E (length E es))))
54 该如何定义list→vec
呢?
有些东西应该是你可以预料到的. 55 是的, list→vec
的类型刻画了list→vec
的定义的一点形状.(define list→vec
(λ (E es)
...但是这里该填什么呢? ))
方框里的东西的类型是什么? 56 这个的类型是list→vec
的类型中的Π
表达式的体, 即(Vec E (length E es))
如果es
是一个Nat
的话, 那么我们可以用ind-Nat
, 但是es
是一个(List E)
.是不是我们还有ind-List
?
很好的思路.ind-Nat
比起rec-Nat
多一个参数, 即动机.
57 ind-List
也需要动机吗?
ind-List
比起rec-List
多一个参数, 而这参数也是动机:(ind-List target
mot
base
step )
58 那么这整个表达式的类型是什么呢?
首先, target 是一个(List E )
. 59 当然咯.否则的话, ind-List
就不会是List
上的归纳 (induction) 了.
正如ind-Nat
, mot 解释了施行归纳的理由. 换言之, 其解释了ind-List
表达式的类型依赖于target 的方式.mot 的类型应该是什么呢?
60 mot 在其被应用于列表时得到一个类型, 故其是一个(→ (List E ) U )
base 应该具有什么类型呢?61 base 应该是一个(mot nil)
, 因为nil
扮演着和zero
相同的角色.
::
则扮演着类似于add1
的角色, 除了::
拥有两个参数: 一个元素和一个列表.62 ind-List
的step的类型和ind-Nat
的step的类型相似吗?
正如ind-Nat
的step将一个对于n 而言的几乎答案转换为一个对于(add1 n )
而言的答案, ind-List
的step接受一个对于某个列表es 而言的几乎答案, 然后构造一个对于(:: e es )
而言的答案.step 的类型为
(Π ((e E )
(es (List E )))
(→ (mot es)
(mot (:: e es))))
63 这里用::
给es
添加一个元素e
就类似于ind-Nat
中以add1
加一.
ind-List
之律
如果target 是一个(List E )
, mot 是一个(→ (List E ) U )
, base 是一个(mot nil)
, 而step 是一个
(Π ((e E )
(es (List E )))
(→ (mot es)
(mot (:: e es))))
那么
(ind-List target
mot
base
step )
是一个
(mot target )
.
ind-List
之第一诫
ind-List
表达式
(ind-List nil
mot
base
step )
和
base 是相同的
(mot nil)
.
ind-List
之第二诫
ind-List
表达式
(ind-List (:: e es )
mot
base
step )
和
(step e es
(ind-List es
mot
base
step ))
是相同的
(mot (:: e es ))
.
Nat
和List
关系密切.因此, 一个ind-List
表达式的类型为
(mot target )
64 不出所料.
我们应该给第10章第55框 里的方框填上一个ind-List
表达式. 65 而其target是es
.(define list→vec
(λ (E es)
(ind-List es
mot-list→vec
base-list→vec
step-list→vec)))
对于第10章第54框 中的list→vec
的类型, 我们可以用→
重写Π
表达式吗? 66 不行, 因为(Vec E (length E es))
既依赖于E
也依赖于es
.
base-list→vec
的类型是什么呢?67 当es
为nil
时, (Vec E (length E es))
和(Vec E 0)
是相同的类型.
那么, base本身是什么呢? 68 唯一的(Vec E 0)
就是vecnil
, 因而我们没有必要定义base-list→vec
.(define list→vec
(λ (E es)
(ind-List es
mot-list→vec
vecnil
step-list→vec)))
现在由base的类型反推, 动机是什么呢? 69 对于base的类型中的zero
进行抽象并不能立即 成立, 因为动机的参数是一个(List E)
而非一个Nat
.但是, length
可以将List
转换为Nat
, 并且其出现在第10章第54框 中作为list→vec
的类型的Π
表达式的体里.
你的观察真仔细. 对于常量进行抽象往往成立, 但是在这种情况下, 我们需要用length
进行微调.以下是mot-list→vec
的类型.
(claim mot-list→vec
(Π ((E U ))
(→ (List E) U )))
现在请定义mot-list→vec
. 70 以下是mot-list→vec
的定义.(define mot-list→vec
(λ (E)
(λ (es)
(Vec E (length E es)))))
例如,(mot-list→vec Atom nil)
的值为(Vec Atom 0)
和预期一致.
step-list→vec
的类型是什么呢?71 这里没什么意外.(claim step-list→vec
(Π ((E U )
(e E)
(es (List E)))
(→ (mot-list→vec E es)
(mot-list→vec E (:: e es))))
现在请定义step-list→vec
. 72 以下就是了.(define step-list→vec
(λ (E e es )
(λ (list→veces )
(vec:: e list→veces ))))
作为几乎答案的参数list→veces
的类型是什么? 73 其为(mot-list→vec E es)
而(mot-list→vec E es)
和(Vec E (length E es))
是相同的类型.
(length E es)
是一个Nat
, 尽管其既非zero
也不以add1
为顶.74 (length E es)
的规范形式必然是中立的, 因为length
中的rec-List
的target为es
, 其是一个变量.
(vec:: e list→veces )
的类型是什么呢?75 鉴于list→veces
的类型为(Vec E (length E es))
因而(vec:: e list→veces )
的类型为(Vec E (add1 (length E es)))
为什么(Vec E (add1 (length E es)))
和(mot-list→vec E (:: e es))
是相同的类型呢? 76 因为以下这些表达式都是相同的类型.| (mot-list→vec E (:: e es))
| (Vec E (length E (:: e es)))
| (Vec E (add1 (length E es)))
现在请定义list→vec
. 77 最终list→vec
的定义不再需要虚框了.(define list→vec
(λ (E es)
(ind-List es
(mot-list→vec E)
vecnil
(step-list→vec E))))
这个更加特化的类型排除了之前两个愚蠢的定义.不幸的是, 仍然存在愚蠢的定义可以具有这个类型.
78 啊, 不是吧!
这新类型排除的第一个愚蠢定义是什么呢? 79 第一个愚蠢定义出现在第10章第41框 , 其总是产生(cons 0 vecnil)
还有一个是什么呢? 80 第10章第52框 中的愚蠢定义总是复制列表的第一个元素52
遍. 新的类型要求正确的长度, 于是排除了这个愚蠢的定义.什么样的其他愚蠢仍然是可能的呢?
以下是一个仍然可能但是愚蠢的step. 使用这个step的list→vec
定义需要进行调整吗?(define step-list→vec
(λ (E e es)
(λ (list→veces )
(replicate E
(length E (:: e es))
e))))
81 不, 相同的定义就够了.(define list→vec
(λ (E es)
(ind-List es
(mot-list→vec E)
vecnil
(step-list→vec E))))
使用这个愚蠢的定义,(list→vec Atom
(:: 'bowl-of-porridge
(:: 'banana
(:: 'nuts nil))))
的规范形式是什么呢? 82 鉴于名字list→veces
是黯淡的, 故实际上此定义并非递归.规范形式是三碗粥 (three bowls of porridge), 即
(vec:: 'bowl-of-porridge
(vec:: 'bowl-of-porridge
(vec:: 'bowl-of-porridge
vecnil)))
第一碗太烫了, 第二碗太冰了, 但是第三碗刚刚好.然而, 这个定义是愚蠢的——'banana
和'nuts
可以使得早餐更有营养. 83 是否存在一种甚至更加特化的类型能够排除以上所有的愚蠢定义呢?
是的, 的确存在. 84 那么, 合并Vec
这件事怎么样了呢?
马上就来! 但是, 请先吃早餐——你需要为接下来的内容补充能量. 85 已经迫不及待了!
第11章 所有的列表生来相等 吃完所有的粥之后, 是时候来杯配上瑞典甜点的午后咖啡了! 1 哈! Fika .
以下是我们fika 的甜点清单 (list).(claim treats
(Vec Atom 3))
(define treats
(vec:: 'kanelbullar
(vec:: 'plättar
(vec:: 'prinsesstårta
vecnil))))
2 听起来很棒! 但是, treats
和drinks
该如何组合到一起呢?(claim drinks
(List Atom))
(define drinks
(:: 'coffee
(:: 'cocoa nil)))
是这样的——前一章留下了一些松散的线索. 一条线索是Vec
版本的append
, 另一条线索是可以排除更多愚蠢定义的list→vec
. 3 嗯.
如果es
具有l
个元素而end
具有j
个元素, 那么它们总共具有多少元素呢? 4 当然它们一起应该有(+ l j )
个元素.
好的.(claim vec-append
(Π ((E U )
(l Nat)
(j Nat))
(→ (Vec E l ) (Vec E j)
(Vec E (+ l j)))))
5 这看起来和append
的类型很像.
vec-append
的类型和append
的类型有什么不同之处呢?6 更加特化的类型揭示了列表中到底有几个元素.
诚然如此.为了定义vec-append
, 我们还缺少什么呢?
7 Vec
的一个消去子.
实际上, 我们可以按照和first
, rest
, last
, drop-last
一样的方式, 使用ind-Nat
, head
, tail
来定义vec-append
.然而, 使用ind-Vec
的定义可以更加直接地表达其意图.
8 Vec
上每个可以使用ind-Vec
写成的操作也可以用head
和tail
来编写吗?
并非如此.以上所有可以用head
和tail
编写的定义里, 类型只依赖于长度, 而这是一个Nat
. 然而, 有时一个类型依赖于一个Vec
, 那么ind-Vec
就是必要的了.
9 ind-Vec
和ind-List
类似吗?
是的, ind-Vec
很像ind-List
. 一个ind-Vec
表达式(ind-Vec n es
mot
base
step )
具有两个target:n , 其为一个Nat
,以及es , 其是一个(Vec E n )
. 10 所以说n 是es 的元素数目.ind-List
和ind-Vec
之间还有其他什么不同之处吗?
ind-Vec
表达式的每个部分都必须将es
的元素数目考虑在内.mot 的类型为
(Π ((k Nat))
(→ (Vec E k) U ))
因为其解释了任意 的作为target的Nat
和Vec
被消去的原因.11 为什么E 不也成为Π
表达式的一个参数呢?
很好的问题. 这是因为列表的元素的类型所扮演的角色与元素数目所扮演的角色相当不同.在任意单独的列表之中, 元素的类型始终如一, 但是一个列表的tail
的元素数目和其本身的元素数目不同 .
12 为什么这个事实相当重要呢?
元素的类型E 只需要确定一次, 之后整个消去过程中都会是相同的. 但是, 元素的数目却会随着ind-Vec
的每一步骤 (step) 而改变.动机是如何为step的类型所用的呢?
13 step的类型会在几乎答案的类型和答案的类型之中使用动机.
这意味着动机被用于不同数目的元素, 那就是为什么元素数目会是动机的参数.这两类 (variety) 类型构造子的参数, 即变或不变, 有着特别的名字. 那些不变的, 例如Vec
和List
中的元素类型, 被称为parameter , 而那些会变的则被称为指标(index) .
14 所以说Vec
中的元素数目是一个指标.
ind-Vec
之律
如果n 是一个Nat
, target 是一个(Vec E n )
, mot 是一个
(Π ((k Nat))
(→ (Vec E k) U ))
base 是一个
(mot zero vecnil)
, 而
step 是一个
(Π ((k Nat)
(h E )
(t (Vec E k)))
(→ (mot k t)
(mot (add1 k) (vec:: h t))))
那么
(ind-Vec n target
mot
base
step )
是一个
(mot n target )
.
是的, 的确如此.每当一个类型构造子拥有一个指标, 那么该指标就会出现在其消去子的动机之中, 因而也会出现在step之中. 15 ind-Vec
中base 的类型是什么呢?
base 的类型为(mot zero vecnil)
在ind-Vec
之中, mot 接受两个参数, 而非一个.16 第10章第47框 里的mot-replicate
不也是接受两个参数吗?
并非如此, 尽管似乎表面上是这样.回忆一下, mot-replicate
是Curried的函数. 应用mot-replicate
于第一个参数 (其是元素的类型) 将构造出一个和ind-Nat
一起使用的单参数的动机.
17 step 的类型是什么呢?
step 将对于某个列表t
而言的一个几乎答案转换为对于(vec:: h t)
而言的一个答案, 故其是一个(Π ((k Nat)
(h E )
(t (Vec E k)))
(→ (mot k t)
(mot (add1 k) (vec:: h t))))
为什么在答案的类型之中, mot 应用于(add1 k)
作为其第一个参数呢?18 step将t
的几乎答案转换为(vec:: h t)
的答案, 而后者比前者多一个元素.为什么这里列表的head
和tail
被称为h
和t
, 而非通常的e
和es
呢?
名字es 已被用来指代第二个target.现在是时候使用ind-Vec
来定义vec-append
了, 请开始.
19 正如append
, base是end
.(define vec-append
(λ (E l j )
(λ (es end)
(ind-Vec l es
mot-vec-append
end
step-vec-append))))
为什么end
的类型是(Vec E j)
呢? 20 在base情况下, es
是vecnil
, 这意味着es
的元素数目l
为zero
, 而(+ zero j)
和j
是相同的Nat
.| (Vec E (+ zero j))
| (Vec E j)
因而end
的类型为(Vec E j)
, 而这恰是我们所想要的.
现在请定义mot-vec-append
. 21 这个定义可以通过对于base的类型中的元素数目和列表进行抽象得到.(claim mot-vec-append
(Π ((E U )
(k Nat)
(j Nat))
(→ (Vec E k) U )))
(define mot-vec-append
(λ (E k j)
(λ (es )
(Vec E (+ k j)))))
按照第11章第21框 里的mot-vec-append
, vec-append
的定义需要一个λ
表达式作为其动机, 为什么呢?(define vec-append
(λ (E l j)
(λ (es end)
(ind-Vec l es
(λ (k)
(mot-vec-append E k j))
end
step-vec-append))))
22 因为动机的两个参数即是两个target, l
和es
, 但是这与mot-vec-append
的最后两个参数不相匹配, 故我们需要λ
表达式来交换k
和j
.
ind-Vec
之第一诫
ind-Vec
表达式
(ind-Vec zero vecnil
mot
base
step )
和
base 是相同的
(mot zero vecnil)
.
ind-Vec
之第二诫
ind-Vec
表达式
(ind-Vec (add1 n ) (vec:: e es )
mot
base
step )
和
(step n e es
(ind-Vec n es
mot
base
step ))
是相同的
(mot (add1 n ) (vec:: e es ))
.
我们可以转而考虑以下对于mot-vec-append
的定义.(claim mot-vec-append
(Π ((E U )
(j Nat)
(k Nat))
(→ (Vec E k) U )))
(define mot-vec-append
(λ (E j k)
(λ (es )
(Vec E (+ k j)))))
这如何改变vec-append
呢? 23 动机的λ
表达式不再是必要的了.(define vec-append
(λ (E l j)
(λ (es end)
(ind-Vec l es
(mot-vec-append E j)
end
step-vec-append))))
当编写Curried的动机, base或者step时, 我们需要仔细考虑参数的顺序. 24 重新排列mot-vec-append
的参数顺序当然要比编写额外的λ
表达式来得容易.
现在请定义step-vec-append
.step-vec-append
的类型是什么呢?
25 这一次, 参数中的j
位于k
之前.(claim step-vec-append
(Π ((E U )
(j Nat)
(k Nat)
(e E)
(es (Vec E k)))
(→ (mot-vec-append E j
k es)
(mot-vec-append E j
(add1 k) (vec:: e es)))))
你的观察相当敏锐.那么其定义该是什么呢?
26 (define step-vec-append
(λ (E j l-1 e es )
(λ (vec-appendes )
(vec:: e vec-appendes ))))
对于这里使用vec::
的澄清在于(+ (add1 l-1 ) j )
和(add (+ l-1 j ))
是相同的Nat
. 这依赖于关于+
的观察 .
现在vec-append
的所有组件皆已就位. 27 故以下的vec-append
定义无需虚框包裹.(define vec-append
(λ (E l j)
(λ (es end)
(ind-Vec l es
(mot-vec-append E j)
end
(step-vec-append E j)))))
上一章留下的第一条线索收尾了.
(vec-append Atom 3 2 treats drinks)
取什么名字好呢? 28 这个表达式并不由某个类型所描述, 鉴于drinks
是一个(List Atom)
.但是, 下面这个版本的fika
如何?
(claim fika
(Vec Atom 5))
(define fika
(vec-append Atom 3 2
treats
(list→vec Atom drinks)))
如若list→vec
是愚蠢的, 那么fika
也会是愚蠢的. 在第10章第81框 之中我们定义了一个愚蠢但却具有正确类型的list→vec
.(define step-list→vec
(λ (E e es)
(λ (list→veces )
(replicate E
(length E (:: e es))
e))))
(define list→vec
(λ (E es)
(ind-List es
(mot-list→vec E)
vecnil
(step-list→vec E))))
29 使用这个定义的话,(list→vec Atom drinks)
的规范形式为(vec:: 'coffee
(vec:: 'coffee vecnil))
但是某些人比起'coffee
可能更喜欢'cocoa
.我们该如何排除这种愚蠢呢?
到目前为止, 我们已经用了更加特化的类型来排除一些愚蠢的定义. 另一种排除愚蠢定义的方法在于证明 所作的定义并不愚蠢. 30 能举出这种证明的一个例子吗?
一种排除list→vec
的愚蠢定义的方法是证明将Vec
变回List
能够得到与之前相等的List
.这需要vec→list
, 以下是其动机.
(claim mot-vec→list
(Π ((E U )
(l Nat))
(→ (Vec E l )
U )))
(define mot-vec→list
(λ (E l )
(λ (es )
(List E))))
那step是什么呢? 31 这个step将每个vec::
替换以一个::
构造子, 就像step-list→vec
将每个::
替换以一个vec::
.(claim step-vec→list
(Π ((E U )
(l-1 Nat)
(e E)
(es (Vec E l-1 )))
(→ (mot-vec→list E
l-1 es)
(mot-vec→list E
(add1 l-1 ) (vec:: e es)))))
(define step-vec→list
(λ (E l-1 e es )
(λ (vec→listes )
(:: e vec→listes ))))
vec→list
的定义也和list→vec
的定义非常类似.(claim vec→list
(Π ((E U )
(l Nat))
(→ (Vec E l )
(List E))))
(define vec→list
(λ (E l )
(λ (es)
(ind-Vec l es
(mot-vec→list E)
nil
(step-vec→list E)))))
那么(vec→list Atom 3 treats)
的规范形式是什么呢?32 其为(:: 'kanelbullar
(:: 'plättar
(:: 'prinsesstårta nil)))
所以说是不是如何找出一个ind-Vec
表达式的值已经是很显然的了呢? 33 是的, 其实就和找出一个ind-List
表达式的值差不多, 除了step要应用于两个target.
以下陈述该如何写作类型呢?对于每个List
, 将其转换为一个Vec
然后再将其变回为一个List
可以产生一个与起始列表相等的List
. 34 用语每个 暗示了这里应该有一个Π
. 以下类型如何?(claim list→vec→list=
(Π ((E U )
(es (List E)))
(= (List E)
es
(vec→list E
(list→vec E es)))))
这已经非常接近于正确答案了, 但是vec→list
的第二个参数应该是Vec
的元素数目.(list→vec E es)
应该有几个元素呢? 35 好吧, 我还记得length
.(claim list→vec→list=
(Π ((E U )
(es (List E)))
(= (List E)
es
(vec→list E
(length E es)
(list→vec E es)))))
归纳的合适target是什么呢? 36 这个归纳的target应该是es
. 以下的定义有着通常的可疑分子 (suspect): 一个动机, 一个base, 以及一个step.(define list→vec→list=
(λ (E es)
(ind-List es
(mot-list→vec→list= E)
(base-list→vec→list= E)
(step-list→vec→list= E))))
base应该是什么呢? 37 base的类型为(= (List E)
nil
(vec→list E
(length E nil)
(list→vec E nil)))
也就是(= (List E) nil nil)
这是base的类型.但是, base本身究竟是什么呢?
38 当然是(same nil)
咯.
又一次, 我们无需定义basebase-list→vec→list=
以下是动机的类型.(claim mot-list→vec→list=
(Π ((E U ))
(→ (List E) U )))
现在请定义mot-list→vec→list=
. 39 对于第11章第37框 里base的类型中的nil
进行抽象即可将我们直接导向动机的定义.(define mot-list→vec→list=
(λ (E es)
(= (List E)
es
(vec→list E
(length E es)
(list→vec E es)))))
剩下来的就只有step了.step的合适类型会是什么呢?
40 遵循ind-List
之律即可.(claim step-list→vec→list=
(Π ((E U )
(e E)
(es (List E)))
(→ (mot-list→vec→list= E
es)
(mot-list→vec→list= E
(:: e es)))))
以下是step定义的开始.(define step-list→vec→list=
(λ (E e es)
(λ (list→vec→list=es )
list→vec→list=es )))
我们应该在这白框里填上什么以将一个对于es
而言的几乎证明转换为一个对于(:: e es)
而言的证明呢? 41 作为几乎证明的参数list→vec→list=es
是一个(= (List E)
es
(vec→list E
(length E es)
(list→vec E es)))
这是使用我们第8章所引入的老朋友cong
来消去list→vec→list=es
的好机会.
回忆一下, cong
表达了每个函数根据相等的参数都会产生相等的结果. 42 相等进, 相等出!这里我们将如何使用cong
呢?
(cong (same 'plättar)
(snoc Atom (:: 'kanelbullar nil)))
的类型是什么呢?43 snoc
尚未得到置于列表最后的新元素.因为
(same 'plättar)
是一个(= Atom 'plättar 'plättar)
而这新元素将会是'plättar
, 故其类型为(= (List Atom)
(:: 'kanelbullar
(:: 'plättar nil))
(:: 'kanelbullar
(:: 'plättar nil)))
请证明将'plättar
cons到两个相等的甜点清单 (list) 上将产生相等的甜点清单 . 44 这个证明可以用在我们要填的白框里.
首先, 这个陈述该如何写成一个类型呢? 45 两个相等的甜点清单 可以被写成是一个Π
表达式, 其带有两个类型为(List Atom)
的参数和一个它们相等的证明.(claim Treat-Statement U )
(define Treat-Statement
(Π ((some-treats (List Atom))
(more-treats (List Atom)))
(→ (= (List Atom)
some-treats
more-treats)
(= (List Atom)
(:: 'plättar some-treats)
(:: 'plättar more-treats)))))
如果有了以下定义, 证明这个陈述将变得更加容易.(claim ::-plättar
(→ (List Atom)
(List Atom)))
(define ::-plättar
(λ (tasty-treats)
(:: 'plättar tasty-treats)))
请以这个定义和cong
来证明Treat-Statement
. 46 以下是treat-proof
的定义.(claim treat-proof
Treat-Statement)
(define treat-proof
(λ (some-treats more-treats)
(λ (treats=)
(cong treats= ::-plättar))))
很好!关于相等列表的长度, 我们可以说些什么呢?
47 每两个相等的列表都拥有相等的长度.
现在请使用cong
来证明每两个相等的列表都拥有相等的长度 . 48 length-treats=
与treat-proof
相当类似.(claim length-treats=
(Π ((some-treats (List Atom))
(more-treats (List Atom)))
(→ (= (List Atom)
some-treats
more-treats)
(= Nat
(length Atom some-treats)
(length Atom more-treats)))))
(define length-treats=
(λ (some-treats more-treats)
(λ (treats=)
(cong treats= (length Atom)))))
让我们回到手头的问题上来, 现在我们可以给第11章第41框 中的白框里填上一个cong
表达式了.作为几乎证明的参数list→vec→list=es
是一个
(= (List E)
es
(vec→list E
(length E es)
(list→vec E es)))
那么第11章第41框 中的白框的类型是什么呢? 49 这个白框的类型为(= (List E)
(:: e es)
(vec→list E
(length E (:: e es))
(list→vec E (:: e es))))
现在是时候观察到一个关于list→vec
的事实了, 这类似于关于+
的观察 .| (vec→list E
(length E (:: e es))
(list→vec E (:: e es)))
的值是什么呢? 50 让我们现在看看.| (vec→list E
(add1 (length E es))
(vec:: e (list→vec E es)))
| (:: e
(vec→list E
(length E es)
(list→vec E es)))
若有疑问, 则求值
我们可以通过对于类型中的表达式进行求值以及运用相同于 图表计算例子来获得直觉 (insight).
这个新的观察与关于+
的观察 有何类似之处呢? 51 在之前的观察里, 我们可以从+
的第一个参数里抽出一个add1
并将其置于整个表达式之外.这新的观察在于我们可以类似地从list→vec
的第二个参数里抽出一个::
并将一个vec::
置于整个表达式之外.
当使用cong
时, 相同的函数将会被应用于一个=
表达式的from 部分和to 部分之上.什么函数可以将
es
转换为(:: e es)
而将(vec→list E
(length E es)
(list→vec E es))
转换为(:: e
(vec→list E
(length E es)
(list→vec E es)))
呢? 52 (:: e)
, 我说的对吗?
这非常接近了, 但是函数的构造子是λ
, 其他的构造子会构造不同的类型(的值). 53 以下函数可以执行前述的技巧 (trick).(claim ::-fun
(Π ((E U ))
(→ E (List E)
(List E))))
(define ::-fun
(λ (E )
(λ (e es)
(:: e es))))
现在请给第11章第41框 中的方框填上表达式来完成对于step-list→vec→list=
的定义. 54 以下就是了.(define step-list→vec→list=
(λ (E e es )
(λ (list→vec→list=es )
(cong list→vec→list=es
(::-fun E e)))))
现在是时候将各个组件拼装在一起了, 需要使用动机, base, 还有step. 请记得第11章第35框 中的claim
. 55 以下的定义同样不需要虚框了.(define list→vec→list=
(λ (E es)
(ind-List es
(mot-list→vec→list= E)
(same nil)
(step-list→vec→list= E))))
这个证明可以排除第11章第29框 里的愚蠢定义.不过, 为什么呢?
56 这是因为, 若是使用这个愚蠢定义的话,(vec→list Atom
(length Atom drinks)
(list→vec Atom drinks))
就不与drinks
相等了.
为什么不相等呢? 57 因为(:: 'coffee
(:: 'coffee nil))
与(:: 'coffee
(:: 'cocoa nil))
不相等.
诚然如此. 这个证明足以排除诸多愚蠢的定义. 59 诸多?
在某个阶段, 我们不得不相信足够特化的类型已经避免了容易遭致的愚蠢. 这需要来之不易的自知之明.若是vec→list
能够消除由list→vec
所引入的愚蠢, 那么问题仍然不会被人发现.
60 什么情况下会发生这种事情呢?
想象一下, 如果vec→list
和list→vec
都会逆转列表的顺序. 61 我需要来点咖啡和蛋糕, 这对想象有益.
在这想象中的世界里, 证明能够成立, 但是vec→list
和list→vec
都是愚蠢的. 62 若是它们也能反转列表的话, 名字里就应该体现出这一点来!
第12章 Even Numbers Can Be Odd 什么叫做将一个数字分成两个相等的部分? 2 这意味着存在某个数字使得其加上自身能够产生原来的数字 .
这个定义该如何写成一个类型呢? 3 根据第10章第18框 , 我们可以使用一个Σ
表达式.
一个存在 陈述具有两个重要的部分: 一个是存在的这个东西的类型, 另一个是其所具有的性质.这里, 存在的东西的类型为Nat
, 而其性质为恰好是偶Nat
的一半. 这分别对应于存在 陈述的证据的car
和cdr
部分.
4 那么Even
该长什么样呢?
偶性 (evenness) 的定义可以被写成是返回一个类型的函数.(claim Even
(→ Nat U ))
(define Even
(λ (n)
(Σ ((half Nat))
(= Nat n (double half)))))
(Even 10)
的值是什么呢? 5 (Even 10)
的值为(Σ ((half Nat))
(= Nat 10 (double half)))
类型(Even 10)
中有什么值呢? 6 这些值看起来像(cons a d )
, 其中a
是一个Nat
, 而d
是一个(= Nat 10 (double a ))
请找出a 和d 使得(cons a d )
是一个(Even 10)
7 a 显然应该是5
, 因为5
是10
的一半, 而(same 10)
是一个(= Nat 10 (double 5))
这就是证明10
为偶数所需的.那么, 证明本身是什么呢?
8 这个证明即(cons 5
(same 10))
这很正确. 那么0
如何呢? 9 0
的一半还是0
.(claim zero-is-even
(Even 0))
(define zero-is-even
(cons 0
(same 0)))
Even
还可以怎么定义呢?10 用+
也行.(define Even
(λ (n)
(Σ ((half Nat))
(= Nat n (+ half half)))))
这当然是成立的, 毕竟对于每个n , (twice n )
等于(double n )
. 尽管这两个函数总是返回相等的答案, 有时其中一个比另一个更容易使用, 以其更快变成值. 就这里的情形而言, +
以及基于+
的twice
会在第二个参数上遗留一个add1
, 而double
会立刻将两个add1
搬运到顶层. 11 正如第9章第52框 中的对于twice=double
的证明所言.
以下陈述该如何写成一个类型呢?比每个偶数大二的数字仍为偶数 . 12 好问题.
在将陈述转换为类型时, 更具描述性 (descriptive) 的表达可能更加实用.以下是言称相同陈述的另一方式:
对于每个自然数n , 如果n 是偶数, 那么2 + n 也是偶数 . 13 每个 听起来像是Π
.(claim +two-even
(Π ((n Nat))
(→ (Even n)
(Even (+ 2 n)))))
现在请证明它. 14 显然, 这个证明需要用到ind-Nat
, 鉴于类型依赖于一个Nat
.
实际上不用归纳法也能证明.不过, 定义的哪些部分是能够立即写出的呢?
15 以下是一个开始...(define +two-even
(λ (n en )
...这里该填什么呢? ))
好问题.如果5
是10
的一半, 那么(+ 2 10)
的一半又是什么呢?
16 (+ 2 10)
和12
是相同的Nat
, 而12
的一半是6
.
如果6
是12
的一半, 那么(+ 2 12)
的一半又是什么呢? 17 (+ 2 12)
和14
是相同的Nat
, 而14
的一半是7
.这里遵循着重复的模式.
是的, 的确是重复的模式. 这种模式可以用来给框里填上内容.如果a 是n
的一半, 那么(+ 2 n)
的一半是什么呢?
18 应该是(add1 a )
.不过, 在我们要填的方框里, a 在哪里呢?
其实就是(car en )
, 因为en
是一个(Even n)
.这意味着
(car en )
是n
的一半, 而(cdr en )
正好证明了这一点. 19 是的, 因为car
和cdr
用在由Σ
所描述的表达式上.
如果p 是一个(Σ ((x A ))
D )
那么(car p )
是一个A , 而(cdr p )
的类型则由将D 中的每个x 一致地替换为(car p )
得到. 20 这不过就是对于第10章第6框 的内容的重述.
现在我们可以继续推进+two-even
的定义了. 21 这λ
表达式的体以cons
为顶, 因为其必然是一个(Even (+ 2 n))
(define +two-even
(λ (n en )
(cons (add1 (car en ))
)))
而其car
为(add1 (car en ))
则是因为(car en )
是n
的一半.
到目前为止, 一切顺利.(cdr en )
的类型是什么呢?
22 (cdr en )
是一个(= Nat
n
(double (car en )))
也就是说我们已经有了一个可用的相等性证明, 而它是几乎(almost) 正确的. 23 该如何将一个(= Nat
n
(double (car en )))
转换为一个(= Nat
(+ 2 n)
(double (add1 (car en ))))
呢?
这就是选择double
而非twice
的价值所在, 正如第9章第59框 中我们定义double-Vec
时的情况.(double (add1 (car en )))
和(add1
(add1
(double (car en ))))
是相同的Nat
. 24 若将其中的double
替换为twice
, 那么这个Nat
将会变成(add1
(+ (car en )
(add1 (car en ))))
而我们需要更多的工作才能将两个add1
都移动到顶层.
谨慎选择定义
谨慎选择的定义可能将极大地简化之后的证明.
在第12章第21框 要填的方框之中,(cdr en )
是一个(= Nat
n
(double (car en )))
请找出一个表达式, 其类型为(= Nat
(+ 2 n)
(add1
(add1
(double (car en )))))
25 表达式(cong (cdr en ) (+ 2))
能够满足我们的要求, 因为(+ 2 n)
和(add1
(add1 n))
是相同的Nat
.
这恰好就是我们为了完成证明所需要的东西了. 26 感谢之前的提示.(define +two-even
(λ (n en )
(cons (add1 (car en ))
(cong (cdr en ) (+ 2)))))
请用+two-even
来证明它. 28 为了使用+two-even
, 我们需要0
为偶数的证据. 这个证据出现在第12章第9框 之中.(claim two-is-even
(Even 2))
(define two-is-even
(+two-even 0 zero-is-even))
以下是two-is-even
的值.| two-is-even
| (+two-even 0 zero-is-even)
| (cons
(add1 (car zero-is-even))
(cong (cdr zero-is-even)
(+ 2)))
现在请找出其规范形式. 29 找出规范形式只需要再多走几步而已.| (cons (add1 zero)
(cong (same zero) (+ 2)))
| (cons 1
(same (+ 2 zero)))
| (cons 1 (same 2))
有没有更显式的表达方式? 31 奇数没有办法分成相等的两个部分, 总会余下一个add1
.
这个描述该如何写成一个类型呢?提示: 使用Even
的定义作为指引.
32 这难道不是一个奇怪 (odd) 的定义吗?(claim Odd
(→ Nat U ))
(define Odd
(λ (n)
(Σ ((haf Nat))
(= Nat n (add1 (double haf))))))
并非如此, 不过这的确是Odd
的定义.haf
是什么意思呢?
33 它和half
相当接近, 其为比n
小一的偶数的一半.
以下是1
为奇数的声明.(claim one-is-odd
(Odd 1))
现在请证明它. 34 以下即是证明.(define one-is-odd
(cons 0
(same 1)))
之所以这里的cdr
是(same 1)
是因为(same 1)
是一个(= Nat 1 (add1 (double 0)))
现在请证明13
是奇数 . 35 面包师的一打的haf 为6
.(claim thirteen-is-odd
(Odd 13))
(define thirteen-is-odd
(cons 6
(same 13)))
如果n 为偶数, 那么关于(add1 n )
可以言称什么呢? 36 你所意图的是否是以下这个陈述?若n 为偶数, 则(add1 n )
为奇数 .
是的.该如何将其写作一个类型呢?
37 其应该使用一个Π
表达式和一个→
表达式, 因为n 意味着对于每个n , 而若—则 陈述相当于→
表达式.
现在请翻译陈述. 38 以下就是了.(claim add1-even→odd
(Π ((n Nat))
(→ (Even n)
(Odd (add1 n)))))
那么其证据是什么呢? 请记住, 真性等同于(the same as) 拥有证据, 然而到现在为止还未有证据呈现. 40 难道说这个陈述是假的?
那倒不是.我只是说既没有陈述为真的证据, 也没有陈述为假的证据. 暂时, 这仍然是一个谜.
41 我们最好赶快把这个谜团解开吧.
为了解决这个谜团, 我们应该考虑当n 为偶数时n 的一半和(add1 n )
的haf 之间的关系. 42 它们是相同的Nat
.
为什么它们是相同的Nat
呢? 43 这是因为Odd
相较于Even
在其定义中的相等类型的from 部分所出现的额外add1
正好与会出现在to 部分的n 的额外add1
相抵消 .
现在请使用这个重要的事实来证明这个谜样的陈述并使得其为真. 44 Et voilà! (define add1-even→odd
(λ (n en )
(cons (car en )
(cong (cdr en ) (+ 1)))))
定义应该写来让人理解.为什么这个定义是正确的呢?
45 这个λ
表达式的体中是一个cons
表达式, 其为对于(Odd (add1 n))
的证明.
你对于这个证明的car
部分有什么看法吗? 46 这个car
部分是(car en )
, 因为一个奇数的haf 即比它小一的偶数的一半.
那对于cdr
部分的看法呢? 47 这个cdr
部分以cong
构建, 因为(cdr en )
是一个(= Nat
n
(double (car en )))
而(Odd (add1 n))
要求其是一个(= Nat
(add1 n)
(add1 (double (car en ))))
现在这个陈述的确为真了. Take a bow.如果n 为奇数, 那么关于
(add1 n )
我们可以说些什么呢? 48 显然,若n 为奇数, 则(add1 n )
为偶数 .
这和刚才的声明如出一辙... 49 诚然如此.(claim add1-odd→even
(Π ((n Nat))
(→ (Odd n)
(Even (add1 n)))))
现在是使得该声明为真的时间.25
的haf 是多少?
50 其为12
, 因为(add1 (double 12))
和25
是相同的Nat
.
那么26
的一半是什么呢? 51 其为13
, 因为(double 13)
和26
是相同的Nat
.
遵循这个模板, 某个奇数n 的haf 和(add1 n )
的一半之间有什么关系呢? 52 如果a 是奇数n 的haf , 那么偶数(add1 n )
的一半是(add1 a )
.
现在请开始定义, 使用这个haf . 53 以下就是了.(define add1-odd→even
(λ (n on )
(cons (add1 (car on ))
)))
这个框里需要填上一个(= Nat
(add1 n)
(double (add1 (car on ))))
这个类型是从何而来的呢? 54 其来源于Even
的定义与cdr
之诫的组合.
(cdr on )
具有什么类型呢?55 在这个框里,(cdr on )
是一个(= Nat
n
(add1 (double (car on ))))
那么我们该如何使用(cdr on )
来构造(= Nat
(add1 n)
(double (add1 (car on ))))
的证据呢? 56 cong
就够了, 因为(double (add1 (car on )))
和(add1
(add1
(double (car on ))))
是相同的Nat
.(define add1-odd→even
(λ (n on )
(cons (add1 (car on ))
(cong (cdr on ) (+ 1)))))
这个定义不再需要虚框. 57 Whew! 是时候再来一场fika 了.
Behold! Ackermann!
(claim repeat
(→ (→ Nat Nat) Nat Nat))
(define repeat
(λ (f n)
(iter-Nat n
(f 1)
(λ (iterf,n-1 )
(f iterf,n-1 )))))
(claim ackermann
(→ Nat Nat Nat))
(define ackermann
(λ (n)
(iter-Nat n
(+ 1)
(λ (ackermannn-1 )
(repeat ackermannn-1 )))))
第13章 Even Haf a Baker's Dozen 每个自然数都是偶数或者奇数吗? 1 大概如此.不过证据该如何寻找呢?
将每个自然数都是偶数或者奇数 写成一个类型需要一种新的类型构造子: Either
, 其可以用来将或 (or) 表达为类型. 2 这看起来很合理的样子.何时Either
构造一个类型呢?
如果L 是一个类型而R 是一个类型, 那么(Either L R )
是一个类型. 3 (Either L R )
的值是什么呢?
Either
之律
如果L 是一个类型而R 是一个类型, 那么(Either L R )
是一个类型.
其存在两个构造子. 如果lt 是一个L , 那么(left lt )
是一个(Either L R )
. 如果rt 是一个R , 那么(right rt )
是一个(Either L R )
.何时两个类型为(Either L R )
的值是相同的呢?
4 基于之前的类型, 以下是我的猜测.如果lt 1 和lt 2 是相同的L , 那么(left lt 1 )
和(left lt 2 )
是相同的(Either L R )
.
到目前为止, 你说得很好. 不过, 还有要补充的吗? 5 是的, 还有一点. 如果rt 1 和rt 2 是相同的R , 那么(right rt 1 )
和(right rt 2 )
是相同的(Either L R )
.
left
之律
如果lt 是一个L , 那么(left lt )
是一个(Either L R )
.
right
之律
如果rt 是一个R , 那么(right rt )
是一个(Either L R )
.
的确以上就是所有(相同)的可能了.Either
的消去子叫做ind-Either
.
7 这并不令人意外.
ind-Either
拥有两个base, 但却没有step.为什么呢?
8 这是因为尽管存在两种构造一个(Either L R )
的方式, 但是left
和right
都不会以某个(Either L R )
作为参数.
那么, ind-Either
可以引入递归吗? 9 当然不行了, 这是因为Either
的两个构造子left
和right
均不是递归性的.
在一个ind-Either
表达式(ind-Either target
mot
base-left
base-right )
之中, target 是一个(Either L R )
. 10 mot 解释了为何target 会被消去吗?
和往常一样, 的确如此. mot 的类型是一个(→ (Either L R ) U )
base-left 解释了对于每个left
而言, 动机是如何 满足的. 换言之, base-left 的类型为(Π ((x L ))
(mot (left x)))
11 base-right 的类型以相同的方式构建吗?
的确如此, base-right 解释了对于每个right
而言, 动机是如何 满足的.base-right 的类型是什么呢?
12 base-right 的类型为(Π ((y R ))
(mot (right y)))
鉴于每个 (every) 在写成类型时会变为Π
.
(ind-Either (left x )
mot
base-left
base-right )
的值是什么呢?13 其为(base-left x )
的值, 这其实是可用的表达式之中唯一具有正确类型的.
(ind-Either (right y )
mot
base-left
base-right )
的值是什么呢?14 其是(base-right y )
的值, 出于相同的原因.
ind-Either
之律
如果target 是一个(Either L R )
, mot 是一个
(→ (Either L R ) U )
base-left 是一个
(Π ((x L ))
(mot (left x)))
而
base-right 是一个
(Π ((y R ))
(mot (right y)))
那么
(ind-Either target
mot
base-left
base-right )
是一个
(mot target )
.
ind-Either
之第一诫
(ind-Either (left x )
mot
base-left
base-right )
和
(base-left x )
是相同的
(mot (left x ))
.
ind-Either
之第二诫
(ind-Either (right y )
mot
base-left
base-right )
和
(base-right y )
是相同的
(mot (right y ))
.
现在我们知道该如何将之前的陈述写成类型了, 即每个自然数都是偶数或者奇数 .(claim even-or-odd
(Π ((n Nat))
(Either (Even n) (Odd n))))
15 这是一条关于所有Nat
的声明, 那么其证明需要使用ind-Nat
吗?
是的, 的确如此.mot-even-or-odd
描述了消去的意图. 请尝试在不先找出base的情况下定义动机.
(claim mot-even-or-odd
(→ Nat U ))
16 我们可以对于第13章第15框 中的n
进行抽象.(define mot-even-or-odd
(λ (k)
(Either (Even k) (Odd k))))
不错的选择.base是什么呢?
17 这个base是一个(Either (Even zero) (Odd zero))
而zero
恰好是一个偶数.
听起来有点熟悉? 18 当然咯.这个base应该是
(left zero-is-even)
是的.step的类型是什么呢?
19 step的类型可以使用动机得到.(claim step-even-or-odd
(Π ((n-1 Nat))
(→ (mot-even-or-odd n-1)
(mot-even-or-odd (add1 n-1)))))
现在请定义step-even-or-odd
. 20 以下是我们的开始...(define step-even-or-odd
(λ (n-1)
(λ (e-or-on-1 )
...但是这里该填什么呢? )))
e-or-on-1
的类型是什么?21 这个类型来源于step的声明.| (mot-even-or-odd n-1)
| (Either (Even n-1) (Odd n-1))
Either
的消去子是什么?22 当然是ind-Either
了.
所以说请消去它. 23 以下是一个带有三个要填的空白方框的版本.(define step-even-or-odd
(λ (n-1)
(λ (e-or-on-1 )
(ind-Either e-or-on-1
))))
这真是不错的开始.动机是什么呢?
24 根据step-even-or-odd
的声明, 这个消去过程应该产生一个(mot-even-or-odd (add1 n-1))
这次请尝试使用λ
表达式而不是单独定义动机. 动机的参数是消去的target, 不过这个消去并没有产生依赖于target的类型, 故动机的参数是黯淡的. 25 比起单独定义动机, 使用λ
表达式要短上许多.(define step-even-or-odd
(λ (n-1)
(λ (e-or-on-1 )
(ind-Either e-or-on-1
(λ (e-or-on-1 )
(mot-even-or-odd
(add1 n-1)))
))))
是的, 的确更短. 不过, 更短不总是意味着更容易阅读. 每次动笔编写程序时, 请比较两种风格并判断到底哪一种更容易理解.当n-1
为偶数时, (add1 n-1)
为奇数的证据是什么呢?
26 这个证据可以由add1-even→odd
构造出来.
第13章第25框 的第一个空白方框是一个(→ (Even n-1)
(Either
(Even (add1 n-1))
(Odd (add1 n-1))))
27 ind-Either
之律言称left
的base应该是一个(Π ((x L ))
(mot (left x)))
那么为什么这个空白方框的类型不以Π
为顶呢?
这个类型的确以Π
为顶, 因为→
是Π
在其参数未被用到时的另一种写法, 见第6章第40框 . 28 鉴于(add1 n-1)
是奇数, 所以我们应该使用right
:(λ (en-1 )
(right
(add1-even→odd n-1 en-1 )))
是这样的.那么, 第二个空白方框该填什么呢?
29 在这个方框里, n-1
为奇数. 因此, (add1 n-1)
是偶数, 而我们应该使用left
:(λ (on-1 )
(left
(add1-odd→even n-1 on-1 )))
现在请将以上部件组装为step-even-or-odd
的定义. 30 给空白方框填上就行.(define step-even-or-odd
(λ (n-1)
(λ (e-or-on-1 )
(ind-Either e-or-on-1
(λ (e-or-on-1 )
(mot-even-or-odd
(add1 n-1)))
(λ (en-1 )
(right
(add1-even→odd
n-1 en-1 )))
(λ (on-1 )
(left
(add1-odd→even
n-1 on-1 )))))))
现在请定义even-or-odd
. 31 所有组件皆已就位.(define even-or-odd
(λ (n)
(ind-Nat n
mot-even-or-odd
(left zero-is-even)
step-even-or-odd)))
even-or-odd
是对于每个自然数都是偶数或者奇数 的证明, 但是这又不仅仅是一个证明——其是一个λ
表达式, 在接受一个参数时会产生一个值.32 其总是会产生一个值, 鉴于所有函数都是完全的.这个值很有趣吗?
让我们看看.(even-or-odd 2)
的值是什么?
33 这是个有趣的问题.
请为冗长的相同于 图表作好准备, 以下是我们的开始.| (even-or-odd 2)
| ((λ (n)
(ind-Nat n
mot-even-or-odd
(left zero-is-even)
step-even-or-odd))
2)
| (ind-Nat 2 ...)
| (step-even-or-odd
1
(ind-Nat 1 ...))
在这个图表里, 省略号...
代表ind-Nat
或者ind-Either
的参数压根没有发生变化. 34 接着写.| ((λ (n-1)
(λ (e-or-on-1 )
(ind-Either e-or-on-1
(λ (e-or-on-1 )
(mot-even-or-odd
(add1 n-1)))
(λ (en-1 )
(right
(add1-even→odd
n-1 en-1 )))
(λ (on-1 )
(left
(add1-odd→even
n-1 on-1 ))))))
1 (ind-Nat 1 ...))
在每一步时, 请寻找表达式中变与不变的部分.请你试着识别出现了多次的动机, base和step.
35 target是什么情况呢?
一般而言, target鲜有重复, 不过也值得一看.| ((λ (e-or-on-1 )
(ind-Either e-or-on-1
(λ (e-or-on-1 )
(mot-even-or-odd 2))
(λ (en-1 )
(right
(add1-even→odd
1 en-1 )))
(λ (on-1 )
(left
(add1-odd→even
1 on-1 )))))
(ind-Nat 1 ...))
| (ind-Either (ind-Nat 1 ...)
(λ (e-or-on-1 )
(mot-even-or-odd 2))
(λ (en-1 )
(right
(add1-even→odd
1 en-1 )))
(λ (on-1 )
(left
(add1-odd→even
1 on-1 ))))
36 啊, 这是因为一旦找到了target的值之后, 就会开始选择base或者step.| (ind-Either
(step-even-or-odd
0
(ind-Nat 0 ...))
(λ (e-or-on-1 )
(mot-even-or-odd 2))
(λ (en-1 )
(right
(add1-even→odd
1 en-1 )))
(λ (on-1 )
(left
(add1-odd→even
1 on-1 ))))
| (ind-Either
((λ (n-1)
(λ (e-or-on-1 )
(ind-Either e-or-on-1
...)))
0 (ind-Nat 0 ...))
(λ (e-or-on-1 )
(mot-even-or-odd 2))
(λ (en-1 )
(right
(add1-even→odd
1 en-1 )))
(λ (on-1 )
(left
(add1-odd→even
1 on-1 ))))
| (ind-Either
((λ (e-or-on-1 )
(ind-Either e-or-on-1
(λ (e-or-on-1 )
(mot-even-or-odd 1))
(λ (en-1 )
(right
(add1-even→odd
0 en-1 )))
(λ (on-1 )
(left
(add1-odd→even
0 on-1 )))))
(ind-Nat 0 ...))
(λ (e-or-on-1 )
(mot-even-or-odd 2))
(λ (en-1 )
(right
(add1-even→odd
1 en-1 )))
(λ (on-1 )
(left
(add1-odd→even
1 on-1 ))))
37 | (ind-Either
((λ (en-1 )
(right
(add1-even→odd
0 en-1 )))
zero-is-even)
(λ (e-or-on-1 )
(mot-even-or-odd 2))
(λ (en-1 )
(right
(add1-even→odd
1 en-1 )))
(λ (on-1 )
(left
(add1-odd→even
1 on-1 ))))
| (ind-Either
(right
(add1-even→odd
0 zero-is-even))
(λ (e-or-on-1 )
(mot-even-or-odd 2))
(λ (en-1 )
(right
(add1-even→odd
1 en-1 )))
(λ (on-1 )
(left
(add1-odd→even
1 on-1 ))))
| ((λ (on-1 )
(left
(add1-odd→even
1 on-1 )))
(add1-even→odd
0 zero-is-even))
38 | (left
(add1-odd→even
1
(add1-even→odd
0
zero-is-even)))
这个图表目前的最后一个表达式是一个值.Whew!
(left
(add1-odd→even
1
(add1-even→odd
0
zero-is-even)))
的确是一个值.我们可以从这个值中了解到什么信息呢?
39 根据这个值, 显然我们可以判断出2
是偶数, 因为这个值以left
为顶.
在当前的情况下, 还有更多的信息值得挖掘.请找出
(left
(add1-odd→even
1
(add1-even→odd
0
zero-is-even)))
的规范形式. 40 找出这个规范形式的第一步是将add1-odd→even
替换为其定义.
是这样的.| (left
((λ (n on )
(cons (add1 (car on ))
(cong (cdr on ) (+ 1))))
1
(add1-even→odd
0
zero-is-even)))
下一步是什么呢? 41 下一步是将n
替换为1
以及将add1-even→odd
替换为其定义.| (left
((λ (on )
(cons (add1 (car on ))
(cong (cdr on ) (+ 1))))
((λ (n en )
(cons (car en )
(cong (cdr en ) (+ 1))))
0
zero-is-even)))
然后我们应该顺便展开zero-is-even
的定义.| (left
((λ (on )
(cons (add1 (car on ))
(cong (cdr on ) (+ 1))))
((λ (en )
(cons (car en )
(cong (cdr en )
(+ 1))))
zero-is-even)))
| (left
((λ (on )
(cons (add1 (car on ))
(cong (cdr on ) (+ 1))))
((λ (en )
(cons (car en )
(cong (cdr en )
(+ 1))))
(cons 0 (same 0)))))
接着呢? 42 接着, 找出en
的car
和cdr
部分.| (left
((λ (on )
(cons (add1 (car on ))
(cong (cdr on ) (+ 1))))
(cons 0
(cong (same 0) (+ 1)))))
下一步似乎我们应该找出(cong (same 0) (+ 1))
的值, 而根据cong
之诫, 其值为(same 1)
以下就是了.| (left
((λ (on )
(cons (add1 (car on ))
(cong (cdr on ) (+ 1))))
(cons 0
(same 1))))
为了得到规范形式, 还剩什么步骤呢? 43 还有一个cong
表达式可以变得更加直接.| (left
(cons 1
(cong (same 1) (+ 1))))
| (left
(cons 1
(same 2)))
从这个规范形式中我们可以了解到什么信息呢? 44 根据值, 我们可以看出2
是偶数, 而这个规范形式还有藏在left
下的对于2
是偶数的证明 .
相同于 图表里的每一步骤都和前一步是相同的, 因此即便是值也包含着证明.45 然而, 规范形式往往更加易于理解, 这里的情况也并不例外.
从这样一个证明之中我们可以了解到什么呢? 46 不仅2
是偶数, 而且1
是2
的一半.
类似于even-or-odd
这样的定义扮演着双重的角色. 在第一个角色之中, even-or-odd
是对于每个Nat
都是偶数或者奇数的证明 . 47 另一个角色是什么呢?
在第二个角色之中, even-or-odd
是一个函数 , 其可以判断 一个Nat
是偶数还是奇数. 为了达到这个目的, 其需要找出Nat
的一半 (half) 或者haf . 48 even-or-odd
的有趣之处在于其既 可以作为一个陈述的证据也 可以用于找出(意图的)结果.
诚然如此, 现在是时候通过林中漫步放松一下了. 49 听起来不错.
第14章 There's Safety in Numbers 请从以下菜单里选择一道菜品:ratatouille kartoffelmad hero sandwich prinsesstårta 1 我要选第十四个.
菜单上只有四道菜, 所以说你什么也不会得到. 2 那可真是不幸啊.
为了从一个列表中选出一个特定的元素, 我们必须要知道当没有足够的元素时该做什么. 3 我们或许可以说可能存在着某个元素, 但是也可能并不存在.
为了表示并不存在某个元素的情形, 我们需要一个新的类型, 叫做Trivial
. 4 什么是Trivial
?
Trivial
是一个类型, 而sole
是一个Trivial
.每个Trivial
表达式都和sole
是相同的Trivial
.
5 那么中立的Trivial
表达式又怎么样呢?
仍是如此, 中立的Trivial
表达式也和sole
是相同的, 而这就是所有关于Trivial
要说的事情了. 6 这个类型的命名恰如其分.
sole
之诫
如果e 是一个Trivial
, 那么e 和sole
是相同的.
某个元素可能或者并不存在于一个列表之中的事实可以使用Maybe
表示.(claim Maybe
(→ U U ))
7 Maybe
是如何表示出席 (presence) 或者缺席 (absence) 的呢?
要么是一个X
, 要么是一个Trivial
.(define Maybe
(λ (X)
(Either X Trivial)))
8 好吧.
缺席可以使用(right sole)
指明.(claim nothing
(Π ((E U ))
(Maybe E)))
(define nothing
(λ (E )
(right sole)))
9 想必出席应该使用left
.
的确如此, 以下是其声明.(claim just
(Π ((E U ))
(→ E (Maybe E))))
10 为了使用left
, 一个E
是不可或缺的.(define just
(λ (E e)
(left e)))
使用Maybe
, 我们可以写下List
的一个完全版本的head
.(claim maybe-head
(Π ((E U ))
(→ (List E) (Maybe E))))
11 根据这个类型, 此定义以λ
开始.(define maybe-head
(λ (E es)
))
从(maybe-head Atom nil)
之中我们应该期望什么? 12 其应该是(nothing Atom)
, 因为空列表并没有头部 (head).
我们应该从(maybe-head Atom
(:: 'ratatouille
(:: 'kartoffelmad
(:: (sandwich 'hero)
(:: 'prinsesstårta nil)))))
之中期望什么? 13 其应该是(just Atom 'ratatouille)
.
第15章 Imagine That ...