U combinator, Y combinator和递归
最近把Y combinator搞清楚了,总算是迈入了函数式编程的大门,可喜可贺!
Why Y? Why?
Y combinator有什么用?为什么需要它?我们想象这样一个问题:要在scheme里面实现一个计算阶乘的函数,可能会这样写:
程序没有什么难理解的,使用递归来计算阶乘。但是,如果我们想要在一个匿名函数里实现怎么办?换句话说,怎么在lambda中进行递归?
U combinator
要在进行递归,我们必须找到一种让函数调用自己的办法。假设我们有以下函数:
不难发现,虽然这个
函数并不是我们想要的递归函数, 但是1
fact1
。原因是我们有1
(fact1 fact1) = fact
这两点。 因此我们可以得到一个递归的阶乘定义:
事实上,这种模式我们可以用在任何递归函数上,我们定义
就可以把这种递归的方式进行推广。这里面的U就是传说中的
。1
U combinator
Y combinator
我们刚才提到U combinator可以实现递归,即使编程语言不支持递归,只支持高阶函数。那么Y combinator又是什么呢? 我们考虑以下的函数:
这个函数很像我们的
函数(注意和fact1的区别!),它和1
fact
函数之间是什么关系呢?1
fact
所以说
, 也就是说, 1
(fact2 fact) = fact
是1
fact
的 不动点。1
fact2
我们定义一个函数
, 接受一个函数1
Y
为参数,返回1
f
的不动点。这就是1
f
。1
Y combinator
所以,假设我们可以得到Y, 那么
通常所说的
是这个:1
Y combinator
具体推导过程这里略过,但是我们可以很容易验证满足
成立。1
(Y f) = f (Y f)
但实际上不动点函数有很多种,这里介绍另外一种的推导,首先我们根据定义有
事实上这个定义在lazy-scheme里面是可以成立的,但是在applicative order中会无限循环,我们需要这样修改:
这样的定义已经可以work了,可以试试
。但是我们还得把函数定义里面的Y给消除掉。怎么消除呢?想起前面提到的1
((Y fact2) 5) = 120
了吗?没错,我们就用它:1
U combinator
用
代替需要递归的函数,然后在外围加一个1
(y y)
,就得到了一个可以工作的1
U
的定义。这个定义和我们之前介绍的并不一样。其实1
Y
不止一个。1
Y combinator