The Little Typer

目前的排版不论从效果还是从实现来看都相当丑陋, 但也只能将就一下了. 或许只有等到博客进行大型实质性重构时, 我才会考虑这些问题.

和其他小人书一样, 本书也包含大量文字游戏, 这有点难以传达? 若有必要, 我会添加一些译注.

他序

自序

第1章 愈是变化, 愈是不变

欢迎回来!1很高兴回到这里!
让我们为一门叫做Pie的新语言擦拭并更新一些我们的旧玩具.
以下东西是一个Atom, 这显然吗?
'atom
2一点也不, Atom是什么意思?
要成为一个Atom, 就是要成为一个原子(atom).
注记. 在Lisp中, 原子可以是符号, 数字, 以及其他许多东西. 但在这里, 原子只是符号.
3那么, 'atom是一个Atom是因为'atom是一个原子.
以下东西是一个Atom, 这显然吗?
'ratatouille
译注. ratatouille指的是普罗旺斯杂烩.
4是的, 因为'ratatouille也是一个原子.

但是, 精确地说, 什么是一个原子呢?

原子是由一个单引号 (tick mark) 后跟着一个或更多的字母或者连字符而来的.
注记. 在Pie中, 只有原子使用单引号.
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吗?
译注. cœurs d'artichauts是一种食物, 即洋蓟心.
9是的, 因为œ是一个字母.
'ἄτομον是一个Atom吗?
译注. ἄτομον是一个古希腊语词, 意思是不可分割的东西, 即原子.
10That's Greek to me!
译注. 这算是一语双关.
但是希腊字母也是字母, 所以它必然是一个Atom.
单引号之律

一个单引号后直接跟着一个或更多的字母或者连字符则为一个Atom.

诸如

'ratatouille是一个Atom

'cœurs-d-artichauts是一个Atom

这样的句子被称为判断(judgment).
注记. 感谢Per Martin-Löf (1942–).
11判断的要义在于什么?
一个判断是一个人对于一个表达所采取的一个态度. 当我们得以获知什么东西时, 我们就在作出一个判断.
关于Atom'courgette, 可以判断什么?
12'courgette是一个Atom.
译注. courgette指的是西葫芦.
一个判断形式(form of judgment)是一个带有空白的观察, 例如

____是一个____.

13还有其他的判断形式吗?
判断 (judgment)的另一种形式是判断 (judgement).14十分有趣.
'ratatouille
'ratatouille
是相同的
Atom
吗?
15是的.

它们之所以是相同的Atom, 是因为单引号之后有着相同的字母.

'ratatouille
'courgette
是相同的
Atom
吗?
16不是.

单引号之后, 它们有着不同的字母.

单引号之诫

两个表达式为相同的Atom, 如果它们的值是单引号后跟着完全等同的字母和连字符.

第二种判断形式为

____和____是相同的____.

17因而

'citron'citron是相同的Atom

是一个判断.
译注. citron指的是香橼.
这的确是一个判断, 而且我们有理由去相信.

'pomme'orange是相同的Atom

是判断吗?

18诚然它是判断, 但是我们没有理由去相信它. 毕竟, 我们不该比较苹果 (apple) 和橙子.
译注. pomme是法语的苹果.
(cons 'ratatouille 'baguette)
是一个
(Pair Atom Atom)
这显然吗?
注记. 当准备好的时候, 读[page 62]看定型 (typing)指令.
19不, 完全不是.

成为一个

(Pair Atom Atom)
是什么意思呢?

译注. baguette指的是法棍面包.
成为一个
(Pair Atom Atom)
就是要成为一个序对, 其car是一个Atom, 例如'ratatouille, 其cdr也是一个Atom, 例如'baguette.
20cons, car, cdr看上去很眼熟. 不谈以前, 这里它们是什么意思呢? 它们和序对 (pair) 又有什么关系呢?
一个序对以cons起手, 而以另外两个部分作结, 我们称其为它的carcdr.
注记. 在Lisp中, cons可以使得列表更长. 但在这里, cons只是用来构造序对.
21好吧, 这意味着之所以
(cons 'ratatouille 'baguette)
是一个
(Pair Atom Atom)
是因为(cons 'ratatouille 'baguette)是一个序对, 其car是一个Atom, 其cdr也是一个Atom.

那么, cons是一个Pair吗?

甚至consPair在单独情况下都不是表达式, 它们都需要两个参数.

(cons 'ratatouille 'baguette)
(cons 'ratatouille 'baguette)
是相同的
(Pair Atom Atom)
吗?

注记. 在Lisp中, cons是一个过程, 有着其自身的含义, 但是诸如condlambda这样的形式如果单独出现则是没有意义的.
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)是一个类型吗?

注记. 当一个名字 (例如Pair或者Atom) 牵涉类型时, 首字母会大写.
26是的, 因为它描述了carcdr均为Atom的序对.
第三种判断形式为

____是一个类型.

27这意味着

Atom是一个类型

(Pair Atom Atom)是一个类型

都是判断.
Atom之律

Atom是一个类型.

'courgette是一个类型.

是一个判断吗?
28它的确是一个判断, 但是我们没有任何理由去相信它, 因为'courgette并不描述其他表达式.
AtomAtom是相同的类型吗?29想必如此, 它们看上去就应该是相同的类型.
第四种判断形式, 也是最后一种, 如下

____和____是相同的类型.

30那么, 所以说

AtomAtom是相同的类型

是一个判断, 并且我们有理由去相信它.
判断的四种形式
  1. ____是一个____.
  2. ____和____是相同的____.
  3. ____是一个类型.
  4. ____和____是相同的类型.
以下是一个判断吗?

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吗?

译注. pêche指的是桃子.
35好问题.

'pêche是一个'fruit吗?

不, 当然不是, 因为

'fruit是一个类型

并不可信.

某些形式的判断只有在早前的判断的基础之上才具备意义.

注记. 这种早前的判断有时被称为前置假定(presupposition).
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))
是一个...
译注. aubergine指的是茄子.
40... (Pair Atom Atom), 因为
(cons 'aubergine 'courgette)
是一个序对, 其carAtom 'aubergine, 其cdrAtom '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-oilAtom
(cdr
 (cdr
  (cons
   'ratatouille
   (cons 'baguette 'olive-oil))))
的规范形式吗?
是的, 的确如此.
(cons 'ratatouille 'baguette)
是一个规范的
(Pair Atom Atom)
吗?
注记. 规范的(normal)具有规范形式(in normal form)的简略说法.
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)

注记. 在Lisp中, 相同的原子使用两次cons产生的序对并不eq. 但在这里, 它们无法以任何方式进行区分.
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, 其中AD代表任意的类型.

