王垠在博文《GTF - Great Teacher Friedman》里提到,他在康奈尔大学的一个同学花了一个星期琢磨出前驱函数的丘奇编码表示,这个表示跟克林尼最初的设计是完全不同的。
维基百科的丘奇编码词条中介绍了这两种方法。
因λ演算是“无状态”的,构造前驱函数的确不是很简单。
其中一种方法(可能是克林尼最初构造的那个)是先构造有序对,通过有序对保存两个相邻的数,从而实现前驱函数。这种方法是较容易理解的,也是许多讲丘奇编码的书会讲的方法。
而王垠的这位同学想到的方法则比较难懂。大体的思路是,将丘奇数封装在一个特殊构造Value中,实现相对于丘奇数的移位,然后重新构造类型为(Value→Value)的后继函数。通过公理"某数的后继为自然数0对应的Value",构造出"-1"的Value表示,从而成功构造出前驱函数。
下文把前驱函数统一记作PRED
,其接受一个丘奇数作为参数,返回参数的前驱丘奇数。PRED
的大体框架是
(define PRED
(lambda (number)
(lambda (f x)
...)))
注意,(PRED ZERO)
的值仍然是丘奇数0ZERO
。
通过有序对构造前驱函数的方法不再赘述,这里具体讲(复述)一下维基百科中给出的构造思路,也就是王垠所说的他同学给出的方法。
首先回顾一下丘奇数的构造。丘奇数的构造思路是:用函数嵌套层数来表达自然数。按照这种思路,丘奇数接受两个参数:其一是供反复嵌套调用的函数f
,其二是传递给这个函数的参数x
。
(define ZERO
(lambda (f x) x))
(define ONE
(lambda (f x) (f x)))
(define TWO
(lambda (f x) (f (f x))))
后继函数SUCC
很容易构造出来,也就是在原有丘奇数的基础上,把f
多嵌套一层,然后返回新的丘奇数即可。
(define SUCC
(lambda (number)
(lambda (f x)
(f (number f x)))))
多嵌套一层很容易,但是去掉一层嵌套比较麻烦。
构造PRED
的关键在于“重新定义”丘奇数和后继函数。通过封装的手段,可以构造一个丘奇数的容器,记作VALUE
。它接受一个丘奇数(的返回值,即嵌套调用部分(f..(f x)..)
,为简洁起见,下同),返回一个闭包,这个闭包将传入的丘奇数保存在参数的位置,其定义为
(define VALUE
(lambda (number)
(lambda (package)
(package number))))
很容易看出,
((VALUE n) f) = (f n) (性质(*))
利用这个性质,可以构造辅助函数EXTRACT
,用来从封装的VALUE中得到number
:
(define EXTRACT
(lambda (value)
(value (lambda (x) x))))
上面这个性质与后继函数的行为非常接近。基于此性质,可以构造一个新的后继函数INC
,与丘奇数后继函数不同的是,INC
以封装的VALUE为输入,以后继的VALUE为输出。
;; VALUE -> VALUE
(define INC
(lambda (value)
(lambda (package)
(package (value f)))))
因(value f)
得到的是原有丘奇数的后继数(也是丘奇数),所以INC
需要将它再次封装为VALUE。需要注意的是,INC
引入了自由变量f
,而这个f
已经在PRED
的框架中出现了(即丘奇数所需的那个f
),因此实际上是约束的。
下面的步骤验证了INC
的递推性质,并且构造了“-1”所对应的VALUE,将其设为迭代起点。
记V0 = (VALUE x)
,则根据性质(*)和INC
定义:
V0 = (VALUE x)
(INC V0) = (VALUE (f x)) = V1
(INC (INC V0)) = (VALUE (f (f x))) = V2
...
(INC Vn) = V[n+1]
如果将INC
在V0
上反复调用n
次(n
是丘奇数),则
(n INC V0) = Vn
这里Vn
可理解为自然数n所对应的VALUE。为了构造前驱函数,只需要再构造出一个“-1”,即V[-1]
,从V[-1]
而不是V0
开始这个迭代过程,就可以实现前驱函数。
显然,V[-1]
应具有性质
(INC V[-1]) = (VALUE x) = V0
展开得到
(lambda (package) (package (V[-1] f))) = (lambda (package) (package x))
由此可得(合一),
(V[-1] f) = x
应用η-变换可得
V[-1] = (lambda (u) x)
观察V[-1]
和V0
可以发现,V[-1]
实际上对应的是丘奇数的0,而V0
对应的是丘奇数的1。也就是说,**通过VALUE
的封装,实现了将丘奇数“左移”一位的目的,使之从丘奇数的“-1”开始迭代,并以丘奇数返回结果,从而实现前驱的效果。**即:
(n INC V[-1]) = V[n-1]
使用EXTRACT
将得到的VALUE对应的丘奇数嵌套形式抽取出来,就得到了PRED
函数的函数体。需要说明的是,由于(EXTRACT V[-1]) = x
,因此(PRED ZERO) = ZERO
是满足的。
最后将前面构造的VALUE
(辅助函数,实际不需要)、EXTRACT
、INC
组合起来,即可得PRED
函数:
(define TEN
(lambda (f x) (f (f (f (f (f (f (f (f (f (f x))))))))))))
(define PRED
(lambda (number)
(lambda (f x)
(define EXTRACT
(lambda (value)
(value (lambda (x) x))))
(define INC
(lambda (value)
(lambda (package)
(package (value f)))))
(define V-1
(lambda (u) x))
(EXTRACT (number INC V-1)))))
((PRED TEN)
(lambda (x) (+ x 1))
0)
化简为:
(define PRED
(lambda (number)
(lambda (f x)
((number
(lambda (value)
(lambda (package)
(package (value f))))
(lambda (u) x))
(lambda (x) x)))))
这就是最终得到的前驱函数。