什么是y组合子?

y组合子是一个计算机科学的概念,源自泛函式(functional”侧面的事情。大多数程序员根本不了解组合子,如果他们听说过的话。

  • 什么是y组合子?
  • 组合符是如何工作的?
  • 它们有什么用?
  • 它们在过程语言中有用吗?
132247 次浏览

如果你准备好长篇大读,Mike Vanier有一个great的解释。长话短说,它允许您在一种不一定支持递归的语言中实现递归。

不动点组合子(或不动点运算符)是一种高阶函数,用于计算其他函数的一个不动点。此操作与编程语言理论相关,因为它允许以重写规则的形式实现递归,而不需要语言的运行时引擎的显式支持。(src维基百科)

JavaScript中的y-combinator:

var Y = function(f) {
return (function(g) {
return g(g);
})(function(h) {
return function() {
return f(h(h)).apply(null, arguments);
};
});
};


var factorial = Y(function(recurse) {
return function(x) {
return x == 0 ? 1 : x * recurse(x-1);
};
});


factorial(5)  // -> 120
< p > 编辑: 通过查看代码,我学到了很多东西,但是如果没有一些背景知识,这个代码有点难以理解——对此我感到抱歉。根据其他答案所提供的一些常识,你可以开始分析正在发生的事情

Y函数是“Y组合子”。现在看一下使用Y的var factorial行。请注意,您传递给它的函数具有一个参数(在本例中为recurse),该参数稍后也将在内部函数中使用。形参名称基本上成为内部函数的名称,允许它执行递归调用(因为它在定义中使用recurse())。Y -combinator的作用是将内部匿名函数与传递给Y的函数的参数名相关联。

关于Y如何魔术的完整解释,请查看相关文章(顺便说一下,不是我写的)。

Y-combinator是一个“函数”(一个作用于其他函数的函数),当你不能从内部引用函数时,它可以实现递归。在计算机科学理论中,它推广了递归,抽象其实现,从而将其与所讨论的函数的实际工作分离。递归函数不需要编译时名称的好处是一种额外的好处。=)

这适用于支持lambda函数的语言。lambdas基于__abc1的性质通常意味着它们不能通过名称引用自己。通过声明变量,引用它,然后赋值给它,来完成自引用循环,是很脆弱的。可以复制lambda变量,并重新分配原始变量,这将破坏自引用。

静态类型语言(程序语言语言通常是)中,y组合子的实现和使用都很麻烦,因为通常键入限制要求在编译时知道函数的参数数量。这意味着必须为需要使用的任何参数count编写y-combinator。

下面是一个在c#中如何使用和工作的Y-Combinator的例子。

使用y组合子涉及到一种构造递归函数的“不寻常”方式。首先,你必须把你的函数写成一段代码,调用一个已经存在的函数,而不是它自己:

// Factorial, if func does the same thing as this bit of code...
x == 0 ? 1: x * func(x - 1);

然后将其转换为一个函数,该函数接受一个要调用的函数,并返回一个这样做的函数。这被称为函数,因为它接受一个函数,并对其执行一个操作,该操作产生另一个函数。

// A function that creates a factorial, but only if you pass in
// a function that does what the inner function is doing.
Func<Func<Double, Double>, Func<Double, Double>> fact =
(recurs) =>
(x) =>
x == 0 ? 1 : x * recurs(x - 1);

现在你有了一个函数,它接受一个函数,并返回另一个函数,它看起来像一个阶乘,但它不是调用它自己,而是调用传递给外部函数的参数。怎么把它变成阶乘呢?将内部函数传递给自身。Y-Combinator就是这样做的,它是一个具有永久名称的函数,可以引入递归。

// One-argument Y-Combinator.
public static Func<T, TResult> Y<T, TResult>(Func<Func<T, TResult>, Func<T, TResult>> F)
{
return
t =>  // A function that...
F(  // Calls the factorial creator, passing in...
Y(F)  // The result of this same Y-combinator function call...
// (Here is where the recursion is introduced.)
)
(t); // And passes the argument into the work function.
}

不是阶乘调用自身,而是阶乘调用阶乘生成器(由对Y-Combinator的递归调用返回)。根据t的当前值,从生成器返回的函数会再次调用生成器,用t - 1,或者直接返回1,终止递归。

