这是一篇很干很干的货~
如果一个编程语言,函数是一等公民,比如javascript,但是在语言层面就不支持递归(这是一个蛋疼的假设),那该怎么办?
比如下面这个阶乘的例子(为了简单起见,假设了入参n是>=2的正整数,函数定义里就不检查了)
//如果javascript支持递归我们会这样定义阶乘函数
function factorial(n){
if (n==2) return 2;
else return n*factorial(n-1);
//上一行根据我们蛋疼的假设,代码中的factorial要报错,
//因为翻译器/编译器认定factorial函数的调用
//在factorial函数的定义内部不可被执行
}
console.log(factorial(5));
//上一行如果语言层面支持递归,此处本应打印120
假设有一个伪装得很像递归函数的函数
//此处多传入一个很神奇的入参self,self是一个函数
function fakeFactorial(self,n){
if (n==2) return 2;
else return n*self(n-1);
//上一行使用self就像使用递归函数factorial一样,
//假装此处self(n-1)的调用等同于递归调用factorial(n-1)
}
假设再有一个更神奇的Y函数,也就是YCombinator了,简称Y
//通过伪装递归函数fakeFactorial获得真正的递归函数factorial
var factorial = Y(fakeFactoial);
console.log(factorial(5));
//上一行打印120!我们在语言层面不支持的情况下自己实现了递归!
//不论fakeFactorial还是factorial还是Y的定义里面都不存在递归调用
我们写的每个伪装递归函数(包含真正递归逻辑的)都统称为F,上面例子里F就是fakeFactorial
然后通过神奇的Y函数就可以得到一个真正的递归函数f,上面例子里f就是factorial
利用Y函数实现递归的统一写法就是 f=Y(F),上例里就是factorial=Y(fakeFactorial)
然后我们就可以直接使用f了
这样的Y函数存在吗?是咋实现的?里面是不是偷偷使用了递归?是不是!
根据不动点理论,任何一个…(此处省略1万字理论)…,所以Y函数是存在的。
呃~~~~ 作为资深码农,我们还是来看程序怎么一步一步实现这个Y函数吧
既然在factorial里面不能调用factorial,那我把factorial作为参数一起传给factorial函数自己不就可以了么。
function factorial(self,n){
if (n==2) return 2;
else return n*self(self,n-1);//此处太丑陋
//上一行我们想要n*self(n-1),不是n*self(self,n-1)
}
console.log(factorial(factorial,5));
//此处打印了120!程序正确运行了!
把这个版本改名如下
function fakeFactorial(uglySelf,n){
if (n==2) return 2;
else return n*uglySelf(uglySelf,n-1);
//上一行的uglySelf的值是实际上是fakeFactorial函数,
//而我们想要的是factorial函数
}
console.log(fakeFactorial(fakeFactorial,5));
//此处打印了120!程序正确运行了!
那Y的实现就很简单了
//入参F是伪装递归函数,比如例子里的fakeFactorial
function Y(F){
return function(n){
return F(F,n);
};
}
var factorial = Y(fakeFactorial);
console.log(factorial(5));
//打印120了,程序正确
好像很简单嘛,但是仔细看和最终的正确答案还是不太一样啊,我们再重新审视第一次尝试里的fakeFactorial的实现
function fakeFactorial(uglySelf,n){
if (n==2) return 2;
else return n*uglySelf(uglySelf,n-1);
//上一行我们目标是n*self(n-1)而不是
//丑陋的n*uglySelf(uglySelf,n-1)
}
目标是n*self(n-1),如果我们能把其中的
...
else return n*uglySelf(uglySelf,n-1);
...
先变成
var self=uglySelf(uglySelf);
...
else return n*self(n-1);
...
不就可以了么,easy。
鉴于uglySelf其实就是fakeFactorial,那我们只要改造fakeFactorial,接受uglySelf(也就是fakeFactorial)作为参数,返回一个接受n作为参数的self(也就是factorial)函数就ok了
function fakeFactorial(uglySelf){
var factorial = function(n){
var self=uglySelf(uglySelf);
if(n==2)return 2;
else return n*self(n-1);
}
return factorial;
}
var factorial = fakeFactorial(fakeFactorial);
console.log(factorial(5));
//又打印120了,程序正确
那么Y的实现就更简单了
//此处入参F就是伪装递归函数fakeFactorial
function Y(F){
return F(F);
}
var factorial=Y(fakeFactorial);
console.log(factorial(5));
//打印120,妥妥的
我们再来回顾一下第二次尝试里面的fakeFactorial
function fakeFactorial(uglySelf){
var factorial = function(n){
var self=uglySelf(uglySelf);
//***以下两行就是最终正确的递归实现***
if(n==2)return 2; //<==正确的递归书写逻辑
else return n*self(n-1); //<==正确的递归书写逻辑
//***以上两行就是最终正确的递归实现***
}
return factorial;
}
我们把fakeFactorial函数里“两行正确的递归书写逻辑”放入真正的“fakeFactorial”函数里,然后把第二次尝试里的fakeFactorial改个名字,比如generateFactorial
首先是正确的我们要的最终的fakeFactorial函数定义
function fakeFactorial(self,n){
if(n==2)return 2; //<==我们要的正确的递归书写
else return n*self(n-1); //<==我们要的正确的递归书写
}
然后是generateFactorial函数
function generateFactorial(uglySelf){
var factorial = function(n){
var self=uglySelf(uglySelf);
//***以下调用正确的fakeFactorial替换之前的正确书写的两行递归实现***
return fakeFactorial(self,n);
}
return factorial;
}
测试一下
var factorial = generateFactorial(generateFactorial);
console.log(factorial(5));
//输出120,成功
正确的答案越来越近了
回顾第三次尝试中的generateFactorial函数和fakeFactorial函数,我们发现在generateFactorial函数里是写死了对fakeFactorial函数的调用的,如下
function generateFactorial(uglySelf){
var factorial = function(n){
var self=uglySelf(uglySelf);
//***以下调用正确的fakeFactorial替换之前的正确书写的两行递归实现***
return fakeFactorial(self,n);//此处写死了对fakeFactorial的调用
}
return factorial;
}
在这里我们需要把fakeFactorial函数作为参数绑定到generateFactorial函数里面,另外有那个已经快要被忘却的Y函数
先保持正确的fakeFactorial函数不变
function fakeFactorial(self,n){
if(n==2)return 2;
else return n*self(n-1);
}
然后是把fakeFactorial绑定到generateFactorial函数里的函数
//bind的入参F就是正确的伪装递归函数fakeFactorial
function bindFactorial(F){
var generateFactorial=function(uglySelf){
var factorial = function(n){
var self=uglySelf(uglySelf);
return F(self,n);//此处就是对fakeFactorial的调用
}
return factorial;
}
return generateFactorial;
}
测试一下
var generateFactorial = bindFactorial(fakeFactorial);
var factorial = generateFactorial(generateFactorial);
console.log(factorial(5));
//打印了120
上面的测试代码已经说明了Y函数应该是怎么样的了
//此处入参F就是fakeFactorial函数
function Y(F){
var generateFactorial = bindFactorial(F);
var factorial = generateFactorial(generateFactorial);
return factorial;
}
再测试一下
var factorial = Y(fakeFactorial);
console.log(factorial(5));
//输出120,正确!
此时此刻我们已经得到了正确的fakeFactorial,和正确的Y函数,并且通过Y(fakeFactorial)的返回值得到正确的factorial递归函数。
不过bindFactorial函数是个中间产物,完全可以合并到Y的逻辑里面,这样整个儿世界就清爽了
首先还是正确的fakeFactorial逻辑
function fakeFactorial(self,n){
if(n==2)return 2;
else return n*self(n-1);
}
然后我们来合并Y,先简单copy&paste,然后看看哪些地方可以精简的
function Y(F){
var bindFactorial=function(F){//bindFactorial的定义可以删掉
var generateFactorial=function(uglySelf){
var factorial = function(n){
var self=uglySelf(uglySelf);
return F(self,n);
}
return factorial;
}
return generateFactorial;//bindFactorial的return也可以去掉
}//bindFactorial定义结束的花括号可以删掉
var generateFactorial = bindFactorial(F);//bindFactorial去掉后此行调用也可以去掉了
var factorial = generateFactorial(generateFactorial);
return factorial;
}
初步简化后的版本
function Y(F){
var generateFactorial=function(uglySelf){//generateFactorial可以替换为fGen更通用
var factorial = function(n){//factorial可以替换为f就通用了
var self=uglySelf(uglySelf);
return F(self,n);
}
return factorial;
}
var factorial = generateFactorial(generateFactorial);//generateFactorial可以替换为fGen
return factorial;
}
再把变量名替换成通用的版本
function Y(F){
var fGen=function(uglySelf){
var f = function(n){
var self=uglySelf(uglySelf);
return F(self,n);
}
return f;
}
var f = fGen(fGen);
return f;
}
测试一下
var factorial = Y(fakeFactorial);
console.log(factorial(5));
//输出120,全无意外
如有代码洁癖可以再来个脱水版本的Y函数
function Y(F){
var fGen=function(uglySelf){
return function(n){
return F(uglySelf(uglySelf),n);
}
}
return fGen(fGen);
}
通过上面总共五次的尝试,我们得到了神奇Y函数的实现,在一个函数作为一等公民的语言里,如果这个语言层面不支持递归(又见蛋疼假设),我们就可以用Y函数配合伪装递归函数来实现递归逻辑的编写。
Y函数如下
function Y(F){
var fGen=function(uglySelf){
return function(n){
return F(uglySelf(uglySelf),n);
}
}
return fGen(fGen);
}
阶乘的伪装递归函数如下
function fakeFactorial(self,n){
if(n==2)return 2;
else return n*self(n-1);
}
真正的阶乘函数可以通过运行Y得到
var factorial = Y(fakeFactorial);
console.log(factorial(5));//打印120,正确完成递归函数的调用