我相信我在数学上理解Y-combinator的概念:它返回给定函数F
的固定点,因此f = Y(F)
f
满足f == F(f)
。
但我不明白实际的计算程序是如何明智的?
我们来看看here给出的javascript示例:
var Y = (F) => ( x => F( y => x(x)(y) ) )( x => F( y => x(x)(y) ) )
var Factorial = (factorial) => (n => n == 0 ? 1 : n * factorial(n-1))
Y(Factorial)(6) == 720 // => true
computed_factorial = Y(Factorial)
我不明白的部分是如何实际计算computed_factorial
函数(固定点)?通过跟踪Y的定义,你会发现它在x(x)
部分遇到无限递归,我看不到任何终止案例。然而,奇怪的确回来了。谁能解释一下?
在惰性评估语言中,Y组合子可以定义为:
Y = (f =>
(x => f( x(x) ))
(x => f( x(x) )))
但由于Javascript是一种急切的评估语言,因此以这种方式定义Y会导致x(x)部分在您尝试将Y应用于函数时无限递归。
为了解决这个问题,可以引入一个匿名的“包装器”函数来延迟x的执行。这个包装函数在调用时的行为与x(x)相同,但会立即返回,因为它只是一个函数定义。
知道x(x)将绑定到递归函数,在示例的情况下:
Factorial = f => n => n==0 ? 1 : n*f(n-1)
我们可以提前告诉我们只传递一个参数。它允许我们使用以下模式生成一个匿名函数,其行为与任何给定函数f(x)相同:
f => x => f(x)
当我们将这个模式应用于x(x)项时,Y将不再无限递归并变为:
Y = (f =>
(x => f( y => x(x)(y) ))
(x => f( y => x(x)(y) )))
我将使用ES6箭头函数语法。既然您似乎了解CoffeeScript,那么阅读它应该没有问题。
这是你的Y组合子
var Y = F=> (x=> F (y=> x (x) (y))) (x=> F (y=> x (x) (y)))
我将使用你的factorial
函数的改进版本。这个使用累加器,这将阻止评估变成一个大金字塔。这个函数的过程将是线性迭代的,而你的函数将是递归的。当ES6最终获得尾部呼叫消除时,这会产生更大的差异。
语法上的差异是名义上的。无论如何,这并不重要,因为你只想看看如何评估Y
。
var factorial = Y (fact=> acc=> n=>
n < 2 ? acc : fact (acc*n) (n-1)
) (1);
那么这将导致计算机开始做一些工作。所以,在进一步讨论之前,让我们对此进行评估......
我希望你的文本编辑器中有一个非常好的支架荧光笔......
var factorial
= Y (f=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (1) // sub Y
= (F=> (x=> F (y=> x (x) (y))) (x=> F (y=> x (x) (y)))) (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (1) // apply F=> to fact=>
= (x=> (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> x (x) (y))) (x=> (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> x (x) (y))) (1) // apply x=> to x=>
= (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> (x=> (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> x (x) (y))) (x=> (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> x (x) (y))) (y)) (1) // apply fact=> to y=>
= (acc=> n=> n < 2 ? acc : (y=> (x=> (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> x (x) (y))) (x=> (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> x (x) (y))) (y)) (acc*n) (n-1)) (1) // apply acc=> to 1
= n=> n < 2 ? 1 : (y=> (x=> (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> x (x) (y))) (x=> (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> x (x) (y))) (y)) (1*n) (n-1) // return value
= [Function] (n=> ...)
所以你在我们打电话后可以看到这里:
var factorial = Y(fact=> acc=> n=> ...) (1);
//=> [Function] (n=> ...)
我们得到一个等待单个输入的函数n
。让我们现在运行一个阶乘
在我们继续之前,您可以通过在javascript repl中复制/粘贴它来验证(并且您应该)此处的每一行都是正确的。每一行都将返回
24
(这是factorial(4)
的正确答案。抱歉,如果我为你破坏了它)。这就像你简化分数,求解代数方程或平衡化学公式一样;每一步都应该是正确的答案。请务必向右滚动以查看我的评论。我告诉你我在每一行完成了哪项操作。完成操作的结果在后续行。
并确保你再次使用支架荧光笔...
factorial (4) // sub factorial
= (n=> n < 2 ? 1 : (y=> (x=> (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> x (x) (y))) (x=> (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> x (x) (y))) (y)) (1*n) (n-1)) (4) // apply n=> to 4
= 4 < 2 ? 1 : (y=> (x=> (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> x (x) (y))) (x=> (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> x (x) (y))) (y)) (1*4) (4-1) // 4 < 2
= false ? 1 : (y=> (x=> (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> x (x) (y))) (x=> (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> x (x) (y))) (y)) (1*4) (4-1) // ?:
= (y=> (x=> (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> x (x) (y))) (x=> (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> x (x) (y))) (y)) (1*4) (4-1) // 1*4
= (y=> (x=> (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> x (x) (y))) (x=> (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> x (x) (y))) (y)) (4) (4-1) // 4-1
= (y=> (x=> (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> x (x) (y))) (x=> (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> x (x) (y))) (y)) (4) (3) // apply y=> to 4
= (x=> (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> x (x) (y))) (x=> (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> x (x) (y))) (4) (3) // apply x=> to x=>
= (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> (x=> (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> x (x) (y))) (x=> (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> x (x) (y))) (y)) (4) (3) // apply fact=> to y=>
= (acc=> n=> n < 2 ? acc : (y=> (x=> (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> x (x) (y))) (x=> (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> x (x) (y))) (y)) (acc*n) (n-1)) (4) (3) // apply acc=> to 4
= (n=> n < 2 ? 4 : (y=> (x=> (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> x (x) (y))) (x=> (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> x (x) (y))) (y)) (4*n) (n-1)) (3) // apply n=> to 3
= 3 < 2 ? 4 : (y=> (x=> (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> x (x) (y))) (x=> (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> x (x) (y))) (y)) (4*3) (3-1) // 3 < 2
= false ? 4 : (y=> (x=> (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> x (x) (y))) (x=> (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> x (x) (y))) (y)) (4*3) (3-1) // ?:
= (y=> (x=> (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> x (x) (y))) (x=> (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> x (x) (y))) (y)) (4*3) (3-1) // 4*2
= (y=> (x=> (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> x (x) (y))) (x=> (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> x (x) (y))) (y)) (12) (3-1) // 3-1
= (y=> (x=> (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> x (x) (y))) (x=> (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> x (x) (y))) (y)) (12) (2) // apply y=> to 12
= (x=> (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> x (x) (y))) (x=> (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> x (x) (y))) (12) (2) // apply x=> to y=>
= (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> (x=> (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> x (x) (y))) (x=> (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> x (x) (y))) (y)) (12) (2) // apply fact=> to y=>
= (acc=> n=> n < 2 ? acc : (y=> (x=> (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> x (x) (y))) (x=> (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> x (x) (y))) (y)) (acc*n) (n-1)) (12) (2) // apply acc=> 12
= (n=> n < 2 ? 12 : (y=> (x=> (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> x (x) (y))) (x=> (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> x (x) (y))) (y)) (12*n) (n-1)) (2) // apply n=> 2
= 2 < 2 ? 12 : (y=> (x=> (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> x (x) (y))) (x=> (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> x (x) (y))) (y)) (12*2) (2-1) // 2 < 2
= false ? 12 : (y=> (x=> (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> x (x) (y))) (x=> (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> x (x) (y))) (y)) (12*2) (2-1) // ?:
= (y=> (x=> (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> x (x) (y))) (x=> (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> x (x) (y))) (y)) (12*2) (2-1) // 12*2
= (y=> (x=> (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> x (x) (y))) (x=> (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> x (x) (y))) (y)) (24) (2-1) // 2-1
= (y=> (x=> (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> x (x) (y))) (x=> (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> x (x) (y))) (y)) (24) (1) // apply y=> to 24
= (x=> (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> x (x) (y))) (x=> (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> x (x) (y))) (24) (1) // apply x=> to x=>
= (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> (x=> (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> x (x) (y))) (x=> (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> x (x) (y))) (y)) (24) (1) // apply fact=> to y=>
= (acc=> n=> n < 2 ? acc : (y=> (x=> (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> x (x) (y))) (x=> (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> x (x) (y))) (y)) (acc*n) (n-1)) (24) (1) // apply acc=> to 24
= (n=> n < 2 ? 24 : (y=> (x=> (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> x (x) (y))) (x=> (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> x (x) (y))) (y)) (24*n) (n-1)) (1) // apply n=> to 1
= 1 < 2 ? 24 : (y=> (x=> (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> x (x) (y))) (x=> (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> x (x) (y))) (y)) (24*1) (1-1) // 1 < 2
= true ? 24 : (y=> (x=> (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> x (x) (y))) (x=> (fact=> acc=> n=> n < 2 ? acc : fact (acc*n) (n-1)) (y=> x (x) (y))) (y)) (24*1) (1-1) // ?:
= 24
我也见过Y
的其他实现。这是从头开始构建另一个(在javascript中使用)的简单过程。
// text book
var Y = f=> f (Y (f))
// prevent immediate recursion (javascript is applicative order)
var Y = f=> f (x=> Y (f) (x))
// remove recursion using U combinator
var Y = U (h=> f=> f (x=> h (h) (f) (x)))
// given: U = f=> f (f)
var Y = (h=> f=> f (x=> h (h) (f) (x))) (h=> f=> f (x=> h (h) (f) (x)))
Y组合子是lambda演算中最有趣的现象之一。我怀疑它立即看到它,人们可以想出它的功能。
Y = f => (g => g(g))(g => n => f(g(g))(n));
这个想法是递归地运行lambda(匿名函数)。
嘿等一下..!如果你没有名字来引用一个函数并且首先在它自己内部调用它,你究竟能做到这一点..?
让我们试着逐步理解它的推导。我将使用箭头功能,所以如果你不熟悉它们,请关注this link。它们非常简单。 x => x
意味着function(x){return x;}
。 JS this
关键字在箭头中具有不同的含义,但根据此主题,这是关于主题的。
因此,我们将一如既往地使用阶乘函数,但我们将导出的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
meh ..!
所以我们开始玩内部函数。通常我会通过这一步但看到转换可能会有所帮助...所以让我们继续。我可以定义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
并返回正确工作的因子函数。因此,我们定义一个函数,将我们的hyporthetical递归函数作为一个参数,并给我们一些工作(固定)。 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));
我希望一切都清楚......
我想详细说明(这将是一篇长篇阅读文章)关于diwo关于热切无限x(x)评估的答案。
在阅读了diwo的答案之后,我很快浏览了并且在this中跳过了不感兴趣的部分,以下是我对此的理解。
让我们用x - > v表示评估(执行程序),意思是“x评估到值v”。
在急切和懒惰的评估中,匿名函数被视为值,即它已经被评估,因此函数的评估立即停止。
然后对f(y)的急切评估将如下所示:f(y) - >(首先评估y到v) - > f(v) - >(将函数f应用于参数v) - > f(v)。 (现在(第二步之后)功能真的应用了,会在第二步看到)
而对于对比,f(y)的懒惰评估将跳过第一步:f(y) - >(将函数f应用于参数v) - > f(y)(现在函数实际应用,但请注意,y保持不变)。
现在让我们像在第二次定义的diwo的答案和Y一样是因子:
Y = (f =>
(x => f( x(x) ))
(x => f( x(x) )))
和
F = Factorial = f => n => n==0 ? 1 : n*f(n-1)
整个评估(不工作)Y F看起来像:
YF - > w(w):=(x => F(x(x)))(x => F(x(x))) - > F((x => F(x(x)))( x => F(x(x))))= F(w(w)),其中w是(x => F(x(x)))的符号。现在这在热切和懒惰的评价中都是一样的,但从现在开始,我们得到了不同。
在懒惰的评估中,F将“抽象地”(没有评估)应用于w(w),并且如此评估
- >(n => n == 0?1:n *(w(w))(n-1))),
然后停止评估,因为这是匿名函数,我们已经知道匿名函数不会被进一步评估。
在对对比度的急切评估中,F(w(w)) - > F(v),这意味着必须首先评估参数w(w)。
现在评估w(w)=(x => F(x(x)))(x => F(x(x))) - > F((x => F(x(x)))(x = > F(x(x))))= F(w(w))。现在,这在急切的评估中使用规则进一步评估,首先评估论证,即w(w)。正如我们刚才看到的那样,再次对F(w(w))进行评估。因此我们陷入循环... YF - > w(w) - > F(w(w)) - > F(F(w(w))) - > F(F(W(w(w)) )) - > ...错误。
如果我们通过定义改进这一点
Y = (f =>
(x => f( y => x(x)(y) ))
(x => f( y => x(x)(y) )))
相反,评估类似于懒惰的情况:
YF - > z(z):=(x => F(y => x(x)(y)))(x => F(y => x(x)(y))) - > F(y =>(x => F(y => x(x)(y)))((x => F(y => x(x)(y))))(y)))= F(y = > z(z)(y)))。
作为急切的规则,我们现在必须评估参数(y => z(z)(y))。因为这是匿名函数,所以它的评估是完整的,所以我们继续将F应用于(y => z(z)(y)),就像在惰性求值中一样。我们现在得到
F(y => z(z)(y))) - >(n => n == 0?1:n *((y => z(z)(y))(n-1)))现在结束评估,因为这是匿名函数。这类似于第一次懒惰评估。