它是复杂而神秘的,但在运行时,它的工作关键是“延迟执行”,并将递归分解为跨越两个函数。内部的F是作为参数传递,在下一个迭代中调用只有在必要时

为了帮助自己掌握Y-Combinator,我在Clojure和Scheme中都写了一份关于Y-Combinator的“傻瓜指南”。他们受到了《小阴谋家》的影响

< p >方案: https://gist.github.com/z5h/238891 < / p > < p >或Clojure: https://gist.github.com/z5h/5102747 < / p >

这两个教程都是代码中穿插的注释,应该删减。可粘贴到您最喜欢的编辑器。

下面是Y-Combinator和Factorial函数的JavaScript实现(来自Douglas Crockford的文章,可在:http://javascript.crockford.com/little.html获得)。

function Y(le) {
return (function (f) {
return f(f);
}(function (f) {
return le(function (x) {
return f(f)(x);
});
}));
}


var factorial = Y(function (fac) {
return function (n) {
return n <= 2 ? n : n * fac(n - 1);
};
});


var number120 = factorial(5);

我从http://www.mail-archive.com/boston-pm@mail.pm.org/msg02716.html中引用了这个,这是我几年前写的一个解释。

在本例中我将使用JavaScript,但许多其他语言也可以。

我们的目标是能够写出一个1的递归函数 变量只使用1变量的函数,没有 赋值,通过名称定义事物等(为什么这是我们的 目标是另一个问题,我们把它作为 我们所面临的挑战。)似乎不可能,是吧?作为 一个例子,让我们实现阶乘。

第一步是说我们可以很容易地做到这一点如果我们 作弊了一点。使用二元函数和 我们至少可以避免使用

// Here's the function that we want to recurse.
X = function (recurse, n) {
if (0 == n)
return 1;
else
return n * recurse(recurse, n - 1);
};


// This will get X to recurse.
Y = function (builder, n) {
return builder(builder, n);
};


// Here it is in action.
Y(
X,
5
);
现在让我们看看能不能少作弊。首先我们用 任务,但我们不需要。我们可以写成X和 Y内联。< / p >
// No assignment this time.
function (builder, n) {
return builder(builder, n);
}(
function (recurse, n) {
if (0 == n)
return 1;
else
return n * recurse(recurse, n - 1);
},
5
);
但是我们用两个变量的函数得到一个1的函数 变量。我们能解决这个问题吗?一个叫 Haskell Curry有一个巧妙的技巧,如果你有好的高阶 那么你只需要一个变量的函数。的 证明是你可以从函数2(或更多) 一般情况下)变量以1变量为纯粹 机械文本转换如下:

// Original
F = function (i, j) {
...
};
F(i,j);


// Transformed
F = function (i) { return function (j) {
...
}};
F(i)(j);
< p >的地方……完全一样。(这个技巧叫做 “模仿”它的发明者。Haskell也是一种语言 以哈斯克尔·库里命名。把它归为无用的琐事。) 现在只要把这个变换应用到任何地方,我们就得到

// The dreaded Y-combinator in action!
function (builder) { return function (n) {
return builder(builder)(n);
}}(
function (recurse) { return function (n) {
if (0 == n)
return 1;
else
return n * recurse(recurse)(n - 1);
}})(
5
);

请尽情尝试。Alert()返回,将其绑定到一个按钮,等等。 该代码不使用,递归地计算阶乘 2变量的赋值、声明或函数。(但 试图追踪它是如何工作的可能会让你头晕目眩。 递过来,没有推导,只是稍微重新格式化了一下 将导致代码肯定是令人困惑和困惑的。)

你可以替换递归定义阶乘的4行 任何你想要的递归函数

我想知道从头开始建立这个系统是否有任何用处。让我们来看看。这是一个基本的递归阶乘函数:

function factorial(n) {
return n == 0 ? 1 : n * factorial(n - 1);
}

让我们重构并创建一个名为fact的新函数,该函数返回一个匿名的阶乘计算函数,而不是执行计算本身:

function fact() {
return function(n) {
return n == 0 ? 1 : n * fact()(n - 1);
};
}


var factorial = fact();

这有点奇怪,但这没什么问题。我们只是在每一步生成一个新的阶乘函数。

这个阶段的递归仍然相当明确。fact函数需要知道自己的名字。让我们参数化递归调用:

function fact(recurse) {
return function(n) {
return n == 0 ? 1 : n * recurse(n - 1);
};
}


