Y 组合子是怎么推导出来的
如果你学过一些函数式,或者了解过一点 lambda 演算(没了解过也可以继续往下读),你应该知道 for while 之类的循环总是以递归的形式出现,而它的诀窍在于一个叫 \(Y\) 组合子的东西。
但是,每当看到 \(Y\) 组合子那复杂的形式,你就算能理解怎么用它,估计也很难想象出它是怎么被推导出来的。
这篇文章就提供了一个更加具象的推导的思路。
推导
总之让我们先别去思考什么演算什么子了,来考虑一下现实的东西。
斐波那契数列是递归,用普通编程语言你一般可以写成
card(dad_yc)
card(JS+λ)
const feb = n => n <= 2 ? 1 : feb(n - 1) + feb(n - 2);card(JS)
function feb(n) { if (n <= 2) { return 1; } else { return feb(n - 1) + feb(n - 2); } }card(Python+λ)
feb = lambda n: ( 1 if n <= 2 else feb(n - 1) + feb(n - 2) )card(Python)
def feb(n): if n <= 2: return 1 else: return feb(n - 1) + feb(n - 2)
可以发现,我们通过 feb 这个名字,在函数内部拿到了函数自己。
现在给你一个挑战:如果 feb 不知道自己叫 feb ,怎么样才能在它的内部拿到自己并调用?
一个很好的思路是在调用自己的时候把自己也作为一个参数传进去,这样下一次的自己就可以调用自己了。
至于第一次调用自己的时候怎么办?再套个函数包装一下就行。
card(dad_yc)
card(JS+λ)
const febWithSelf = (self, n) => n <= 2 ? 1 : self(self, n - 1) + self(self, n - 2); const feb = n => febWithSelf(febWithSelf, n);card(JS)
function febWithSelf(self, n) { if (n <= 2) { return 1; } else { return self(self, n - 1) + self(self, n - 2); } } function feb(n) { return febWithSelf(febWithSelf, n); }card(Python+λ)
feb_with_self = lambda self, n: ( 1 if n <= 2 else self(self, n - 1) + self(self, n - 2) ) feb = lambda n: feb_with_self(feb_with_self, n)card(Python)
def feb_with_self(self, n): if n <= 2: return 1 else: return self(self, n - 1) + self(self, n - 2) def feb(n): return feb_with_self(feb_with_self, n)
如果你学过什么叫做柯里化,那你应该知道 f(a, b) 和 f(a)(b) 是一个意思——如果你没学过,那现在我告诉你了。所以我们可以改写程序为
card(dad_yc)
card(JS+λ)
const febWithSelf = self => n => n <= 2 ? 1 : self(self)(n - 1) + self(self)(n - 2); const feb = febWithSelf(febWithSelf);card(JS)
function febWithSelf(self) { return function (n) { if (n <= 2) { return 1; } else { return self(self)(n - 1) + self(self)(n - 2); } } } const feb = febWithSelf(febWithSelf);card(Python+λ)
feb_with_self = lambda self: lambda n: ( 1 if n <= 2 else self(self)(n - 1) + self(self)(n - 2) ) feb = feb_with_self(feb_with_self)card(Python)
def feb_with_self(self): def do_feb(n): if n <= 2: return 1 else: return self(self)(n - 1) + self(self)(n - 2) return do_feb feb = feb_with_self(feb_with_self)
发现一堆 self(self) 太冗长了有没有,柯里化之后我们就可以把 self(self) 变成 f 。
card(dad_yc)
card(JS+λ)
const febOnly = f => n => n <= 2 ? 1 : f(n - 1) + f(n - 2); const febWithSelf = self => n => febOnly(self(self))(n); const feb = febWithSelf(febWithSelf);card(JS)
function febOnly(f) { return function (n) { if (n <= 2) { return 1; } else { return f(n - 1) + f(n - 2); } } } function febWithSelf(self) { return function (n) { return febOnly(self(self))(n); } } const feb = febWithSelf(febWithSelf);card(Python+λ)
feb_only = lambda f: lambda n: ( 1 if n <= 2 else f(n - 1) + f(n - 2) ) feb_with_self = lambda self: lambda n: ( feb_only(self(self))(n) ) feb = feb_with_self(feb_with_self)card(Python)
def feb_only(f): def calc(n): if n <= 2: return 1 else: return f(n - 1) + f(n - 2) return calc def feb_with_self(self): def do_feb(n): return feb_only(self(self))(n) return do_feb feb = feb_with_self(feb_with_self)
不错,你会发现 febOnly 被提取出来了,那也就是说诸如 factorialOnly hanoiOnly 之类各种递归函数也都可以用这种方法?我们下一遍可以来把 febOnly 抽成参数,先来在 feb 里展开 febWithSelf 的定义,
card(dad_yc)
card(JS+λ)
const febOnly = f => n => n <= 2 ? 1 : f(n - 1) + f(n - 2); const feb = (self => n => febOnly(self(self))(n)) (self => n => febOnly(self(self))(n));card(JS)
function febOnly(f) { return function (n) { if (n <= 2) { return 1; } else { return f(n - 1) + f(n - 2); } } } const feb = (function (self) { return function (n) { return febOnly(self(self))(n); } })(function (self) { return function (n) { return febOnly(self(self))(n); } });card(Python+λ)
feb_only = lambda f: lambda n: ( 1 if n <= 2 else f(n - 1) + f(n - 2) ) feb = ( lambda self: lambda n: ( feb_only(self(self))(n) ) )( lambda self: lambda n: ( feb_only(self(self))(n) ) )
这样子就方便把 febOnly 提取为参数了:
card(dad_yc)
card(JS+λ)
const febOnly = f => n => n <= 2 ? 1 : f(n - 1) + f(n - 2); const yCombinator = someOnly => (self => n => someOnly(self(self))(n)) (self => n => someOnly(self(self))(n)); const feb = yCombinator(febOnly);card(JS)
function febOnly(f) { return function (n) { if (n <= 2) { return 1; } else { return f(n - 1) + f(n - 2); } } } function yCombinator(someOnly) { return (function (self) { return function (n) { return someOnly(self(self))(n); } })(function (self) { return function (n) { return someOnly(self(self))(n); } }); } const feb = yCombinator(febOnly);card(Python+λ)
feb_only = lambda f: lambda n: ( 1 if n <= 2 else f(n - 1) + f(n - 2) ) y_combinator = lambda some_only: ( lambda self: lambda n: ( some_only(self(self))(n) ) )( lambda self: lambda n: ( some_only(self(self))(n) ) ) feb = y_combinator(feb_only)card(Python)
这里 Python 可以略过上一步直接提取。
def feb_only(f): def calc(n): if n <= 2: return 1 else: return f(n - 1) + f(n - 2) return calc def y_combinator(some_only): def some_with_self(self): def do_some(n): return some_only(self(self))(n) return do_some return some_with_self(some_with_self) feb = y_combinator(feb_only)
没错,到这我们就找到了这个 yCombinator ,大名鼎鼎的 \(Y\) 组合子!
仔细观察上面的 yCombinator ,可以发现你给它传进去任何一个函数 someOnly ,这 someOnly 的第一个参数都能拿到它自己!没错,这就叫不动点了。
我们推导出来的在 lambda 演算里写作:
好吧其实这是 \(Z\) 组合子。我们还可以把 \(\lambda n.(f\ (s\ s)\ n)\) 柯里化为 \((f\ (s\ s))\) ,意思都差不多。最终得到的这个才是 \(Y\) 组合子
但是在急切求值语言(也就是大部分语言)中,柯里化后得到的 \(Y\) 组合子太急切了,会导致栈溢出,所以在急切求值语言中只能用 \(Z\) 组合子。
但是作为一个强迫症,我不想用这么寒碜的 \(Z\) 组合子。所以我发明了用 JS 实现 lambda 演算的库,其中包含了一个真正惰性求值的 \(Y\) 组合子 yC ,感兴趣你可以去用用。
希望这篇文章能给你一个新的视角来看 \(Y\) 组合子。
博客园原文链接:https://www.cnblogs.com/QiFande/p/19715362,转载请注明。
如果你对本篇文章感兴趣,不如来看看肉丁土豆表的其他文章,说不定也有你喜欢的。
浙公网安备 33010602011771号