从“函数”的角度来看,y组合子是一个计算机科学的概念。大多数程序员根本不了解组合子,如果他们听说过的话。
什么是y组合子? 组合符是如何工作的? 它们有什么用? 它们在过程语言中有用吗?
从“函数”的角度来看,y组合子是一个计算机科学的概念。大多数程序员根本不了解组合子,如果他们听说过的话。
什么是y组合子? 组合符是如何工作的? 它们有什么用? 它们在过程语言中有用吗?
当前回答
其他答案对此提供了相当简洁的答案,但没有一个重要的事实:你不需要用任何实用语言以这种令人费解的方式实现定点组合子,这样做没有任何实际目的(除了“看,我知道y组合子是什么”)。这是一个重要的理论概念,但没有什么实际价值。
其他回答
对于那些没有深入接触过函数式编程,现在也不想开始,但有点好奇的程序员:
Y组合子是一个公式,它允许你在这样的情况下实现递归:函数不能有名称,但可以作为参数传递,用作返回值,并在其他函数中定义。
它的工作原理是将函数作为参数传递给自己,这样它就可以调用自己。
它是lambda演算的一部分,lambda演算实际上是数学,但实际上是一种编程语言,是计算机科学尤其是函数式编程的基础。
Y组合子的日常实用价值是有限的,因为编程语言倾向于让你命名函数。
如果你需要在警察的队列中识别它,它看起来是这样的:
Y = λf.(λx。F (x x)) (λx。F (x x))
你通常可以发现它,因为重复的(λx。F (x x))
λ符号是希腊字母,这是λ演算的名字,有很多(λx.t)风格的术语因为这就是λ演算的样子。
如果你准备好长篇大论,Mike Vanier有一个很好的解释。长话短说,它允许您在一种不一定支持递归的语言中实现递归。
y组合子实现匿名递归。所以与其
function fib( n ){ if( n<=1 ) return n; else return fib(n-1)+fib(n-2) }
你可以这样做
function ( fib, n ){ if( n<=1 ) return n; else return fib(n-1)+fib(n-2) }
当然,y-combinator只适用于按名字命名的语言。如果你想在任何正常的值调用语言中使用它,那么你将需要相关的z-combinator (y-combinator将发散/无限循环)。
作为一个组合子的新手,我发现Mike Vanier的文章(感谢Nicholas Mancuso)真的很有帮助。我想写一个总结,除了记录我的理解,如果它能对其他人有所帮助,我将非常高兴。
从糟糕到不那么糟糕
以factorial为例,我们使用下面的almost-factorial函数来计算number x的阶乘:
def almost-factorial f x = if iszero x
then 1
else * x (f (- x 1))
在上面的伪代码中,almost-阶乘接受函数f和数字x (almost-阶乘是curry的,所以它可以被视为接受函数f并返回一个1-arity函数)。
当almost-factorial计算x的阶乘时,它将x - 1的阶乘计算委托给函数f,并将该结果与x相加(在本例中,它将(x - 1)的结果与x相乘)。
它可以被看作是almost-阶乘接受了一个蹩脚的阶乘函数(它只能计算到数字x - 1),并返回一个不那么蹩脚的阶乘(计算到数字x)。如下形式:
almost-factorial crappy-f = less-crappy-f
如果我们反复地将阶乘的不那么糟糕的版本传递给almost阶乘,我们最终会得到我们想要的阶乘函数f。其中可以考虑为:
almost-factorial f = f
Fix-point
几乎阶乘f = f意味着f是几乎阶乘函数的定点。
这是一种非常有趣的方式来看待上述函数之间的关系,对我来说是一个顿悟的时刻。(如果你还没读过,请阅读Mike关于fix-point的文章)
三个函数
概括地说,我们有一个非递归函数fn(就像我们的几乎阶乘),我们有它的定点函数fr(就像我们的f)然后Y所做的是当你给Y fn, Y返回fn的定点函数。
总之(通过假设fr只有一个参数来简化;X退化为X - 1, X - 2…在递归):
我们将核心计算定义为fn: def fn fr x =…将x与result from (fr (- x1))累加,这是一个几乎有用的函数-尽管我们不能直接在x上使用fn,但它很快就会有用。这个非递归fn使用一个函数fr来计算它的结果 fnfr = fr, fr是Fn的定点,fr是有用的函数,我们可以用fr作用于x来得到结果 Y fn = fr, Y返回一个函数的定点,Y把我们几乎有用的函数fn变成有用的fr
推导Y(不含)
我将跳过Y的推导,直接去理解Y。Mike Vainer的帖子有很多细节。
Y的形式
Y定义为(lambda微积分格式):
Y f = λs.(f (s s)) λs.(f (s s))
如果我们替换函数左边的变量s,我们得到
Y f = λs.(f (s s)) λs.(f (s s))
=> f (λs.(f (s s)) λs.(f (s s)))
=> f (Y f)
所以(yf)的结果就是f的定点。
(yf)为什么成立?
根据f的特征,(Y f)可以是任意arity的函数,为了简化,我们假设(Y f)只带一个参数,就像我们的阶乘函数一样。
def fn fr x = accumulate x (fr (- x 1))
因为fn fr = fr,我们继续
=> accumulate x (fn fr (- x 1))
=> accumulate x (accumulate (- x 1) (fr (- x 2)))
=> accumulate x (accumulate (- x 1) (accumulate (- x 2) ... (fn fr 1)))
当最里面的(fn fr 1)是基本情况并且fn在计算中不使用fr时,递归计算终止。
再看一下Y:
fr = Y fn = λs.(fn (s s)) λs.(fn (s s))
=> fn (λs.(fn (s s)) λs.(fn (s s)))
So
fr x = Y fn x = fn (λs.(fn (s s)) λs.(fn (s s))) x
对我来说,这种设置的神奇之处在于:
fn and fr interdepend on each other: fr 'wraps' fn inside, every time fr is used to calculate x, it 'spawns' ('lifts'?) an fn and delegates the calculation to that fn (passing in itself fr and x); on the other hand, fn depends on fr and uses fr to calculate result of a smaller problem x-1. At the time fr is used to define fn (when fn uses fr in its operations), the real fr is not yet defined. It's fn which defines the real business logic. Based on fn, Y creates fr - a helper function in a specific form - to facilitate the calculation for fn in a recursive manner.
它帮助我现在这样理解Y,希望有帮助。
顺便说一句,我还发现《通过Lambda微积分介绍函数式编程》这本书非常好,我只读了一部分,事实上,我无法理解书中的Y,这让我想到了这篇文章。
上面的大多数答案描述了y组合子是什么,但没有描述它的用途。
用不动点组合子来证明lambda演算是图灵完备的。这是计算理论中一个非常重要的结果,为函数式编程提供了理论基础。
学习不动点组合子也帮助我真正理解了函数式编程。但我从未发现它们在实际编程中有任何用处。