function recurser(x) {
return fact(recurser)(x);
}


var factorial = fact(recurser);

这很好,但是recurser仍然需要知道自己的名字。让我们把它参数化:

function recurser(f) {
return fact(function(x) {
return f(f)(x);
});
}


var factorial = recurser(recurser);

现在,我们不直接调用recurser(recurser),而是创建一个返回结果的包装器函数:

function Y() {
return (function(f) {
return f(f);
})(recurser);
}


var factorial = Y();

现在我们可以完全去掉recurser名称;它只是Y内部函数的一个参数,可以用函数本身替换:

function Y() {
return (function(f) {
return f(f);
})(function(f) {
return fact(function(x) {
return f(f)(x);
});
});
}


var factorial = Y();

唯一仍然引用的外部名称是fact,但现在应该清楚了,它也很容易参数化,创建完整的,泛型的解决方案:

function Y(le) {
return (function(f) {
return f(f);
})(function(f) {
return le(function(x) {
return f(f)(x);
});
});
}


var factorial = Y(function(recurse) {
return function(n) {
return n == 0 ? 1 : n * recurse(n - 1);
};
});

其他答案对此提供了相当简洁的答案,但没有一个重要的事实:你不需要用任何实用语言以这种令人费解的方式实现定点组合子,这样做没有任何实际目的(除了“看,我知道y组合子是什么”)。这是一个重要的理论概念,但没有什么实际价值。

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将发散/无限循环)。

上面的大多数答案描述的是y组合子,而不是

不动点组合子用于表示微积分图灵完全的。这是计算理论中一个非常重要的结果,为函数式编程提供了理论基础。

学习不动点组合子也帮助我真正理解了函数式编程。但我从未发现它们在实际编程中有任何用处。

Y-Combinator是通量电容器的另一个名称。

对于那些没有深入接触过函数式编程,现在也不想开始,但有点好奇的程序员:

Y组合子是一个公式,它允许你在这样的情况下实现递归:函数不能有名称,但可以作为参数传递,用作返回值,并在其他函数中定义。

它的工作原理是将函数作为参数传递给自己,这样它就可以调用自己。

它是lambda演算的一部分,lambda演算实际上是数学,但实际上是一种编程语言,是计算机科学尤其是函数式编程的基础。

Y组合子的日常实用价值是有限的,因为编程语言倾向于让你命名函数。

如果你需要在警察的队列中识别它,它看起来是这样的:

Y = λf.(λx。F (x x)) (λx。F (x x))

你通常可以发现它,因为重复的(λx.f (x x))

λ符号是希腊字母lambda,这给了lambda演算它的名字,并且有很多(λx.t)风格的术语,因为这就是lambda演算的样子。

我认为回答这个问题的最好方法是选择一种语言,比如JavaScript:

function factorial(num)
{
// If the number is less than 0, reject it.
if (num < 0) {
return -1;
}
// If the number is 0, its factorial is 1.
else if (num == 0) {
return 1;
}
// Otherwise, call this recursive procedure again.
else {
return (num * factorial(num - 1));
}
}

现在重写它,使它不使用函数内部的函数名,但仍然递归地调用它。

函数名factorial唯一应该被看到的地方是在调用位置。

提示:不能使用函数名,但可以使用参数名。

解决这个问题。不要去查。一旦你解决了它,你就会明白y组合子解决了什么问题。

this运算符可以简化你的生活:

var Y = function(f) {
return (function(g) {
return g(g);
})(function(h) {
return function() {
return f.apply(h(h), arguments);
};
});
};

这样就避免了额外的函数:

var fac = Y(function(n) {
return n == 0 ? 1 : n * this(n - 1);
});

最后,调用fac(5)

作为一个组合器的新手,我发现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-factorial接受了函数f和数字x (almost-factorial被curcury了,所以它可以被视为接受了函数f并返回一个1-arity函数)。

almost-factorialx计算阶乘时,它将x - 1的阶乘计算委托给函数f,并将该结果与x相加(在这种情况下,它将(x - 1)的结果与x相乘)。

它可以被看作是almost-factorial接受了一个蹩脚的版本的阶乘函数(只能计算到x - 1),并返回一个less-crappy版本的阶乘(计算到x)。如下所示:

almost-factorial crappy-f = less-crappy-f

