Y-combinator是一种来自事物“功能”方面的计算机科学概念。大多数程序员对组合器一无所知,如果他们甚至听说过它们的话。
答案 0 :(得分:278)
当你无法从内部引用函数时,Y-combinator是一个“函数”(对其他函数起作用的函数),它可以实现递归。在计算机科学理论中,它概括了递归,抽象了它的实现,从而将它与所讨论的函数的实际工作分开。不需要递归函数的编译时名称的好处是一种奖励。 =)
这适用于支持lambda functions的语言。 lambdas基于expression的性质通常意味着他们不能通过名字来引用自己。通过声明变量,引用它,然后将lambda分配给它来完成自引用循环来解决这个问题是很脆弱的。可以复制lambda变量,并重新分配原始变量,这会破坏自引用。
Y-combinators在static-typed种语言(procedural languages经常使用)中实现并经常使用是很麻烦的,因为通常的输入限制要求所讨论的函数的参数数量为在编译时已知。这意味着必须为需要使用的任何参数计数编写y组合子。
下面是一个如何在C#中使用和使用Y-Combinator的示例。
使用Y-combinator涉及构造递归函数的“不寻常”方式。首先,您必须将函数编写为调用预先存在的函数的代码,而不是自身:
// 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 作为参数传递,在下一次迭代中调用,仅在必要时。
答案 1 :(得分:192)
如果您准备好长时间阅读,Mike Vanier has a great explanation。简而言之,它允许您使用一种本身不一定支持它的语言来实现递归。
答案 2 :(得分:98)
我从http://www.mail-archive.com/boston-pm@mail.pm.org/msg02716.html解除了这个问题,这是我几年前写的一个解释。
我将在此示例中使用JavaScript,但许多其他语言也可以使用。
我们的目标是能够编写1的递归函数 变量仅使用1个变量的函数而不是 作业,按名称定义事物等(为什么这是我们的 目标是另一个问题,让我们把它作为 我们给予的挑战。)似乎不可能,是吧?如 例如,让我们实现阶乘。
第1步是说我们可以轻松地做到这一点 骗了一点。使用2个变量的函数和 任务我们至少可以避免使用 用于设置递归的赋值。
// 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内联。
// 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
);
但是我们使用2个变量的函数来获得1的函数 变量。我们可以修复吗?嗯,一个聪明的家伙的名字 如果你有更好的高阶,Haskell Curry有一个巧妙的技巧 函数然后你只需要1个变量的函数。该 证据是你可以从2(或更多)的函数中获得 一般情况下)变量为1变量与纯粹 像这样的机械文本转换:
// Original
F = function (i, j) {
...
};
F(i,j);
// Transformed
F = function (i) { return function (j) {
...
}};
F(i)(j);
其中......保持完全相同。 (这个技巧被称为 在其发明者之后“讨好”。 Haskell语言也是 以Haskell Curry命名。在无用的琐事下的文件。) 现在只需在任何地方应用此转换即可 我们的最终版本。
// 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行 你想要的任何其他递归函数。
答案 3 :(得分:80)
我想知道是否有任何用途试图从头开始构建它。让我们来看看。这是一个基本的递归因子函数:
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);
};
});
答案 4 :(得分:48)
上面的大部分答案都描述了Y-combinator 的内容,而不是 。
Fixed point combinators用于表明lambda calculus为turing complete。这是计算理论中非常重要的结果,为functional programming提供了理论基础。
学习定点组合器也帮助我真正理解函数式编程。我在实际编程中从未发现它们有任何用处。
答案 5 :(得分:23)
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
修改强>: 我从查看代码中学到了很多东西,但是如果没有一些背景知识,这个代码有点难以接受 - 抱歉。通过其他答案提供的一些常识,您可以开始挑选正在发生的事情。
Y函数是“y-combinator”。现在看一下使用Y的var factorial
行。请注意,您将一个函数传递给它,该函数具有一个参数(在此示例中为recurse
),该参数稍后也会在内部函数中使用。参数名称基本上成为内部函数的名称,允许它执行递归调用(因为它在其定义中使用recurse()
。)y-combinator执行将其他匿名内部函数与参数名称相关联的魔力传递给Y的函数。
有关Y如何完成魔法的完整解释,请查看JavaScript(不是我的顺便提一下)
答案 6 :(得分:17)
对于那些没有深入体验过函数式编程的程序员,现在不想开始,但是有点好奇:
Y组合子是一个公式,它允许您在函数不能具有名称但可以作为参数传递,用作返回值并在其他函数中定义的情况下实现递归。
它通过将函数作为参数传递给自身来工作,因此它可以调用自身。
它是lambda演算的一部分,它实际上是数学,但实际上是一门编程语言,对于计算机科学尤其是函数式编程来说非常重要。
Y组合器的日常实用价值有限,因为编程语言倾向于让您命名功能。
如果您需要在警察阵容中识别它,它看起来像这样:
Y =λf。(λx.f(x x))(λx.f(x x))
由于重复(λx.f (x x))
,您通常可以发现它。
λ
符号是希腊字母lambda,它为lambda演算提供了名称,并且有很多(λx.t)
样式术语,因为这就是lambda演算的样子。
答案 7 :(得分:12)
其他答案提供了非常简洁的答案,没有一个重要的事实:你不需要用这种复杂的方式用任何实用语言实现定点组合器,这样做没有实际意义(除了“看,我知道什么Y-combinator是“)。这是重要的理论概念,但没有什么实际价值。
答案 8 :(得分:6)
这是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);
答案 9 :(得分:6)
Y-Combinator是助焊剂电容器的另一个名称。
答案 10 :(得分:5)
我在Clojure和Scheme中为Y-Combinator写了一篇“白痴指南”,以帮助自己掌握它。他们受到“The Little Schemer”中材料的影响
在计划中: https://gist.github.com/z5h/238891
或Clojure: https://gist.github.com/z5h/5102747
两个教程都是散布着评论的代码,应该剪切和放大。可以进入你最喜欢的编辑。
答案 11 :(得分:4)
y-combinator实现匿名递归。而不是
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将发散/无限循环)。
答案 12 :(得分:4)
以下是original questions的答案,the article汇总了answer by Nicholas Mancuso中提到的(TOTALY值得一读),以及其他答案:
什么是Y-combinator?
Y-combinator是一个&#34;功能性&#34; (或高阶函数 - 对其他函数起作用的函数),它接受一个参数,这是一个不递归的函数,并返回递归函数的一个版本。
有点递归=),但更深入的定义:
组合子 - 只是一个没有自由变量的lambda表达式。
自由变量 - 是一个不是绑定变量的变量。
Bound变量 - 包含在lambda表达式主体内部的变量,该变量名称作为其参数之一。
考虑这个问题的另一种方法是组合器就是这样一个lambda表达式,在这个表达式中,你可以用它的定义替换组合名称,无论它在哪里找到并且一切都仍然有用(你将进入无限循环)如果组合器在lambda体内包含对自身的引用。)
Y-combinator是一个定点组合器。
函数的固定点是函数域的一个元素,由函数映射到自身。
也就是说,如果c
,f(x)
是函数f(c) = c
的固定点
这意味着f(f(...f(c)...)) = fn(c) = c
组合子如何工作?
以下示例假设强+动态输入:
懒惰(正常顺序)Y-combinator:
此定义适用于具有延迟(也称为延迟,按需调用)评估的语言 - 评估策略将表达式的评估延迟到需要它的值。
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-combinator:
此定义适用于具有严格(也是:急切,贪婪)评估的语言 - 评估策略,其中表达式一旦被绑定到变量就会被评估。
Y = λf.(λx.f(λy.((x x) y))) (λx.f(λy.((x x) y))) = λf.(λx.(x x)) (λx.f(λy.((x x) y)))
它的性质与懒惰相同,它只有一个额外的λ
包装器来延迟lambda的身体评估。我问another question,与此主题有些相关。
它们有什么用处?
从answer by Chris Ammerman 借来的:Y-combinator推广递归,抽象其实现,从而将其与相关函数的实际工作分开。
即使Y-combinator有一些实际的应用,它主要是一个理论概念,理解它将扩展你的整体愿景,并且可能会提高你的分析和开发技能。
它们在程序语言中有用吗?
作为stated by Mike Vanier:可以在许多静态类型语言中定义Y组合子,但是(至少在我看过的例子中)这样的定义通常需要一些非显而易见的类型hackery,因为Y组合器本身并不具有直接的静态类型。这超出了本文的范围,因此我不会进一步提及
并且mentioned by Chris Ammerman:大多数过程语言都有静态类型。
所以回答这个 - 不是真的。
答案 13 :(得分:4)
定点组合子是一个高阶函数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
范围超出变量,以及 beta 和 eta reduction 规则
(λ x. B) E -> B[x := E] Beta
λ x. E x -> E if x doesn’t occur free in E Eta
Beta缩减用表达式(“argument”)x
替换抽象(“function”)body B
中变量E
的所有自由出现次数。 Eta减少消除了冗余抽象。形式主义有时会省略它。不适用缩减规则的 irreducible 表达式采用正常或规范形式。
λ x y. E
是
的简写λ x. λ y. E
(抽象多元化),
E F G
是
的简写(E F) G
(申请左关联),
λ x. x
和
λ y. y
alpha-equivalent 。
抽象和应用程序是lambda演算的两个唯一“语言基元”,但它们允许编码任意复杂的数据和操作。
教会数字是与Peano-axiomatic naturals类似的自然数字的编码。
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
使用β缩减的重写规则:
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
,身份组合者p>
λ x. x
与身份功能同构
id x = x
这样的组合子是组合子结石的原始算子,就像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))
在急切的应用顺序β减少下出现分歧
= (λ 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
如果表达式具有正常形式,则正常顺序β减少将找到它。
Y
定点组合器的基本属性
λ 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
(乘法延迟,汇合)
对于Churchian无类型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))
. . .
正常的β阶减少使得未扩展的无类型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])
答案 14 :(得分:4)
作为组合者的新手,我发现Mike Vanier's article(感谢Nicholas Mancuso)真的很有帮助。我想写一个摘要,除了记录我的理解之外,如果它对其他人有帮助我会很高兴。
使用factorial作为示例,我们使用以下almost-factorial
函数计算数字因子x
:
def almost-factorial f x = if iszero x
then 1
else * x (f (- x 1))
在上面的伪代码中,almost-factorial
接受函数f
并且数字x
(almost-factorial
是curry,因此可以将其视为函数{{ 1}}并返回1-arity函数。)
当f
计算almost-factorial
的阶乘时,它会将x
的阶乘计算委托给函数x - 1
,并将结果与f
一起累积(在此case,它将(x - 1)的结果乘以x)。
可以看出x
接受了糟糕的版本的阶乘函数(只能计算到数字almost-factorial
)并返回 less-crappy 版本的factorial(计算到数字x - 1
)。就像这种形式一样:
x
如果我们反复将 less-crappy 版本的factorial传递给almost-factorial crappy-f = less-crappy-f
,我们最终会获得所需的阶乘函数almost-factorial
。它可以被视为:
f
almost-factorial f = f
表示almost-factorial f = f
的事实是函数f
的修复点。
这是一种非常有趣的方式来看到上述功能的关系,这对我来说是一个很好的时刻。 (如果你没有,请阅读Mike关于修复点的帖子)
为了概括,我们有一个非递归函数almost-factorial
(就像我们的几乎因子一样),我们有 fix-point 函数{{1 (与我们的f一样),然后fn
在您fr
Y
时提供的内容,Y
会返回fn
的定点函数。< / p>
总结(假设Y
仅采用一个参数进行简化; fn
退化为fr
,x
...递归):
x - 1
:x - 2
,这是 几乎有用的 功能 - 虽然我们无法直接在fn
上使用def fn fr x = ...accumulate x with result from (fr (- x 1))
,但很快就会有用。这种非递归fn
使用函数x
来计算其结果fn
, fr
是fn fr = fr
的固定点,fr
是 有用的 功能,我们可以fn
使用fr
来获取结果fr
, x
会返回函数的定点,Y fn = fr
会使我们的几乎有用将函数Y
添加到有用的 Y
fn
(不包括在内)我将跳过fr
的推导并继续理解Y
。 Mike Vainer的帖子有很多细节。
Y
Y
定义为( lambda calculus 格式):
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)
可以是任何arity的函数,为简化起见,我们假设f
只接受一个参数,例如我们的阶乘函数。
(Y f)
自(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
相互依赖:fn
'包裹'fr
内部,每次fr
用于计算fn
,它'生成'('升降机'?)和fr
并将计算委托给x
(自己传递fn
和fn
);另一方面,fr
取决于x
并使用fn
来计算较小问题fr
的结果。fr
用于定义x-1
时fr
在其操作中使用fn
时,尚未定义真实fn
。 fr
。基于fr
,fn
创建fn
- 特定形式的辅助函数 - 以便于以递归方式计算Y
。 这帮助我此刻理解fr
,希望它有所帮助。
fn
这一事实让我想到这篇文章
答案 15 :(得分:3)
这个操作员可以简化你的生活:
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)
。
答案 16 :(得分:3)
定点组合子(或定点算子)是一个高阶函数,用于计算其他函数的固定点。此操作与编程语言理论相关,因为它允许以重写规则的形式实现递归,而无需语言的运行时引擎的明确支持。 (src维基百科)
答案 17 :(得分:0)
我认为回答此问题的最佳方法是选择一种语言,例如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-combinator解决了什么问题。