Y 组合子是 lambda 演算中最有趣的现象之一。我怀疑看到它后,人们能否对它的功能做出解释。
Y = f => (g => g(g))(g => n => f(g(g))(n));
这个想法是递归地运行一个 lambda(匿名函数)。
嘿等一下..!如果您没有名称来引用函数并首先在其自身内部调用它,那么您该怎么做呢?
让我们尝试一步一步地理解它的推导。我将使用箭头功能,所以如果您不熟悉它们,请点击此链接。它们非常简单。x => x
意味着function(x){return x;}
。JSthis
关键字在箭头中具有不同的含义,但根据本主题,这是题外话。
因此,我们将一如既往地使用阶乘函数,但我们将推导出的 Y 组合子将适用于所有递归函数。
阶乘函数可以简单地表示如下
var fa = n => n === 0 ? 1 : n*fa(n-1);
fa(5) // <- 120
但是说我们不想fa
递归地引用函数;相反,我们想从阶乘函数的假设版本中推导出一个有效的阶乘函数。什么是假设的阶乘函数?假设的阶乘函数采用适当的阶乘函数并返回给我们一个有效的阶乘函数。像下面这样
var fh = f => n => n === 0 ? 1 : n*f(n-1);
因此,如果我将fa
函数fh
作为参数传递给它,它将起作用。喜欢;
fh(fa)(5); // <- 120
但是我们不想引用另一个阶乘函数,fa
因为我们已经“有点”在fh
函数中定义了阶乘逻辑。然后我们想。如果我将适当的阶乘函数传递给它,则将参数fh
保持f
在闭包中并返回一个工作阶乘函数( ) 。那么如果我将自己传递给它呢?快速尝试咩..!n => n === 0 ? 1 : n*f(n-1)
fa
fh(fh)(5) // <- NaN
所以我们开始玩内部函数。通常我会通过这一步,但看到转换可能会有所帮助......所以让我们继续吧。我可以定义fb
函数来返回一个函数,它接受两个参数,它本身和阶乘计数n
fb = (f,n) => n === 0 ? 1 : n* f(f,n-1), // fb(fb,5) <- 120
到目前为止一切都很好,但是两个参数阶乘函数与我正在寻找的东西相去甚远。我可以通过添加另一个称为部分应用程序的功能步骤将它们分开。
fc = f => n => n === 0 ? 1 : n* f(f)(n-1), // fc(fc)(5) <- 120
现在这非常接近我们的假设函数fh
。但是内部显示f(f)(n-1)
我们必须更正它以显示 f(n-1)。好吧,也许我们可以使用一个 JS 美女 IIFE 来帮助我们......
fd = f => n => ((g,n) => n === 0 ? 1 : n * g(n-1))(f(f),n) // fd(fd)(5) <- 120
你能看到IIFE..吗?((g,n) => n === 0 ? 1 : n * g(n-1))(f(f),n)
然而,虽然这看起来不错,但我必须摆脱双重参数(g,n)
IIFE 才能达到预期的结果。这将通过部分应用获得另一个级别的功能。
fe = f => n => (g => n => n === 0 ? 1 : n * g(n-1))(f(f))(n) // fe(fe)(5) <- 120
现在我们有里面g => n => n === 0 ? 1 : n * g(n-1)
是我们假设函数的主体fh
。这意味着我可以在上面的函数中替换(我喜欢这部分..就像微积分替换;事实上它是......)fh
fe = f => n => fh(f(f))(n) // fe(fe)(5) <- 120
好的,是时候结束它了。我一开始想要什么..?我想输入fh
一个函数(Y-combinator)并得到它的固定点。在这里我知道它fe(fe)
使用fh
并返回给我正常工作的阶乘函数。因此,让我们定义一个函数以将我们的假设递归函数作为参数并给我们一些工作(固定)的东西。IIFE 再次提供帮助。
Y = f => (g => g(g))(g => n => f(g(g))(n));
所以这应该适用于任何事情。让我们用假设的斐波那契函数来试试我们的 Y 组合器。
var Y = f => (g => g(g))(g => n => f(g(g))(n)),
fibH = f => n => n === 0 ? 0
: n === 1 ? 1
: f(n-2) + f(n-1),
fibo = Y(fibH);
console.log(fibo(10));
我希望一切都清楚...