本文译自 “Deriving the Y Combinator in 7 Easy Steps“,
原文链接:http://igstan.ro/posts/2010-12-01-deriving-the-y-combinator-in-7-easy-steps.html
在没有原生递归支持的语言中,Y结合子(Y Combinator)是一种实现递归的方式(事实上,它更常被作为一种锻炼程序思维的方式)。要实现Y结合子,要求这种语言支持匿名函数。
此处,我选择JavaScript来推导Y结合子,从递归阶乘函数的定义开始,一步一步进行变换。
Step 1
最初的实现,使用JavaScript内建的递归机制。
1 2 3 4 | var fact = function (n) { if (n < 2) return 1; return n * fact(n - 1); }; |
---|
Step 2
获得递归的最简单方法是什么?我们可以定义一个函数,它接受它自身作为参数,并且用这个参数作为参数,调用这个参数。当然,这是一个无限循环,会导致栈溢出。
1 2 3 4 5 | (function (f) { f(f); })(function (f) { f(f); }); |
---|
我们的阶乘函数套用上面的模板,再做点改动,阶乘函数接受一个我们还不知道的参数,所以我们要的是返回一个接受该参数的函数。然后这个函数可以被用于计算阶乘。同时,这可以让我们的阶乘函数不会无限循环下去。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | var fact = (function (f) { return function (n) { // 终止条件 if (n < 2) return 1; //因为f返回一个函数,所以这有一个双重调用。 return n * f(f)(n - 1); }; })(function (f) { return function (n) { // 终止条件 if (n < 2) return 1; // 因为f返回一个函数,所以这有一个双重调用。 return n * f(f)(n - 1); }; }); |
---|
Step 3
此时,我们的代码有一些糟糕的重复,让我们把放进一个名叫recur的辅助函数里。
1 2 3 4 5 6 7 8 9 10 11 12 | var recur = function (f) { return f(f); }; var fact = recur(function (f) { return function (n) { if (n < 2) return 1; // 因为f返回一个函数,所以这有一个双重调用。 return n * f(f)(n - 1); }; }); |
---|
Step 4
上面这个版本的问题是,它有两重调用(指的是f(f)(n-1))。我们想去除它,让我们的函数跟接近于递归版本。怎么做呢?
我们可以使用一个辅助函数,它接受一个数值参数,进行两重调用。这个trick通过把辅助函数放在可见f的作用域里,所以g可以调用f。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | var recur = function (f) { return f(f); }; var fact = recur(function (f) { var g = function (n) { return f(f)(n); }; return function (n) { if (n < 2) return 1; // 没有双重调用了,函数g接受一个数值参数。 return n * g(n - 1); }; }); |
---|
Step 5
以上版本工作良好,但是它的定义太杂乱了。我们可以把它再清理为一个辅助函数,把阶乘定义尽可能分离出来。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | var recur = function (f) { return f(f); }; var wrap = function (h) { return recur(function (f) { var g = function (n) { return f(f)(n); }; return h(g); }); }; var fact = wrap(function (g) { return function (n) { if (n < 2) return 1; return n * g(n - 1); }; }); |
---|
Step 6
g只调用了一次,让我们把g内联进wrap里。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | var recur = function (f) { return f(f); }; var wrap = function (h) { return recur(function (f) { return h(function (n) { return f(f)(n); }); }); }; var fact = wrap(function (g) { return function (n) { if (n < 2) return 1; return n * g(n - 1); }; }); |
---|
Step 7
现在,如果我们把recur也内联进wrap里,我们就得到了著名的Y结合子!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | var Y = function (h) { return (function (f) { return f(f); })(function (f) { return h(function (n) { return f(f)(n); }); }); }; var fact = Y(function (g) { return function (n) { if (n < 2) return 1; return n * g(n - 1); }; }); |
---|
The End
玩的开心!