Lambda算子5b:How of Y
其实是
这篇文章的意译。有些东西省了。添了点私货。就有了下面的帖子。
虽然Y相当神奇。对它的推导也不完全是天外飞仙般无迹可寻。基本上我们为了解决让没有名字的函数能自我引用,一步一步抽象出了Y。所以知道Y的推导过程对我们程序员还是很有意义的:毕竟编程的过程也是抽象的过程。看看当年的老大们怎么从纷繁的表象里抽象出一般规律,对我们日后的思考应该大有好处。为了老大们能够试验,我就用JavaScript了。整个推导的过程非常像编程时的重构。我们提出一个粗略的解决方案,然后仔细观察,找出可以抽象的地方,进行抽象。得到一个更普适的结果后,继续重复重构的步骤,直到得到最优解。估计看这篇帖子的人都知道怎么玩儿小小JavaScript吧?再说有个浏览器就有了测试环境。废话少说,看代码。我们还是以阶乘函数为例。先看通常的写法:
1: function fact(n){
2: if(n == 0){
3: return 1;
4: }
5:
6: if(n > 0){
7: return n * fact(n - 1);
8: }
9: }
上面的JavaScript函数定义内部调用自己。这种调用可行的前提是我们用函数名指代函数定义。也就是说,
fact这个名字绑定的函数定义就是上面的函数体。如果我们不能通过名字来调用函数怎么办呢(就跟lambda算子一样)?也许有老大会问:为什么增加这个限制呢?不是自虐么?理由很简单:理论需要探求事物本质。记得奥卡姆剃刀吧?如无必要,毋增实体。函数名到底是必需元素,还是句法糖?这种研究方法也有实际的意义:再复杂的系统也是在简单但完备的基础上搭建起来的。强大的编程工具,总是基于于层层叠加的抽象,而最低级的抽象层总是非常简单。简单意味着透彻,简单意味着健壮。简单意味着灵活。简单意味着经济。问题是,到底简单到什么地步?怎么保证系统不至于简单到一无所用的地步?这和逻辑学家建立系统时总是要证明系统的正确性和完备性一个道理。而找到了Y,我们也就明白了,原来函数名绑定并非本质。
嗯,继续。函数
fact是递归的基本形式。既然我们不能直接在函数体内通过函数名调用另一个函数,我们至少可以把想调用的函数通过参数传进去。于是我们得到
fact2:
09: fact2 = function(himself, n){
10: return function(n){
11: if(n <