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

问题描述 投票:9回答:4

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

javascript y-combinator
4个回答
2
投票

在惰性评估语言中,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) )))

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)))

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));

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


1
投票

我想详细说明(这将是一篇长篇阅读文章)关于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)))的符号。现在这在热切和懒惰的评价中都是一样的,但从现在开始,我们得到了不同。

First solution + lazy

在懒惰的评估中,F将“抽象地”(没有评估)应用于w(w),并且如此评估

- >(n => n == 0?1:n *(w(w))(n-1))),

然后停止评估,因为这是匿名函数,我们已经知道匿名函数不会被进一步评估。

First (not working) solution + eager

在对对比度的急切评估中,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)) )) - > ...错误。

Second (working) solution + eager

如果我们通过定义改进这一点

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

© www.soinside.com 2019 - 2024. All rights reserved.