邱奇数的除法实现较为困难。
但有了邱奇数的减法和比较,那么我们大致可以想到,如果能够不断把被除数减掉除数,直到它变得小于除数,我们就可以实现邱奇数除法了。
然而问题在于,我们如何在Lambda演算中实现这种“循环”?
最容易想到的是用递归代替循环,那么,Lambda函数能否直接递归呢?
答案是不行,我们使用一个Lambda函数前必须要先定义它。
Y Combinator
于是我们尝试构造一个可以递归的环境,假设有一个生成器函数,它能够提供给一个函数f以包含自身的上下文,即:
这样我们就可以在省略号处正常编写递归函数了。于是问题的关键转化为为,如何编写实现这个函数,我们首先写一个Y的形式,此处g应该是我们前面所写用于生成f的函数。
我们希望给g传入f,即:
但我们又没有f,为了生成f,我们又要调用g,于是要写成:
看起来这样无穷调用,写不出来。
我们来换个思路,我们没有必要构造一个真实的f,我们只需要给传入一个跟f完全一致的函数就可以了。我们可以写一个虚假的F
但这里面仍然用到f,因为F完全等价于f,所以我们可以写:
这个写法仍然递归,但是因为F是我们自己定义的,所以我们可以构造一个把函数传给自己的结构
再把F改写做:
于是最终写法是:
注:如果不考虑实际编程产生死循环,Y组合子可以写作更简单的形式:
这样我们就有了函数递归的能力,我们不妨把公式翻译成JavaScript代码验证下我们的推导:
const Y = g => (F => F(F))(F => x => g(F(F))(x));
const sum = Y(sum => n => n > 0 ? n + sum(n - 1) : 0);
sum(100); //5050
由此我们可以看到,无论语言是否支持递归,只要函数具有闭包性质和一等公民身份,我们都可以基于lambda理论实现递归。
这里的特殊函数Y,我们把它称作Y Combinator,即Y组合子。
邱奇数除法
有了递归,我们实现邱奇数除法就变得顺理成章了。
我们还可以顺道定义取余运算
总结
至此,我们已经实现了邱奇数的加减乘除,在这个过程中,我们还顺道实现了分支和循环(递归)两种逻辑,这样,我们推导出一组跟编程语言环境相近的lambda演算基础设施。
在整个学习过程中,你应该对一些函数式编程中常见的概念有一定体会:
- 函数是一等公民:在lambda演算中,函数是函数、函数是数据、函数是参数、函数是返回值、函数也是表达式,应该说,在原版lambda演算中,并不存在所谓“二等公民”,一切公民都是函数。
- 高阶函数:在lambda演算中,并不存在“非高阶函数”,一切函数都是以函数为参数、以函数为返回值的。
- 柯里化:在lambda演算中,并不支持多参数的函数,所以只能通过柯里化的形式表达多参数函数。
- 纯函数/不可变性:在lambda演算中,并不存在变量和赋值,所以更不可能改变变量的值。
实际上,因为lambda演算属于纯数学,它的设计并不在乎性能和人类编写方便,所以多数编程语言不会直接使用lambda的邱奇数、Y组合子等作为基础设施。
然而,正因为lambda演算达成了图灵完备,这让我们可以从其中获得制造计算机和设计编程语言的灵感。后世也在这个思路的基础上发展了组合子逻辑、类型论、基于范畴论的计算理论,这些都带来了编程领域的新发展。