很好.
(Pair
 (cdr
  (cons Atom 'olive))
 (car
  (cons 'oil Atom)))
的规范形式是什么呢?
52我猜是(Pair 'olive 'oil), 是这样吗?
实际上, 表达式
(Pair
 (cdr
  (cons Atom 'olive))
 (car
  (cons 'oil Atom)))
既不由某个类型刻画, 本身也不是一个类型, 因此提问其规范形式是毫无意义的.
注记. 不被类型刻画且自身不是类型的表达式也被称为是病态类型(ill-typed)的.
53为什么呢?
因为Pair在其参数为实际原子时并非类型.

只有在其参数均为类型 (例如Atom) 时, 它才是一个表达式.

54这是不是意味着Pair不能和carcdr一起使用呢?
不, 完全不是.
(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.

尽管如此, 拥有数字应该也是很自然的事情才对.

数字当然是很方便的. 除了AtomPair, 我们还可以判断某个东西是否是一个Nat.62让我们来试一下.
1是一个Nat吗?
注记. Nat自然数(natural number)的缩写.
63的确, 1是一个Nat.
1729是一个Nat吗?64是的, 1729是一个Nat. 它不仅是一个Nat, 还很著名!
注记. 谢谢Srinivasa Ramanujan (1887–1920) 和Godfrey Harold Hardy (1877–1947).
-1是一个Nat吗?65嗯, 你确定?
不, 当然不是. -23呢?66不是很清楚啊.
正数是Nat.67啊, 那么-23不是一个Nat?
我们更喜欢采取积极 (positive) 的态度.
译注. 这大概是一语双关吧.
最小的Nat是什么?
680不是一个自然数吗?
啊, 所以我们也不能总是很积极. 我们该如何获得剩下来的Nat呢?
注记. 然而, 数字1正的.
69我们可以使用我们的老朋友add1. 如果n是一个Nat, 那么(add1 n)也是一个Nat, 并且这个新的Nat永远是正数, 即便n0.

有多少个Nat呢?

很多很多!70存在最大的Nat吗?
并不, 因为我们总是可以...71...使用add1来加上一?
的确是这样!

026是相同的Nat吗?

注记. 谢谢Giuseppe Peano (1838-1932).
72显然不是.
(+ 0 26)26是相同的吗?
注记. 即便我们还没有解释+, 暂时请用你自己对于加法的理解.
73这个问题没有意义. 但是, 我们可以问它们是否是相同的Nat吗?
当然可以.

(+ 0 26)26是相同的Nat吗?

74是的, 这是因为(+ 0 26)的规范形式为26, 而2626当然是相同的.
zero的意思是什么呢?75zero0是相同的吗?
在Pie里, zero0不过是书写同一个Nat的不同方式而已.

one1是相同的Nat吗?

76嗯, 如果zero0是相同的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, 也是规范的.
注记. 第1章第81框中的顶add1只此一次用下划线标注出来以示强调.
83为什么7, 亦写作
(add1
 (add1
  (add1
   (add1
    (add1
     (add1
      (add1 zero)))))))
是规范的呢?
7是规范的完全是同理可得.84这意味着zero必然是规范的, 不然的话(add1 zero)就不是规范的了.
zero的顶是什么呢?85必须是zero.
之所以zero是规范的, 是因为顶zero是一个构造子, 并且其没有参数.
(add1
 (+ (add1 zero)
    (add1
     (add1 zero))))
是规范的吗?
86不是, 因为+不是构造子.
一个以构造子为顶的表达式被称为一个值(value).

即便

(add1
 (+ (add1 zero)
    (add1
     (add1 zero))))
不是规范的, 它的确是一个值.

注记. 值也被称为典则(canonical)表达式.
87这个表达式不是规范的, 是因为
(+ (add1 zero)
   (add1
    (add1 zero)))
并非书写3的最直接方式.

以构造子为顶的表达式被称为.

现在我们给出另一个并非规范的表达式.
(+ (add1
    (add1 zero))
   (add1 zero))
这是书写3的最直接方式吗?
88肯定不是.

准确来说, 构造子究竟是什么呢?

某些表达式是类型, 例如Nat(Pair Nat Atom).

对于新类型的解释的一部分是要说明其构造子为何. 构造子表达式是构造具有该类型的表达式的直接方式.

89构造子的例子有哪些呢?
Nat的构造子是zeroadd1, 而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中, 除了表达式和我们对于表达式的判断之外, 别无其他.
注记. 在Lisp中, 值和表达式是不同的, 而求值的结果是一个值.
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为顶的情况呢?
zero之诫

zerozero是相同的Nat.

如果每个add1的参数是相同的Nat, 那么两个add1表达式是相同的Nat值.

为什么

(add1 zero)
(add1 zero)
是相同的
Nat

101这两个表达式都是值. 这两个值都以add1为顶, 因此它们的参数应该是相同的Nat.

这两个参数都是zero, zero是一个值, 而且zerozero是相同的Nat值.

add1之诫

如果nk是相同的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)))))
为什么有虚线框呢?105four已经被定义了, 因而不能被再次定义.
定义是永恒的

一旦一个名字被claim了, 那么它就不能被重新claim. 一旦一个名字被define了, 那么它就不能被重新define.

不过当然了, 一开始four的确可以像那样定义.

实际上, 其他表达式都不能分辨出four的两种定义之间的不同, 因为这两个定义了four为相同的Nat.

106cons是一个构造子吗?
是的, cons构造Pair.107为了对于car表达式求值, 是否有必要对于car的参数求值?
的确. 为了找出一个car表达式的值, 我们从找出其参数的值开始.

关于这参数的值, 我们能说什么呢?

108这参数的值以cons为顶.
在找出参数的值之后, 接下来应该做什么呢?109整个表达式的值是cons的第一个参数.
(car
 (cons (+ 3 5) 'baguette))
的值是什么?
110cons的第一个参数是
(+ 3 5)
这并非一个值.
为了找出一个car表达式的值, 首先找出其参数的值, 这应该是(cons a d), 而
(car
 (cons a d))
的值然后就是a.

如何找出一个cdr表达式的值呢?

注记. 这里的a代表card代表cdr.
111就和car一样, 我们从对于cdr的参数求值开始, 直至其变为(cons a d), 然后
(cdr
 (cons a d))
的值就是d的值.

所有的构造子都有参数吗?

当然不是, 回忆一下, 第1章第86框里的zero是一个构造子.

两个表达式为相同的(Pair Atom Nat)是什么意思?

112这必然意味着每个表达式的值都以cons为顶, 并且它们的car是相同的Atomcdr是相同的Nat.
非常好.113原子是构造子吗?
原子'bay是一个构造子, 因而原子'leaf也是一个构造子.114所有原子都是构造子吗?
是的, 每个原子都构造其自身.

这是不是意味着每个原子都是值?

115的确, 因为解释为什么

Atom是一个类型

就是在说原子是Atom值.
嗯.

在表达式zero中, 顶层构造子是什么?

116那必然是zero, 因为zero是没有参数的构造子.
对于表达式'garlic而言, 什么是顶层的构造子?117原子'garlic是仅有的构造子, 所以它必然就是顶层的构造子.

那么, Nat是一个构造子吗?

不是, Nat并非一个构造子. zeroadd1是创造数据的构造子, 而Nat描述了特定的数据, 其要么就是zero, 要么以add1为顶, 且以另一个Nat为其参数.

Pair是一个构造子吗?

118不是, 因为Pair表达式是在描述以cons为顶的表达式. 构造子创建数据, 而不是类型.

那么, Pair应该叫做什么呢?

Pair是一个类型构造子(type constructor), 因其构造了一个类型. 类似地, NatAtom也是类型构造子.
(cons zero 'onion)
是一个
(Pair Atom Atom)
吗?
119不是.

难道它不应该是一个

(Pair Nat Atom)
吗?

的确如此! 但是
(cons 'zero 'onion)
是一个
(Pair Atom Atom)
请问
(cons 'basil
 (cons 'thyme 'oregano))
的类型是什么?
注记. 谢谢Julia Child (1912-2004).
120基于我们的所见所闻, 它必然是一个
(Pair Atom
 (Pair Atom Atom))
译注. basil, thyme, oregano分别是罗勒, 百里香, 牛至, 这是三种典型的香料. 另外, Julia Child是一位厨师和电视名人.
诚然如此.121好吧, 暂时就那么多了, 我的脑袋要炸了!
或许你应该再一次阅读这一章. 判断, 表达式, 类型是本书最重要的概念.122在完全读完这一章之后, 或许应该来点新鲜蔬菜.

第2章 从心所欲, 道法自然

ratatouille如何?1很好(très bien), 谢谢提问.
1章里有构造子和类型构造子, 分别构造值和类型.

然而, car既不是构造子也不是类型构造子.

2那么, car是什么呢?
car是一个消去子(eliminator). 消去子将构造子构造的值拆开来.

另一个消去子是什么呢?

3如果car是一个消去子, 那么显然cdr也是一个消去子.
构造子和消去子

构造子构造值, 而消去子将构造子构造的值拆开来.

另一种看待(消去子和构造子的)不同的方式在于值包含信息, 而消去子允许我们去利用这样的信息.4存在兼作构造子和消去子的东西吗?
不, 并不存在.

可以定义一个函数(function), 其表达力相当于carcdr的联合.

5怎么做呢?
这需要请出我们的老朋友λ.6这是什么? 我有点陌生.
Oops! 它也被称为lambda.
注记. λ可以选择写成lambda.
7好吧, λ可以构造函数.

这是不是意味着λ是一个构造子呢?

是的, 的确如此, 因为每个长得像(λ (x0 x ) body)的表达式都是一个值.

这种值的消去子是什么呢?

注记. 记号x 的意思是零个或更多的x, 因此x0 x 的意思是一个或更多的x.
8我们唯一能对函数做的事情就是将其应用于参数上.

这样的话函数怎么拥有消去子呢?

应用函数于参数函数的消去子.9好吧.
消去函数

应用函数于参数函数的消去子.

(λ (flavor)
  (cons flavor 'lentils))
的值是什么?
译注. lentils指的是小扁豆.
10它自λ起, 因而它已经是一个值了.
是的.
((λ (flavor)
   (cons flavor 'lentils))
 'garlic)
的值是什么呢?
11那必然是(cons 'garlic 'lentils), 如果λlambda的行为一致且cons是一个构造子的话.

但是这难道不意味着即便cons表达式已经是一个值了, cons的第一个参数仍然会被求值吗?

不, 并非如此, 但是这是个非常好的问题. 替换这个λ表达式的flavor行为之发生是因为该λ表达式被应用于了一个参数, 而不是因为cons.
注记. 一致地替换一个变量以一个表达式有时被称为替换(substitution).
这个λ表达式的体的每个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)

注记. (→ 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参数的名字是不同的. 尽管如此, 这通常无关紧要. 现在紧不紧要呢?
两个λ表达式也是相同的, 如果可以通过一致地对于参数换名使得它们的体变得相同.
注记. 一致地对于变量换名常被称为α变换(alpha-conversion). 感谢Alonzo Church (1903-1995).
一致地对于变量换名不会改变任何东西的意义.
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)
是中立的咯?
译注. 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, 而yy是相同的Nat.
的确如此, 并且类似地, 我们有
(λ (x) (car (cons x x)))
(λ (x) x)
是相同的
(→ Nat Nat)
28是的, 因为中立表达式xx是相同的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的.
译注. celery指的是西芹.
35为什么claim后面有写着
(Pair Atom Atom)
呢?
define之律和诫

遵照

(claim name X)(define name expr)

如果

expr是一个X

那么

name是一个X

并且

nameexpr是相同的X

(Pair Atom Atom)描述了我们可以怎样使用vegetables. 例如, 我们知道(car vegetables)是一个Atom, 而(cons 'onion vegetables)是一个
(Pair Atom (Pair Atom Atom))
注记. 这对于小扁豆汤 (lentil soup) 来说也是一个好的开始.
36啊, 懂了.
vegetables
(cons (car vegetables)
      (cdr vegetables))
是相同的
(Pair Atom Atom)
吗?
37的确如此, 因为每个表达式的值都是一个序对, 并且其car'celerycdr'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有一个消去子可以区分值为zeroNat和值以add1为顶的Nat. 这个消去子被称为which-Nat.44which-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 zerozero, 故整个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得到的.
译注. 也就是说, 要扒开一层add1.
51啊, 所以说规范形式为10是因为
((λ (n) (+ 6 n)) 4)
10.
请定义一个叫做gauss的函数, 使得(gauss n)是从zeron的所有Nat之和.

gauss的类型应该是什么?

注记. 根据民间传说, Carl Friedrich Gauss (1777-1855) 在他上小学被要求加起一个很长的数列时发现了0++n=n(n+1)2.
52将多个Nat加起来当然应该是一个Nat.
(claim gauss
  (→ Nat Nat))
是的. 现在请定义它.53怎么做呢?
第一步是挑选一个样例参数. 好的选择大概应该在510之间, 这样的话既足够大而有趣, 也足够小而可控.545怎么样, 然后呢?
听上去不错.
(gauss 5)
的规范形式应该是什么?
55应该是0+1+2+3+4+5, 即15.
译注. 当然, 更严格地说, 是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 zero)是什么?58显然是0.
现在让我们来定义gauss.

请记得白色的框和白纸黑字的部分.

59小菜一碟! 名字n-1暗示了其代表着一个脱去了n的一层add1Nat, 或者说比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-Natdefine还不足以让我们能够应对这个任务. 我们需要不同的消去子, 但是时机还远未成熟.68耐心是一种美德.
给诸如(Pair Nat Nat)这样的表达式定义更加简短的名字也是可能的.69这种情况下的claim是什么样的呢?
另一个好问题!

诸如Atom, Nat, (Pair Atom Nat)这样的表达式是类型, 而每一个这样的类型都是一个U.

注记. U读作you, 是universe(宇宙)的缩写, 以其描述了所有的类型 (除了自身).
70类型是值吗?
有的类型是值.

一个为类型的表达式是一个值, 当其以某个类型构造子为顶时. 到目前为止, 我们见过的类型构造子有Nat, Atom, , 和U.

71所有的类型都是值吗?
类型值

一个由某个类型描述的表达式是一个值, 当其以某个构造子为顶. 类似地, 一个为类型的表达式是一个(类型)值, 当其以某个类型构造子为顶.

并非如此.
(car (cons Atom 'prune))
是一个类型, 但不是一个值, 因为car既不是一个构造子, 也不是一个类型构造子.
72什么样的表达式由
(car (cons Atom 'prune))
描述呢?
译注. 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.76U是一个U吗?
不是, 但U的确是一个类型. 没有表达式可以成为其自身的类型.
注记. U可以是一个U1, 而U1可以是一个U2, 如此类推. 感谢Bertrand Russell (1872-1970). 感谢Jean-Yves Girard (1947-). 在这本书里, 单一的U就够用了, 因为我们不需要U被某个类型刻画.
77每个为U的表达式是否也都是一个类型呢?
是的, 如果X是一个U, 那么X是一个类型.78是否每个类型都由U刻画呢?
每个U都是一个类型

每个由U刻画的表达式都是一个类型, 但是不是每个类型都由U刻画.

每个由U刻画的表达式都是一个类型, 但是不是每个为类型的表达式都由U刻画.79
(cons Atom Nat)
是一个
(Pair U U)
吗?
的确如此.

定义Pear, 其意指Nat的序对的类型.

译注. pear和pair同音.
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
恰被定义为这个类型.
这是很好的论证.83Pear是一个值吗?
不是. 由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)
的表达式. +的类型是什么?
注记. 毕竟X可以是任意的类型.
91其取两个Nat而产生一个Nat, 故类型必然是
(→ Nat Nat Nat)
很好.

ad都为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-Pearclaim而不使用PearPear-maker呢?
93可以, 只需要将Pear-makerPear替换为相应的定义即可.
(claim elim-Pear
  (→ (Pair Nat Nat)
     (→ Nat Nat
        (Pair Nat Nat))
     (Pair Nat Nat)))
名字Pear-makerPear从不是必要的. 名字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加法的类型是什么?

译注. 译者认为pearwise并非贴切, 因为是逐Pear的部分相加, 而不是逐Pear相加, 可能partwise是更好的选择.
98
(→ Pear Pear Pear)
对吗?
怎么用elim-Pear定义pearwise加法?99好难啊, 是不是两个Pear都要消去, 因为这些Nat都是结果的一部分?
当然了.

请定义pearwise+, 以使得

(pearwise+
 (cons 3 8)
 (cons 7 6))
(cons 10 14)
是相同的Pear.

100首先, 将anjoubosc各自拆成其相应的部分, 然后再将它们的第一部分和第二部分分别加起来.
(claim pearwise+
  (→ Pear Pear Pear))
(define pearwise+
  (λ (anjou bosc)
    (elim-Pear
     anjou
     (λ (a1 d1)
       (elim-Pear
        bosc
        (λ (a2 d2)
          (cons
           (+ a1 a2)
           (+ d1 d2))))))))
译注. anjou和bosc都是梨的品种.
或许现在应该好好休息一下, 然后回来重读本章.101是的, 的确是个好想法.

但是, 我们该如何到达第3章呢?

通过到达第3章.102幸亏递归不是一个选项.

课间: 一叉子Pie

是时候玩Pie了呢.1玩弄食物是不是有点不礼貌?
尽管派 (pie) 的确是一种美味的食物, 但是Pie是一种语言, 玩玩而已无伤大雅.2让我们开始吧.
使用Pie很像是对话: 其接受声明, 定义和表达式, 并回之以反馈.3什么样的反馈呢?
对于声明和定义而言, 反馈是判断它们是否具备意义. 对于表达式而言, 反馈是表达式的类型和规范形式.4不具备意义会怎么样呢?
Pie会解释它们出了什么问题, 有时添加有用的提示.5表达式可能会出什么问题呢?
在吃Pie前请吃蔬菜.

尝试输入

'spinach
看看会发生什么.

译注. spinach, 菠菜.
6Pie回之以
(the Atom 'spinach)
这里的the是什么意思?
其意即'spinach是一个Atom.

在Pie里, 一个表达式要么是一个类型, 要么由一个类型描述. Pie可以自己找出诸多表达式的类型, 包括原子.

7
(car 'spinach)
怎么样呢?
这个表达式并不由某个类型刻画, 因为'spinach不是一个序对.8Pie总能判断描述了表达式的类型吗?
不能, 有时Pie需要帮助.

此时, 使用the表达式来告诉Pie意图的类型.

注记. the表达式也被称为类型注解.
9例如?
Pie不能确定单独的cons表达式的类型.10为什么呢?
(cons 'spinach 'cauliflower)
是一个
(Pair Atom Atom)
这难道不是显然的吗?
译注. cauliflower, 花椰菜.
这对于我们来说是显然的, 但是之后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不能确定一个表达式的类型时就使用claimdefine呢?
原则上是可以的, 但总是把名字都写出来令人厌倦.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为顶的表达式都是值吗?

译注. kale, 羽衣甘蓝.
并非如此.
(the X e)
的值是e的值.
18那么
(car
  (the (Pair Atom Nat)
    (cons 'brussels-sprout 4)))
的值是什么?
译注. brussels sprout, 抱子甘蓝.
the之诫

如果X是一个类型而e是一个X, 那么

(the X e)
e是相同的X.

值是很小一点的'brussels-sprout.

现在试试这个:

U

19Pie说:
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)的规范形式是什么?

4zero.
(gauss 1)的值是什么?51, 因为
  1. | (gauss (add1 zero))相同于
  2. | (+ 1 (gauss zero))相同于
  3. | (add1 (gauss zero))
注记. 当表达式垂直对齐而左边又有竖杠时, 我们将假定除了最后一行之外的每一行后面都跟着相同于 (is the same as). 这种图表被称为相同于 (same as)图表.
译注. 原书的竖杠是连贯的, 虽然也可以实现这样的效果, 但是译者太懒了.
这就是值吗?6不是还有更多要做的吗?
  1. | (add1 (gauss zero))
  2. | (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-NatNat的一个消去子. 但是, 以add1为顶的Nat底下还藏着一个更小的Nat, 而which-Nat并不会消去这更小的Nat.16存在消去更小Nat的方法吗?
一种消去更小Nat的方法是使用iter-Nat.17什么是iter-Nat?
一个iter-Nat表达式长得像这样:
(iter-Nat target
  base
  step)
which-Nat一样, 当targetzero时, 整个iter-Nat表达式的值即base的值.
18iter-Natwhich-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)))
的规范形式是什么?
208, 因为add1相继应用于3五次是8:
(add1
  (add1
    (add1
      (add1
        (add1 3)))))
整个iter-Nat表达式的类型和base的类型相同吗?21必然如此, 因为当targetzero时, 整个iter-Nat表达式的值是base的值.
让我们使用X作为base的类型的一个名字.

step的类型如何?

译注. 也就是说, X是一个元变量.
22step被应用于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)))

23target是
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, 鉴于以下相同于图表:
  1. | (+ zero j)
  2. | 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)是什么?

译注. 右侧排版没有按照原文, 见谅.
288, 因为
  1. | (+ (add1 zero) 7)
  2. |
    (iter-Nat (add1 zero)
      7
      step-+)
  3. |
    (step-+
      (iter-Nat zero
        7
        step-+))
  4. |
    (add1
      (iter-Nat zero
        7
        step-+))
  5. | (add1 7)
8.
iter-Nat可以用来定义gauss吗?29iter-Nat展现了一种反复消去add1下面藏着的更小的Nat的方式.

消去更小的Nat... 这听起来的确像是gauss所遵照的方法.

是挺接近的, 但是step没有足够的信息. gauss需要一个结合了which-Natiter-Nat的表达力的消去子. 这个消去子叫做rec-Nat.30rec-Nat是什么?
译注. 根据The Little Typer官网的勘误表, 其实rec-Nat是可以用iter-Nat表达的.
rec-Nat的step会被应用到两个参数上: 一个是add1下面藏着的那个更小的Nat, 另一个是这更小的Nat上的递归答案. 这是第2章第59框里的gauss定义所使用的方法.

此即所谓rec-Nat模式.

注记. rec-Nat模式也被称为原始递归(primitive recursion). 感谢Rózsa Péter (1905-1977), Wilhelm Ackermann (1896-1962), Gabriel Sudan (1899-1977), 以及David Hilbert (1862-1943).
31如何使用rec-Nat定义gauss呢?
这个框里有两种gauss的定义: 一个是来源于第2章第59框虚框版本, 另一个则是使用rec-Nat的版本.
(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三个区别:
  1. which-Nat代之以rec-Nat,
  2. 内层的λ表达式多了一个变量gaussn-1, 以及
  3. 递归(gauss n-1)代之以几乎是答案的gaussn-1.
译注. 原文中rec-Nat版本的gauss也被加上了虚框, 并不是因为这个定义是错的, 而是因为之后出现的利用辅助过程step-gauss的定义才是正式的.
名字n-1gaussn-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的非zeroNat一起使用时, 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时是一个表达式.39base和step的类型应该是什么呢?
base必然具有某个类型, 让我们再次称其为X. X可以是任意的类型, 但是整个rec-Nat表达式必然和base有着相同的类型, 即X.40这就是所有要说的了吗?
还没有说完.

如果base是一个X, 那么step必然是一个

(→ Nat X X)
为什么这是step的合适类型呢?

41step会被应用于两个参数: 第一个参数是一个Nat, 因为其为target里藏在add1下的那个东西; 第二个参数是almost, 其为X是因为其也由rec-Nat产生.
那么, 这是如何与which-Natiter-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)))
注记. 我们将't'nil当成两个任意的值使用. 这或许对于Lisper而言是熟悉的 (感谢John McCarthy (1927-2011)), 但是zerop在Scheme中被称为zero? (感谢Gerald J. Sussman (1947-) 和Guy L. Steele Jr. (1954-)).
43为什么要使用递归性的rec-Nat来定义实际上只需要判断顶层构造子是zero还是add1的过程呢? 毕竟, which-Nat就已经够用了.
which-Nat很容易解释, 但是rec-Nat可以做到任何which-Nat (和iter-Nat) 可以完成的事情.

为什么step-zerop中的λ变量被称为n-1zeropn-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让我们看看.
  1. | (zerop (add1 36))
  2. |
    (rec-Nat (add1 36)
      't
      step-zerop)
  3. |
    (step-zerop 36
      (rec-Nat 36
        't
        step-zerop))
  4. | 'nil
值是立刻确定的. 对于36 (也就是(add1 35)) 而言的zerop值并不需要, 故我们没有找到它的理由.
在表达式的值实际变得必要之前, 我们都无需对于表达式求值. 否则的话, 对于step-zerop的参数
(rec-Nat 36
  't
  step-zerop)
求值就太花工夫了, 以至于若记录在相同于图表中则需另费至少105行.
47有时懒惰 (laziness) 是一种美德.
(zerop 37)(zerop 23)相同吗?48是的, 的确.
  1. | 'nil
  2. |
    (step-zerop 22
      (rec-Nat 22
        't
        step-zerop))
  3. |
    (rec-Nat (add1 22)
      't
      step-zerop)
  4. | (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-1gaussn-1这样的step中的λ变量几乎(almost)就是答案了, 其意见于第2章第56框.51好.

gauss的正式定义是什么?

以下就是了.
(define gauss
  (λ (n)
    (rec-Nat n
      0
      step-gauss)))
base是什么?
52base即rec-Nat的第二个参数. 这个情况下, 是0, 一个Nat.
step是什么?53step-gauss.
的确如此.54根据这个定义, (gauss zero)是什么?
0, 因为
(rec-Nat zero
  0
  step-gauss)
rec-Nat的第二个参数相同, 即0.

以下是我们找出(gauss (add1 zero))的值的开始.

  1. | (gauss (add1 zero))
  2. |
    (step-gauss zero
      (rec-Nat zero
        0
        step-gauss))
  3. |
    (+ (add1 zero)
      (rec-Nat zero
        0
        step-gauss))
现在请继续下去以找到值.

55出发.
  1. |
    (iter-Nat (add1 zero)
      (rec-Nat zero
        0
        step-gauss)
      step-+)
  2. |
    (step-+
      (iter-Nat zero
        (rec-Nat zero
          0
          step-gauss)
        step-+))
  3. |
    (add1
      (iter-Nat zero
        (rec-Nat zero
          0
          step-gauss)
        step-+))
其已是值, 鉴于其以add1为顶.
这个值是规范的吗?56不是, 但是以下图表找出了其规范形式.
  1. |
    (add1
      (rec-Nat zero
        0
        step-gauss))
  2. | (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) 可以不是完全的, 因为这里所有的函数都是完全的, 而每一步骤不过就是应用一个函数.
译注. 这段话中的step有点一语双关的意味.
60所以说, 为什么我们不能将这种推理风格应用于任意的递归函数呢?
这种推理风格无法以我们的工具表达. 但是, 一旦我们相信step完全的rec-Nat是一种消去任意作为target的Nat的方法, 那么每个新定义都是完全的了.
注记. 大致上说, 我们没法做, 但是即便我们能做, 那也会很累人的.
61还有什么使用rec-Nat的更有趣的例子吗?
其可以用来定义*, 意即乘法.

换言之, 如果njNat, 那么

(* n j)
应该是nj之积.

注记. *读作乘 (times).
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.

注记. 感谢Haskell B. Curry (1900-1982) 和Moses Ilyich Schönfinkel (1889–1942).
70以下是*的正式定义.
(define *
  (λ (n j)
    (rec-Nat n
      0
      (step-* j))))
尽管step-*看上去像一个三参数的λ表达式, 它可以只接受一个参数. rec-Nat期望其step是一个恰接受 (get) 两个参数的函数.
以下是计算(* 2 29)的规范形式的图表的前五行.
  1. | (* 2 29)
  2. |
    ((λ (n j)
       (rec-Nat n
         0
         (step-* j)))
      2 29)
  3. |
    (rec-Nat (add1
               (add1 zero))
      0
      (step-* 29))
  4. |
    ((step-* 29)
     (add1 zero)
     (rec-Nat (add1 zero)
       0
       (step-* 29)))
  5. |
    ((λ (n-1 *n-1)
       (+ 29 *n-1))
     (add1 zero)
     (rec-Nat (add1 zero)
       0
       (step-* 29)))
现在, 继续找出规范形式.
71啊, Currying也有参与.
  1. |
    (+ 29
       (rec-Nat (add1 zero)
         0
         (step-* 29)))
  2. |
    (+ 29
       ((step-* 29)
        zero
        (rec-Nat zero
          0
          (step-* 29))))
  3. |
    (+ 29
       (+ 29
          (rec-Nat zero
            0
            (step-* 29))))
  4. |
    (+ 29
       (+ 29 0))
  5. | 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.

注记. 实际上, 第3章第73框中的定义本意在是factorial. 然而, 疏忽导致了错误一直存在于诸多草稿版本之中却未被发现 (至于有多少这样的版本, 作者不想说了). 我们将纠错的任务留给读者.
74那么, 更为强大的类型可以阻止我们像第2章第39框里那样将five定义成是9吗?
译注. 原文为第2章第36框, 应该是笔误.
当然不行了.

类型并不能阻止将five定义成是9的愚蠢行径. 但是, 我们可以将我们的一些想法写成类型.

75非常有趣.

第4章 小菜一碟, 简单如Pie

第2章第80框里, 我们定义Pear
(claim Pear U)
(define Pear
  (Pair Nat Nat))
Pear的消去子是由carcdr定义的.
1并且...
Pear的一个消去子必须要做什么呢?2这个消去子必须要暴露 (或者说解包) 一个Pear中的信息.
Pair的消去子呢? 它必须要做什么?3Pair的一个消去子必然要暴露一个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)的消去子, 不论AD为何.

6不论为何? 即便A'apple-pie吗?
好吧, 当然不是绝对任意的.

根据第1章第54框, 除非AD类型. 也就是说, 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. 前两个参数刻画了要被消去的Paircar部分和cdr部分的类型. 这第三个参数Nat描述了内层的λ表达式的结果类型.
注记. 因此, 内层的λ表达式的参数ad的类型也都是Nat.
9内层的λ表达式的意图是什么?
内层的λ表达式描述了如何使用p的值中的信息. 所谓的信息即pcarcdr部分.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)carcdr.

12以下是swap的类型.
(claim swap
  (→ (Pair Nat Atom)
    (Pair Atom Nat)))
现在定义swap.13以下是swap的定义. 就和karkdr一样, 其也被包裹在虚框里.
(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根据pcarcdr来确定整个表达式的值. 这个值必然具有类型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是什么呢?15A, D, Xelim-Pair的前三个参数?
它们会引用之前已经定义过的类型吗?16不会, 因为它们指的是不论什么样的参数.
一个表达式中的名字要么引用一个定义, 要么指的是由一个λ所命名的一个参数. 显然这个表达式里没有λ, 并且A, D, X也都没有被定义.17这必然意味着第4章第14框中的那个表达式实际上并非一个类型.
的确如此.

然而, 这种思维过程的确言之成理. 回忆一下, 成为一个

(→ Y X)
是什么意思.

18一个
(→ Y X)
是一个λ表达式, 当接受一个Y时, 产生一个X. 它也可以是一个值为这样的λ表达式的表达式. 我说的对不对呢?
YX都是类型吗?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听上去没法完成任务.
的确不行, 但是Π行.
注记. Π读作pie, 并且也可以写成Pi.
23Π是什么意思呢?
以下是一个例子.
(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被定义为是一个λ表达式, 而其被应用于了两个参数, NatAtom.
((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)))
在这个Π表达式里, 参数名是AD. Π表达式可以有一个或更多的参数名, 而这些参数名可以出现在Π表达式的体中.
30Π表达式的是什么?
(Π ((A U)
    (D U))
  (→ (Pair A D)
    (Pair D A)))
在这个Π表达式里, 体是
(→ (Pair A D)
  (Pair D A))
这个是(flip所代表的)λ表达式的体的类型, 这个体由Π表达式的体所描述.
译注. 这句话读起来稍显冗余.
31Π表达式的体里的AD指的是什么?
应用之中律

如果f是一个

(Π ((Y U)) X)
Z是一个U, 那么
(f Z)
是一个X, 其中每个Y都已被一致地替换为了Z.

译注. 原文的Y实际上使用的是无衬线字体, 即Y. 但是, 译者认为Y应该是一个代表句法上的变量的元变量.
体里的AD指的是尚不可知的特定类型. 不论哪两个类型AD作为由Π表达式所描述的λ表达式的参数, 应用这λ表达式的结果总是一个
(→ (Pair A D)
  (Pair D A))
译注. 请读者注意字体, AA相当不同.
32是不是这意味着
(flip Atom (Pair Nat Nat))
的类型为
(→ (Pair Atom
     (Pair Nat Nat))
  (Pair (Pair Nat Nat)
    Atom))
呢?
对的.

但为什么会是如此呢?

33变量AD被替换以其相应的参数: 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框所言, 一致地对于变量换名不会改变任何东西的意义.
译注. meringue, 蛋白酥.
(Π ((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.

译注. 原文的后两个AD本是黯淡的, 但是译者认为既然它们指的是Π表达式中的相应变量, 所以说它们应该是正常颜色更好.
第4章第36框中提出的flip定义是可以允许的. 然而, 就像定义five为意指9一样, 这是愚蠢的.37为什么可以允许这样的定义呢?
外层的λ中的名字不需要匹配Π表达式中的名字. 外层的λ表达式中的CΠ表达式中的A相对应, 因为它们都是第一个名字. 外层的λ表达式中的AΠ表达式中的D相对应, 因为它们都是第二个名字. 重要的是参数命名的顺序.
注记. 尽管使用不相匹配的名字并非错误, 但这的确相当令人困惑. 我们总是使用匹配的名字.
内层的λ表达式中的p与什么相对应?
38p相对应的是后跟着的(Pair A D), 其给出了内层λ表达式的参数类型.
如何对于第4章第36框中的定义里的CA进行一致换名以改善这个定义?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))))
42kdr也是.
(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它应该意味着一个λ表达式 (或者能求值至这样的λ表达式), 当其被应用于两个类型TS时, 将会产生一个表达式, 其类型可由一致地将X中的每个Y替换以T然后再将这新的X中的每个Z替换以S获得.
译注. 这里假定YZ相异, 至于不相异的情况该如何解释, 实际上多个参数的Π表达式可以理解为单参数Π表达式的嵌套.
Π表达式可以拥有任意数目的参数, 而其所描述的λ表达式有着相同数目的参数.

什么表达式具有类型

(Π ((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)
的值是什么?
注记.第2章第19框起就熟悉了.
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))
NatAtom没什么特别之处. 因此, 与其对于每个类型写下一个新的定义, 我们不如使用Π构建一个一般目的性的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)的类型之间有什么联系?53twin-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)是一个类型.
注记. 读作类型为E的元素的列表或者更简单的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...

注记. 出于历史原因, ::亦读作cons或者list-cons.
14..., 但是构造子cons构建一个Pair.
可以有序对的列表, 也可以有列表的序对.

何时(:: e es)是一个(List E)?

注记. e的复数形式是es, 读作ease. 使用es是因为一个列表的剩余部分可以具有任意数目的元素.
15嗯, es必须要是一个(List E). es可以是nil, 而nil是一个(List E).
e可以是任意的东西吗?16当然可以!
当然不行! 再试试.17我猜一下: e必须要是一个E, 不然的话E还没有任何用处.
正确的答案, 错误的原因.

之所以e必须要是一个E, 是因为为了能够使用(List E)的一个消去子, 我们需要保证这样的列表的每个元素都是一个E.

请将rugbrød定义为丹麦黑麦面包的成分.

注记. 读作[ˈʁuˌb̥ʁœðˀ], 如果还是不懂, 就去问问丹麦人.
译注. rugbrød, 丹麦语, 意思是黑麦面包.
18那么成分有什么呢?
rugbrød里的成分有:
  • 全黑麦面粉,
  • 黑麦籽粒, 浸泡直至变软,
  • 纯净水,
  • 活跃的酵种, 以及
  • 盐.
19rugbrø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ød5有什么不同之处?

22似乎看起来它们就没有相同的地方. 5add1zero构成. 而且, 5也不好吃.
rugbrød里包含多少种成分呢?23五种.
不仅只需要五种成分, rugbrød甚至不需要揉面.24那么, ::是不是和add1有什么关系?
::使得一个列表更大, 而add1使得一个Nat更大.

nil是不是和zero有什么关系?

25nil是最小的列表, 而zero是最小的自然数.

列表的消去子和Nat的消去子是不是看起来差不多?

nil之律

nil是一个(List E), 不论类型E为何.

::之律

如果e是一个Ees是一个(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有什么不同之处?

27rec-Liststeprec-Natstep多一个参数——其接受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是带有toppingscondimentsrugbrød.

(claim toppings
  (List Atom))
(define toppings
  (:: 'potato
    (:: 'butter nil)))
(claim condiments
  (List Atom))
(define condiments
  (:: 'chives
    (:: 'mayonnaise nil)))

注记. butter和mayonnaise也可以换成你喜欢的非乳制品替代物.
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让我们看看.
  1. |
    (rec-List (:: 'chives
                (:: 'mayonnaise
                  nil))
      0
      step-     )
  2. |
    (step-     
      'chives
      (:: 'mayonnaise nil)
      (rec-List (:: 'mayonnaise
                  nil)
        0
        step-     ))
  3. |
    (add1
      (rec-List (:: 'mayonnaise
                  nil)
        0
        step-     ))
规范形式是什么? 不要对于省略中间的表达式保有负担.33规范形式为
(add1
  (add1 zero))
或者更为人知的版本是2.
这个rec-List表达式将condiments中的每个::都替换为了add1, 而nil则被替换为了0.

方框里填什么名字好呢?

34length似乎比较恰当.
(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)))
列表可以包含任意类型的元素, 不仅是AtomNat.

什么可以用来作成一个对于所有类型成立的step-length版本呢?

37It'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.

39E传递给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.
译注. (append E nil end)中的Eend是元变量.
51step呢?
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)))))
注记. 当列表包含的元素类型为E时, 表达式(step-append E)应该是append的一个step. 请注意Currying.
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)))
译注. 这里的第一段我不是很理解, 因为你并不会在运行append过程中把参数end拆开.
这是很好的推理.

所以正确的定义是什么?

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的step不能知道参数e会是什么.
没有什么能够阻止我们定义iter-List, 但是又没有必要, 因为iter-List能做的, rec-List也能做, 就像iter-Natwhich-Nat能做的, rec-Nat也能做.56好的, 这里我们就用更富表达力的消去子吧.
实际上还可以用另外的方式定义append, 其将::替换成别的什么东西.57真的能行吗?
当然可以. 除了使用::以将第一个列表的元素cons到结果的开头, 也可以使用snoc将第二个列表的元素加到结果的末尾.

例如,

(snoc Atom toppings 'rye-bread)
的值为
(:: 'potato
  (:: 'butter
    (:: 'rye-bread nil)))

snoc的类型是什么?

注记. 感谢David C. Dickson (1947-).
58snoc的类型是
(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)))))
译注. 说白了, 其实就是复用append.
干得好.

现在请定义concat, 其行为与append类似, 但是在其step中使用了snoc.

(claim concat
  (Π ((E U))
    (→ (List E) (List E)
      (List E))))
concat的类型和append的类型相同, 因为它们做相同的事情.

61除了使用snoc而不是Listcons::, 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)))))
译注. 不幸的是, concat的定义是错误的, 它实际上相当于第一个列表和反转了的第二个列表合并. 没有什么特别好的补救措施, 因为snocconcat本身对于List就不那么自然. 尽管如此, 你的确可以使用之后的reverse来反转end以得到一个更加别扭但是还算正确的定义.
一个列表也可以通过使用snoc得以反转.

reverse的类型应该是什么?

62reverse接受单独一个列表作为参数.
(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-reversereverse.64以下就是了.
(define step-reverse
  (λ (E)
    (λ (e es reversees)
      (snoc E reversees e))))
(define reverse
  (λ (E)
    (λ (es)
      (rec-List es
        nil
        (step-reverse E)))))
注记. 在使用Pie语言时, 必须要将这里的nil写成(the (List E) nil)才行.
现在是时候来点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))))))
译注. 原文将condimentstoppings的顺序弄反了, 翻译是按照经过勘误的版本来的.
我们问的是规范形式, 而非值, 这是很好的. 否则的话, 你原本可能就要在吃的时候自己组转除了'chives的一切了.66反转列表是令人发饿的工作.