如果我们反复将阶乘的less-crappy版本传递给almost-factorial,我们最终将得到我们想要的阶乘函数f。其中可以认为是:

almost-factorial f = f

Fix-point

事实上,almost-factorial f = f意味着f是函数almost-factorialfix-point

这是一种非常有趣的方式来看待上述函数之间的关系,对我来说是一个顿悟的时刻。(如果你还没读过,请阅读Mike关于fix-point的文章)

三个函数

概括地说,我们有一个非递归函数fn(就像我们的almost阶乘),我们有它的fix-point函数fr(就像我们的f),然后Y所做的是,当你给Y fn时,Y返回fn的定点函数。

所以总的来说(通过假设fr只带一个参数来简化);x退化为x - 1x - 2…在递归):

  • 我们将核心计算定义为fn: def fn fr x = ...accumulate x with result from (fr (- x 1)),这是< em > almost-useful < / em >函数——尽管我们不能直接在x上使用fn,但它很快就会有用。这个非递归的fn使用函数fr来计算其结果
  • fn fr = frfrfn的定点,fr< / em > < em >有用函数,我们可以在x上使用fr来得到结果
  • Y fn = frY返回一个函数的定点,Y 将我们的almost useful函数__ABC3转换为useful fr

派生Y(不包含)

我将跳过Y的推导,直接去理解Y。Mike Vainer的帖子有很多细节。

Y的形式

Y定义为(在微积分格式中):

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)

因此,(Y f)的结果确实是f的定点。

为什么(Y f)工作?

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

所以

fr x = Y fn x = fn (λs.(fn (s s)) λs.(fn (s s))) x

对我来说,这种设置的神奇之处在于:

  • fnfr相互依赖:fr“包裹”fn在里面,每次fr被用来计算x时,它都会“生成”(“提升”?)一个fn并将计算委托给那个fn(自身传递frx);另一方面,fn依赖于fr并使用fr来计算一个较小的问题fr3的结果。
  • fr被用来定义fn时(当fn在其操作中使用fr时),真正的fr还没有定义。
  • fn定义了真正的业务逻辑。基于fnY创建了fr——一个特定形式的辅助函数——以方便以递归方式计算fn

它帮助我理解Y在这个时候,希望它有帮助。

顺便说一句,我也发现这本书通过Lambda微积分的函数式编程介绍非常好,我只读了一部分,事实上,我无法理解书中的Y,这让我想到了这个帖子。

匿名的递归

定点组合子是一个根据定义满足等价性的高阶函数fix

forall f.  fix f  =  f (fix f)

fix f表示定点方程的解x

               x  =  f x

自然数的阶乘可以用

fact 0 = 1
fact n = n * fact (n - 1)

使用fix,可以在没有匿名自指性的情况下推导出一般/μ-递归函数的任意构造证明。

