javascript上通过YCombinator实现递归

前言

这是一篇很干很干的货~

问题-递归的支持

如果一个编程语言,函数是一等公民,比如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

答案-part1-使用YCombinator来自制递归!

假设有一个伪装得很像递归函数的函数

//此处多传入一个很神奇的入参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);
}

答案-part2-YCominator的实现

通过上面总共五次的尝试,我们得到了神奇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,正确完成递归函数的调用
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值