Y-combinator如何以编程方式计算固定点?

时间:2016-05-08 15:39:41

标签: javascript y-combinator

我相信我在数学上理解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)部分遇到无限递归,我无法看到任何隐含的终止案例。然而,奇怪的确回来了。谁能解释一下?

4 个答案:

答案 0 :(得分:2)

我将使用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)))

答案 1 :(得分:2)

在惰性评估语言中,Y组合子可以定义为:

Y = (f =>
  (x => f( x(x) ))
  (x => f( x(x) )))

但由于Javascript是一种急切的评估语言,因此以这种方式定义 Y 会导致 x(x)部分在您尝试应用时无限期地递归< em> 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) )))

答案 2 :(得分:2)

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函数中定义了因子逻辑。然后我们想。 fhf参数保留在闭包中,如果我向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));

我希望一切都清楚......

答案 3 :(得分:1)

我想详细说明(这将是一篇长篇文章)关于 diwo的急切的无限x(x)评估

在阅读diwo的答案后,我浏览非常快,并跳过了this中不感兴趣的部分,下面是我的理解方式。

我们自己的符号,定义

用x-> v表示评估(程序执行),意思是“ x评估为v”。

eager lazy 评估中,匿名函数都被视为值,即已经被评估,因此功能评估立即停止。

然后急切对f(y)的求值应像这样:f(y)->(首先将y赋给v)-> f(v)->(将函数f应用于自变量v)-> f(v)。 (现在(第二步之后)功能已真正应用,将在第二秒看到)

为了对比,对f(y)的 lazy 评估将跳过第一步:f(y)->(将函数f应用于自变量v)-> f(y) (现在函数确实已应用,但请注意,y保持不变)。

现在让F成为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( F(w(w))))​​-> ...错误。

第二个(可行的)解决方案+渴望

如果我们通过定义

对此进行改进
Y = (f =>
  (x => f( y => x(x)(y) ))
  (x => f( y => x(x)(y) ))) 

相反,评估类似于懒惰的情况:

Y F-> 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) ))现在结束评估,因为这是匿名函数。这类似于第一次惰性评估。