fact n = (fix fact') n

在哪里

fact' rec n = if n == 0
then 1
else n * rec (n - 1)

这样

   fact 3
=  (fix fact') 3
=  fact' (fix fact') 3
=  if 3 == 0 then 1 else 3 * (fix fact') (3 - 1)
=  3 * (fix fact') 2
=  3 * fact' (fix fact') 2
=  3 * if 2 == 0 then 1 else 2 * (fix fact') (2 - 1)
=  3 * 2 * (fix fact') 1
=  3 * 2 * fact' (fix fact') 1
=  3 * 2 * if 1 == 0 then 1 else 1 * (fix fact') (1 - 1)
=  3 * 2 * 1 * (fix fact') 0
=  3 * 2 * 1 * fact' (fix fact') 0
=  3 * 2 * 1 * if 0 == 0 then 1 else 0 * (fix fact') (0 - 1)
=  3 * 2 * 1 * 1
=  6

这个形式证明

fact 3  =  6

有条不紊地使用重写的定点组合子等价

fix fact'  ->  fact' (fix fact')

微积分

无类型lambda演算形式包含在上下文无关的语法中

E ::= v        Variable
|  λ v. E   Abstraction
|  E E      Application

其中v范围在变量上,以及β埃塔减少规则

(λ x. B) E  ->  B[x := E]                                 Beta
λ x. E x  ->  E          if x doesn’t occur free in E   Eta

Beta简化用表达式(“参数”)E替换抽象(“函数”)体B中变量x的所有自由出现。Eta约简消除了冗余抽象。它有时在形式主义中被省略。不适用归约规则的不可约表达式位于正常的规范形式中。

λ x y. E

是简写

λ x. λ y. E

(抽象multiarity),

E F G

是简写

(E F) G

(应用程序left-associativity),

λ x. x

而且

λ y. y

alpha-equivalent

抽象和应用是lambda演算中仅有的两个“语言原语”,但它们允许编码任意复杂的数据和操作。

教会数字是一种自然数的编码,类似于花生公理化自然数。

   0  =  λ f x. x                 No application
1  =  λ f x. f x               One application
2  =  λ f x. f (f x)           Twofold
3  =  λ f x. f (f (f x))       Threefold
. . .


SUCC  =  λ n f x. f (n f x)       Successor
ADD  =  λ n m f x. n f (m f x)   Addition
MULT  =  λ n m f x. n (m f) x     Multiplication
. . .

一个正式的证明

1 + 2  =  3

使用beta约简重写规则:

   ADD                      1            2
=  (λ n m f x. n f (m f x)) (λ g y. g y) (λ h z. h (h z))
=  (λ m f x. (λ g y. g y) f (m f x)) (λ h z. h (h z))
=  (λ m f x. (λ y. f y) (m f x)) (λ h z. h (h z))
=  (λ m f x. f (m f x)) (λ h z. h (h z))
=  λ f x. f ((λ h z. h (h z)) f x)
=  λ f x. f ((λ z. f (f z)) x)
=  λ f x. f (f (f x))                                       Normal form
=  3

组合子

在lambda演算中,组合子是不包含自由变量的抽象。最简单的:I,单位组合子

λ x. x

同构的恒等函数

id x = x

这样的组合子是combinator结石的基元操作符,类似于SKI系统。

S  =  λ x y z. x z (y z)
K  =  λ x y. x
I  =  λ x. x

Beta还原不是强烈的正常化;并不是所有的可约化表达式,“重解”,在约简下收敛到正常形式。一个简单的例子是ω组合子的发散应用

λ x. x x

本身:

   (λ x. x x) (λ y. y y)
=  (λ y. y y) (λ y. y y)
. . .
=  _|_                     Bottom

减少最左边的子表达式(“头”)是优先的。应用顺序规范了替换前的参数,而正常秩序则不是。这两种策略类似于渴望求值(例如C)和懒惰求值(例如Haskell)。

   K          (I a)        (ω ω)
=  (λ k l. k) ((λ i. i) a) ((λ x. x x) (λ y. y y))

在热切应用阶beta缩减下发散

=  (λ k l. k) a ((λ x. x x) (λ y. y y))
=  (λ l. a) ((λ x. x x) (λ y. y y))
=  (λ l. a) ((λ y. y y) (λ y. y y))
. . .
=  _|_

因为在严格的语义中

forall f.  f _|_  =  _|_

但在惰性法阶约简下是收敛的

=  (λ l. ((λ i. i) a)) ((λ x. x x) (λ y. y y))
=  (λ l. a) ((λ x. x x) (λ y. y y))
=  a

如果一个表达式具有正规形式,则用正规阶beta缩减法可以找到它。

Y

Y 定点combinator的基本属性

λ f. (λ x. f (x x)) (λ x. f (x x))

是由

   Y g
=  (λ f. (λ x. f (x x)) (λ x. f (x x))) g
=  (λ x. g (x x)) (λ x. g (x x))           =  Y g
=  g ((λ x. g (x x)) (λ x. g (x x)))       =  g (Y g)
=  g (g ((λ x. g (x x)) (λ x. g (x x))))   =  g (g (Y g))
. . .                                      . . .

等效

Y g  =  g (Y g)

同构于

fix f  =  f (fix f)

无类型lambda演算可以在一般/μ递归函数上编码任意构造证明。

 FACT  =  λ n. Y FACT' n
FACT'  =  λ rec n. if n == 0 then 1 else n * rec (n - 1)


FACT 3
=  (λ n. Y FACT' n) 3
=  Y FACT' 3
=  FACT' (Y FACT') 3
=  if 3 == 0 then 1 else 3 * (Y FACT') (3 - 1)
=  3 * (Y FACT') (3 - 1)
=  3 * FACT' (Y FACT') 2
=  3 * if 2 == 0 then 1 else 2 * (Y FACT') (2 - 1)
=  3 * 2 * (Y FACT') 1
=  3 * 2 * FACT' (Y FACT') 1
=  3 * 2 * if 1 == 0 then 1 else 1 * (Y FACT') (1 - 1)
=  3 * 2 * 1 * (Y FACT') 0
=  3 * 2 * 1 * FACT' (Y FACT') 0
=  3 * 2 * 1 * if 0 == 0 then 1 else 0 * (Y FACT') (0 - 1)
=  3 * 2 * 1 * 1
=  6

(乘法延迟,汇流)

对于丘奇无类型lambda演算,除了Y之外,已经证明存在无限个递归可枚举的定点组合子。

 X  =  λ f. (λ x. x x) (λ x. f (x x))
Y'  =  (λ x y. x y x) (λ y x. y (x y x))
Z  =  λ f. (λ x. f (λ v. x x v)) (λ x. f (λ v. x x v))
Θ  =  (λ x y. y (x x y)) (λ x y. y (x x y))
. . .

法阶beta约简使未扩展的无类型lambda演算成为一个图灵完全重写系统。

在Haskell中,可以优雅地实现定点组合子

fix :: forall t. (t -> t) -> t
fix f = f (fix f)

Haskell的惰性在所有子表达式都被求值之前归一到有限。

primes :: Integral t => [t]
primes = sieve [2 ..]
where
sieve = fix (\ rec (p : ns) ->
p : rec [n | n <- ns
, n `rem` p /= 0])

下面是原来的问题的答案,编译自尼古拉斯·曼库索的回答中提到的这篇文章(完全值得一读),以及其他答案:

什么是y组合子?

y组合子是一个“函数”(或高阶函数——一个作用于其他函数的函数),它接受一个参数,这是一个非递归的函数,并返回该函数的一个递归版本。


有点递归=),但更深入的定义:

一个组合子-只是一个没有自由变量的lambda表达式。
自由变量-不是绑定变量。
绑定变量-包含在lambda表达式体中的变量,该变量名作为其参数之一

另一种思考方式是,combinator是这样一个lambda表达式,在其中,你可以在任何地方用它的定义替换一个组合子的名称,并且一切都仍然有效(如果combinator在lambda体中包含对自身的引用,你将进入一个无限循环)。

y组合子是一个定点组合子。

函数的不动点是函数定义域中的一个元素,它被函数映射到函数自身。
也就是说,如果f(c) = c . c是函数f(x)的一个固定点
这意味着f(f(...f(c)...)) = fn(c) = c

组合符是如何工作的?

下面的例子假设strong + dynamic typing:

惰性(正规阶)y组合子: < / p >
此定义适用于具有lazy(也称为deferred, call-by-need)求值的语言——该求值策略将表达式的求值延迟到需要它的值时。

Y = λf.(λx.f(x x)) (λx.f(x x)) = λf.(λx.(x x)) (λx.f(x x))

这意味着,对于给定的函数f(这是一个非递归函数),对应的递归函数可以首先通过计算λx.f(x x)来获得,然后将这个lambda表达式应用于自身。

严格(应用阶)y组合子: < / p >
这个定义适用于具有严格求值策略(也包括热切求值和贪婪求值策略)的语言,在这种策略中,表达式一旦绑定到变量就会被求值。

Y = λf.(λx.f(λy.((x x) y))) (λx.f(λy.((x x) y))) = λf.(λx.(x x)) (λx.f(λy.((x x) y)))

它本质上和lazy一样,只是有一个额外的λ包装器来延迟lambda的体求值。我已经问过另一个问题,与这个主题有点相关。

它们有什么用?

Stolen borrow from answer by Chris Ammerman: Y-combinator泛化递归,抽象其实现,从而将其与函数的实际工作分离。

尽管Y-combinator有一些实际应用,但它主要是一个理论概念,理解它将扩展你的整体视野,并有可能提高你的分析和开发技能。

它们在过程语言中有用吗?

As 陈述Mike Vanier:可以在许多静态类型语言中定义Y组合子,但是(至少在我看到的例子中)这样的定义通常需要一些不明显的类型技巧,因为Y组合子本身没有一个直接的静态类型。这超出了本文的范围,所以我不再进一步提及

克里斯·安默曼提到的一样:大多数过程性语言都有静态类型。

所以这个问题的答案是,不是真的。