理解 Y Combinator
很多次看到Y combinator, 但是一直没有好好理解它。The Little Schemer上也 有一章讲Y combinator讲得很详细的。不过当时看了没有理解明白。真正理解它 的方式还是自己来推导一遍最好。
为什么要有Y combinator
Y combinator提供了一种不用特殊手段就可以实现自引用(self-reference) 的方式。像在Scheme中,定义全局函数也可以实现自引用, 比如下面的阶乘 函数的实现:
(define fact (lambda (n) (if (< n 2) 1 (* n (fact (- n 1))))))
在某种程度上(参考),使用define定义是不那么好的,因为它使函数的定义 依赖于全局变量空间(the global variable space)。而全局变量空间是容易 被破坏或者修改的。
如何推导出Y combinator
The Little Schemer的第9章对于推导Y combinator讲得很详细。不过既然自 己试着推导了一遍,记录一下也挺好。上面说到Y的目的就是不要有define, 那么一个没有define的阶乘函数就是这样一个:
(lambda (n) (if (< n 2) 1 (* n (? (- n 1)))))
这个函数里的?是我们暂时不知道要放什么的地方。不过就暂时这样的话,也 能计算阶乘1和2,比如下面的计算就是可以计算1的阶乘:
((lambda (n) (if (< n 2) 1 (* n (? (- n 1))))) 1)
那么?里面到底是要放什么呢?要放的是一个真正的阶乘函数。如果有define 定义,那真正的阶乘函数就是上面的fact。但是我们要去掉对define的依赖, 就必须另找其它方法。
一个神奇的函数
先把?里面的东西放一下,来看一个神奇的函数:
((lambda (m) (m m)) (lambda (l) (l l)))
把这个函数展开:
((lambda (l) (l l)) (lambda (l) (l l)))
得到是自己!这称为不动点(这里我说得应该不严格)。不动点理论很有意思, 在很多领域里都有应用。比如求2的平方根,你可以通过找函数f(x) = 0.5 * ( x + 2 / x)的不动点来实现。查看wiki的不动点理论,有更多惊喜等着 你:http://en.wikipedia.org/wiki/Fixed-point_theorem
转化一下,看起来快解决了
看了前面讲的那个不动点函数,我们可以找到?了。在找?之前,我们可以把?提出来,可能帮助理解。下面的转化还是那个只能求1和2的阶乘函数:
((lambda (m) (m ?)) (lambda (l) (lambda (n) (if (< n 2) 1 (* n (l (- n 1)))))))
你也可以在上面加入一点功能,让它能求3的阶乘:
((lambda (m) (m (m ?))) (lambda (l) (lambda (n) (if (< n 2) 1 (* n (l (- n 1)))))))
也可以再加入4的阶乘的求解:
((lambda (m) (m (m (m ?)))) (lambda (l) (lambda (n) (if (< n 2) 1 (* n (l (- n 1)))))))
然后是5的阶乘:
((lambda (m) (m (m (m (m ?))))) (lambda (l) (lambda (n) (if (< n 2) 1 (* n (l (- n 1)))))))
你会发现,这样挺累的。。。另外,你也注意到了在上面的求3,4,5的阶乘函数里的?是什么其实不重要。那就把它换成m吧!如下:
((lambda (m) (m m)) (lambda (l) (lambda (n) (if (< n 2) 1 (* n (l (- n 1)))))))
但是这还是没达到我们的目的。上面的还是只能求1和2的阶乘。
那个神奇的函数
你还记得那个神奇的函数不?
((lambda (m) (m m)) (lambda (l) (l l)))
想一想,把这个想法应用到前面最后我们转化的函数上:
((lambda (m) (m m)) (lambda (l) (lambda (n) (if (< n 2) 1 (* n ((l l) (- n 1)))))))
你会发现这函数已经可以求任意数的阶乘了!这一步的跳跃有点大。你如果 要真正理解它,需要自己好好想想。
收工
上面最后我们已经实现了不用define进行自引用了。但是要看到Y combinator,需要整理一下上面的结果。首先(l l)是返回一个接收一个参 数的函数,我们可以把它等价为(参考 The Little Schemer):
(lambda (x) ((l l) x))
那可以得到:
((lambda (m) (m m)) (lambda (l) ((lambda (f) (if (< n 2) 1 (* n (f (- n 1))))) (lambda (x) ((l l) x)))))
进一步抽像出:
(lambda (f) (if (< n 2) 1 (* n (f (- n 1)))))
就可以得到:
((lambda (fac) ((lambda (m) (m m)) (lambda (l) (fac (lambda (x) ((l l) x)))))) (lambda (f) (lambda (n) (if (< n 2) 1 (* n (f (- n 1)))))))
而上面的部分:
((lambda (m) (m m)) (lambda (l) (fac (lambda (x) ((l l) x)))))
就是Y combinator,更规范一些的定义是:
(define Y (lambda (fun) ((lambda (m) (m m)) (lambda (m) (fun (lambda (x) ((m m) x)))))))
这个函数Y接收一个参数fun,返回这个函数自身。举一个例子:
((Y (lambda (len) (lambda (lst) (if (null? lst) 0 (+ 1 (len (cdr lst))))))) '(a b c e))
以上函数返回4.