Y组合器从事物的“功能”方面讲是计算机科学概念。大多数程序员甚至对组合器一无所知。
- 什么是Y组合器?
- 组合器如何工作?
- 它们有什么用?
- 它们在程序语言中有用吗?
Y组合器从事物的“功能”方面讲是计算机科学概念。大多数程序员甚至对组合器一无所知。
Answers:
如果您已准备好长时间阅读,Mike Vanier会提供一个很好的解释。长话短说,它允许您使用不一定本地支持的语言来实现递归。
Y组合器是一种“功能”(在其他功能上运行的功能),当您不能从自身内部引用该功能时,它可以启用递归。在计算机科学理论中,它概括了递归,抽象了其实现,从而将其与所讨论功能的实际工作区分开。递归函数不需要编译时名称的好处是一种好处。=)
这适用于支持lambda函数的语言。该表达的lambda表达式为基础的性质,通常意味着他们可以不上名字称呼自己。通过声明该变量,对其进行引用,然后为该变量分配lambda来完成自引用循环,可以解决此问题。可以复制lambda变量,然后重新分配原始变量,这会破坏自引用。
Y组合器在静态类型的语言(通常是过程语言)中难以实现,而且经常使用,因为通常类型限制要求在编译时就知道该函数的参数数量。这意味着必须为需要使用的任何参数计数编写一个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 作为参数传递,仅在必要时在下一次迭代中调用。
fix :: (a -> a) -> a
,并且a
can可以是任意数量的参数函数。这意味着静态类型并不会真正使您麻烦。
我已经从http://www.mail-archive.com/boston-pm@mail.pm.org/msg02716.html取消了此操作,这是我几年前写的一个解释。
在此示例中,我将使用JavaScript,但是许多其他语言也可以使用。
我们的目标是能够仅使用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);
...保持完全相同。(此技巧在其发明者之后被称为“ currying”。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个递归定义阶乘的行替换为所需的任何其他递归函数。
function (n) { return builder(builder)(n);}
而不是builder(builder)
?
我不知道尝试从头开始构建它是否有用。让我们来看看。这是一个基本的递归阶乘函数:
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);
};
});
recurser
。丝毫不知道它在做什么,或者为什么。
recurser
函数是朝着这个目标迈出的第一步,因为它为我们提供了一个递归版本,fact
该版本从不引用名称。
function Y(recurse) { return recurse(recurse); } let factorial = Y(creator => value => { return value == 0 ? 1 : value * creator(creator)(value - 1); });
?这就是我的消化方式(不确定它是否正确):通过不显式引用该函数(不允许作为组合器),我们可以使用两个部分应用/咖喱函数(一个创建函数和一个calculate函数),我们可以创建无需递归计算函数名称的实现递归的lambda /匿名函数?
y-combinator在JavaScript中:
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组合器”。现在看一下var factorial
使用Y 的那一行。请注意,您将具有参数的函数(在此示例中为recurse
)传递给它,该函数稍后还将在内部函数中使用。参数名称基本上成为内部函数的名称,从而允许它执行递归调用(因为它recurse()
在其定义中使用。)y组合器执行了将否则为匿名的内部函数与传递给该函数的参数名称相关联的魔力是的
有关Y如何做魔术的完整解释,请查看链接文章(不是我本人)。
arguments.callee
在严格模式下是不允许的:developer.mozilla.org/en/JavaScript/...
(function fact(n){ return n <= 1? 1 : n * fact(n-1); })(5)
对于尚未深入了解函数式编程并且不关心立即开始但又有些好奇的程序员:
Y组合器是一个公式,可让您在函数没有名称但可以作为参数传递,用作返回值以及在其他函数中定义的情况下实现递归。
它通过将函数作为参数传递给自身来工作,因此可以调用自身。
它是lambda微积分的一部分,它实际上是数学,但实际上是一种编程语言,并且对于计算机科学,尤其是函数式编程非常重要。
Y组合器的日常实用价值受到限制,因为编程语言倾向于让您命名函数。
如果您需要在警察队伍中识别它,它看起来像这样:
Y =λf。(λx.f(xx))(λx.f(xx))
通常,您会因为重复而发现它(λx.f (x x))
。
这些λ
符号是希腊字母lambda,它使lambda微积分具有其名称,并且有很多(λx.t)
样式术语,因为这就是lambda微积分的外观。
U x = x x
,Y = U . (. U)
(使用类似Haskell的表示法)。IOW,带有适当的组合器,Y = BU(CBU)
。因此,Yf = U (f . U) = (f . U) (f . U) = f (U (f . U)) = f ((f . U) (f . U))
。
定点组合器是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减少规则
(λ x. B) E -> B[x := E] Beta
λ x. E x -> E if x doesn’t occur free in E Eta
Beta减少将表达式(“参数”)替换x
为抽象(“函数”)主体中变量的所有自由出现。减少Eta消除了多余的抽象。有时从形式主义中将其省略。不能归约规则不适用的不可归约表达式为正则或规范形式。B
E
λ x y. E
是的简写
λ x. λ y. E
(抽象多元性),
E F G
是的简写
(E F) G
(应用程序左关联),
λ x. x
和
λ y. y
是与alpha等效的。
抽象和应用是lambda演算的仅有的两个“语言原语”,但是它们允许对任意复杂的数据和操作进行编码。
教堂数字是自然数的编码,类似于Peano-axiomatic自然数。
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
这样的组合器是像SKI系统一样的组合器结石的原始运算符。
S = λ x y z. x z (y z)
K = λ x y. x
I = λ x. x
Beta的减少不是很正常的;并非所有可还原的表达式“ redexes”在beta还原下都收敛为正常形式。一个简单的例子是omega ω
组合器的不同应用
λ x. x x
对自己:
(λ x. x x) (λ y. y y)
= (λ y. y y) (λ y. y y)
. . .
= _|_ Bottom
优先减少最左边的子表达式(“ heads”)。应用顺序在替换之前将参数标准化,而正常顺序则不会。这两种策略类似于渴望评估(例如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 _|_ = _|_
但收敛于懒惰的正序beta减少
= (λ l. ((λ i. i) a)) ((λ x. x x) (λ y. y y))
= (λ l. a) ((λ x. x x) (λ y. y y))
= a
如果表达式具有正常形式,则可以找到正常顺序的beta减少形式。
定点组合器的基本属性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))
. . .
正常顺序的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])
λ x . x
,您今天好吗?
其他答案对此提供了非常简洁的答案,没有一个重要的事实:您不需要以任何复杂的方式以任何实际语言实现定点组合器,并且这样做没有任何实际目的(“看,我知道什么是Y组合器”是”)。它是重要的理论概念,但实用价值很小。
这是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);
我已经在Clojure和Scheme中为Y-Combinator编写了一种“白痴指南”,以帮助自己掌握它。他们受到“小策划者”中材料的影响
在计划中:https : //gist.github.com/z5h/238891
或Clojure:https: //gist.github.com/z5h/5102747
这两篇教程的代码都散布着注释,应该剪切并粘贴到您喜欢的编辑器中。
作为新手,我发现了Mike Vanier的文章(感谢Nicholas Mancuso)确实很有帮助。除了记录我的理解之外,我还想写一个摘要,如果对其他人有帮助,我将非常高兴。
以阶乘为例,我们使用以下almost-factorial
函数来计算数字的阶乘x
:
def almost-factorial f x = if iszero x
then 1
else * x (f (- x 1))
在上面的伪代码中,almost-factorial
采用函数f
和数字x
(almost-factorial
经过咖喱处理,因此可以将其视为采用函数f
并返回1-arity函数)。
在almost-factorial
计算阶乘时x
,它将阶乘的计算委托x - 1
给函数f
并累加结果x
(在这种情况下,它将(x-1)的结果与x相乘)。
可以看成是阶乘函数almost-factorial
的cr脚版本(只能计算到number x - 1
),并返回阶乘函数的less脚版本(计算到number x
)。以此形式:
almost-factorial crappy-f = less-crappy-f
如果我们反复将不那么糟糕的阶乘传递给almost-factorial
,最终将获得所需的阶乘函数f
。可以认为是:
almost-factorial f = f
这事实上almost-factorial f = f
意味着f
是定点的功能almost-factorial
。
这是查看上面函数之间关系的一种非常有趣的方式,对我来说这是一个愚蠢的时刻。(如果没有,请阅读Mike关于定点的文章)
概括地说,我们有一个非递归函数fn
(如我们的几乎阶乘),我们有它的定点函数fr
(如我们的f),那么Y
当您给出时Y
fn
,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
(不包括)我将跳过的推导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)
因此,的确(Y f)
是的固定点f
。
(Y f)
起作用?取决于的签名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
“ wrap” 进行计算时,都会“产生”(“ lifts”?)并将计算委托给它(自身和);另一方面,取决于并用于计算较小问题的结果。fn
fr
x
fn
fn
fr
x
fn
fr
fr
x-1
fr
用于定义时fn
(在其操作中fn
使用fr
时),实数fr
尚未定义。fn
定义了真正的业务逻辑。根据fn
,Y
产生fr
-在一个特定形式的辅助函数-便于计算为fn
在递归方式。目前,它帮助我理解了Y
这种方式,希望对您有所帮助。
顺便说一句,我还发现《通过Lambda微积分进行函数式编程入门》一书非常好,我只是其中一部分,而我无法理解Y
这本书的事实促使我撰写了这篇文章。
以下是原始问题的答案,这些答案是根据尼古拉斯·曼库索(Nicholas Mancuso)的答案中提到的文章(总共值得一读)汇编而成 的,以及其他答案:
什么是Y组合器?
Y组合器是一个“函数”(或一个高阶函数-在其他函数上运行的函数),它带有一个参数(该函数不是递归的),并返回该函数的一个版本,即递归的。
有点递归=),但更深入的定义:
组合器-只是没有自由变量的lambda表达式。
自由变量-是不是绑定变量的变量。
绑定变量-包含在以该变量名作为其自变量之一的lambda表达式内的变量。
考虑这种情况的另一种方法是,combinator是这样的lambda表达式,您可以在其中找到的位置替换其定义的combinator的名称,并使一切仍然有效(如果combinator可能会陷入无限循环在lambda体内包含对自身的引用)。
Y组合器是定点组合器。
函数的不动点是函数域的元素,该域由函数映射到自身。
也就是说,c
是函数的一个固定点f(x)
,如果f(c) = c
这意味着f(f(...f(c)...)) = fn(c) = c
组合器如何工作?
下面的示例假定使用强类型+动态类型:
惰性(正序)Y组合器:
此定义适用于具有惰性(也称为:延迟的,按需调用)评估的语言-一种评估策略,该评估策略会延迟对表达式的评估,直到需要其值为止。
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组合器:
此定义适用于具有严格(也包括:渴望,贪婪)评估的语言-一种评估策略,在该策略中,表达式一旦绑定到变量就立即进行评估。
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的身体评估。我问了另一个问题,与这个话题有些相关。
它们有什么用?
克里斯·安默曼(Chris Ammerman)从答案中借来了Stolen:Y-combinator概括了递归,将其实现抽象化,从而将其与相关功能的实际工作区分开。
尽管Y-combinator有一些实际应用,但它主要是一个理论概念,对它的理解将扩大您的总体视野,并有可能增加您的分析和开发技能。
它们在程序语言中有用吗?
正如Mike Vanier所说:可以用许多静态类型的语言定义Y组合器,但是(至少在我所看到的示例中)这样的定义通常需要一些非显而易见的类型的黑客,因为Y组合器本身不需要具有简单的静态类型。这超出了本文的范围,因此我不再赘述
就像克里斯·阿默曼(Chris Ammerman)提到的那样:大多数程序语言都具有静态类型。
因此,请回答这个问题-并非如此。
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组合器(y组合器将发散/无限循环)。
我认为回答这个问题的最好方法是选择一种语言,例如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解决的问题。