第6章 究竟到底是多少?

...1在吃了那么多三明治之后, 吃点Π也是好的.
我们很高兴你问了...2我很擅长预测你想要我提出的问题.
当然了, 让我们开始吧.

让我们定义一个函数first, 其找出任意List的第一个元素.

3那不是很简单吗?
实际上, 这是不可能哒!4为什么不可能?
之所以不可能, 是因为nil没有第一个元素...5...因而first不是完全的.
写一个last函数怎么样, 它不是找出第一个元素, 而是找出一个List的最后一个元素?6函数last也不是完全的, 因为nil也没有最后一个元素.
为了能够写下一个完全函数first, 我们必须使用一个比List更加特化 (specific) 的类型. 这样一个更加特化的类型被称为Vec, 其是向量 (vector)的缩写, 但是它真的只是带有长度的列表而已.

E是一个类型而k是一个Nat时, 表达式(Vec E k)是一个类型. 这个Nat给出了列表的长度.

(Vec Atom 3)是一个类型吗?

注记. (Vec E k)读作长度为kE的列表或者更简单的E的列表, 长度为k.
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是一个Ees是一个(Vec E k)时, (vec:: e es)是一个(Vec E (add1 k)).

12如果一个表达式是一个(Vec E (add1 k))那么其值至少拥有一个元素, 因而有可能定义firstlast, 是不是这样呢?
对的.
(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是一个Ees是一个(Vec E k), 那么(vec:: e es)是一个(Vec E (add1 k)).

(vec:: 'crimini
  (vec:: 'shiitake vecnil))
是一个
(Vec Atom 3)
吗?
14不是, 因为其并非恰有三个原子的列表.
这和第6章第12框是怎样联系起来的呢?
译注. 原文是第11框, 但是译者认为第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)
1zero不是相同的Nat.
为什么1zero不是相同的Nat呢?18根据第1章第100框的解释, 两个Nat相同, 当其值相同; 而其值相同, 当其均为zero或者均以add1为顶(且add1的参数是相同的Nat).
译注. 括号里的内容是译者添加的. 另外, 不仅是第100框, 也有第101框的内容.
现在可以定义first-of-one了, 其获取一个(Vec E 1)的第一个元素.19但是这可能吗? 到目前为止, 我们还没有Vec的消去子.
很好的论点. Vec的两个消去子是headtail.20headtail是什么意思呢?
es
是一个
(Vec E (add1 k))
时,
(head es)
是一个
E
es的值可以具有怎样的形式?
21它不可能是vecnil, 因为vecnil只有zero个元素. 因此, esvec::为顶.
表达式
(head
  (vec:: a d))
a是相同的E.
22tail怎么样呢?
es
是一个
(Vec E (add1 k))
时,
(tail es)
是一个
(Vec E k)
23esvec::为顶.
(tail
  (vec:: a d))
d
是相同的
E
吗?
不是, 但
(tail
  (vec:: a d))
d
是相同的
(Vec E k)
现在定义first-of-one.
24first-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...Π?
Π比我们所见更加灵活.32什么是更加灵活的Π呢?
一人食的蘑菇派. (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是一个类型.

译注. 虽然没有明说, 但是X中的y是被绑定的.
完全正确. (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)
的简略写法, 当yX中没有被用到时.

λ之终律

若当y是一个Yx是一个X, 那么

(λ (y) x)
是一个
(Π ((y Y)) X)

译注. 虽然没有显式写出, 但是这里的x依赖于y, X也依赖于y. 当然, 实际上这两个y不需要使用相同的名字 (但指的是同一个东西). 多说一句, 虽然这里是λ之终律, 但是本书之中并无λ之始律, 这可能是作者的失误. 不过, 既然律陈述的是定型规则, λ之始律应该说的是还没有Π的情况下λ表达式的类型.
应用之终律

如果f是一个

(Π ((y Y)) X)
z是一个Y, 那么
(f z)
是一个X, 其中每个y都已被一致地替换为了z.

译注. 应用之始律里没有Π, 应用之中律里Π的参数的类型都是U, 终律放宽了这个限制.
λ终第一诫

如果两个λ表达式可以通过一致换名使其成为相同的

(Π ((y Y)) X)
那么它们就是相同的.

λ终第二诫

如果f是一个

(Π ((y Y)) X)
y不出现在f中, 那么f
(λ (y) (f y))
是相同的.

译注. 这里的出现应该指的是自由出现.
类型
(Π ((es (Vec E (add1 l))))
  E)
本可以写成
(→ (Vec E (add1 l)) E)
因为esE中没有被用到.

实际上, 我们本也可以将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))))

headtail都是函数, 而所有函数都是完全的. 这意味着它们不可能与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就不能使用了.

6iter-Nat如何呢?
rec-Nat可以做任何iter-Nat可以做的事情.7有什么更强大的东西可用吗?
那被称为ind-Nat, 这是induction on Nat的缩写.8什么是ind-Nat?
ind-Natrec-Nat很像, 除了其允许base和step中几乎是答案的参数 (这里是peasl-1) 的类型包括作为target的Nat.

换言之, ind-Nat用于依赖类型.

9这里有一个被称为how-many-peasNat包含在类型
(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的结果.
注记. 感谢Conor McBride (1973-).
11所以说动机是一个函数, 其体是一个U.
的确如此. 动机解释了为什么target要被消去.

peas的动机是什么?

12这是个好问题.

不过, 至少其类型是清晰的.

(claim mot-peas
  (→ Nat U))

注记. mot读作moat.
对于依赖类型应使用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的目的是什么?

16rec-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-peasstep-peas的类型里出现了两次?
21
22

课间: 一次吃一块

1
2