Y组合子(Y combinator)与匿名Lambda
Y组合子(Y combinator)与匿名 Lambda
Y组合子是函数编程的理论基础,lambda 演算的一部分。它的作用就是把匿名 lambda 函数自身给计算出来。
在介绍组合子之前需要先介绍不动点:不动点(fixed point)是指函数的某种输入和函数本身相等,也就是 f(x) 等于 x 。当然,继续之前你还得了解 first class function 中的高阶函数和柯里化(currying)的概念。
现在,尝试使用前面设计的语言来做一个例子解释 Y 组合子的用途,该语言中名字只有在定义完成后才可见,也就是定义函数时无法知道自己的名字,这样就导致了无法进行递归。那么如何在这门语言中使用递归呢?
所谓 Y 组合子即一个 Y 函数,它用于计算高阶函数的不动点。假设有函数 f(x) 和高阶函数 g(x),我们用 t 来表示 g(x) 的不动点。那么就有 g(Y(g)) = Y(g)
等价于 g(t) = t
,其中 Y(g) 得到的是 g(x) 的不动点。
下面,我们来计算 Y 的形式,定义斐波拉契函数如下:
define f = function(fib) {
return function(n) {
if (n <= 2) return 1;
return fib(n-1) + fib(n-2);
};
};
现在,只需要将 fib
函数传递给 f
就能得到 fib
…显然这种办法是行不通的。我们进行如下改写:
define fib = function(h, x) {
if (x <= 2) return 1;
return h(h, x-1) + h(h, x-2);
};
fib(fib, 10);
虽然实现了递归,但是这种办法没有那么优美。我希望能够像其他语言一样,使用 fib(10)
进行调用。现在将函数柯里化:
define fib = function(h) {
return function(x) {
if (x <= 2) return 1;
return h(h)(x-1) + h(h)(x-2);
};
};
fib(fib)(10);
这样的方式仍然不够好,我们进一步将内部的 h(h)
部分改为 fib(x)
:
define fib = function(h) {
return function(x) {
let f = function(fib) {
if (x <= 2) return 1;
return fib(x-1) + fib(x-2);
};
return f(h(h));
};
};
fib(fib)(10);
现在发现其中的 f
定义的部分与最开始的代码相似,改写如下:
define fib = function(h) {
return function(x) {
let f = function(fib) {
return function(n) {
if (n <= 2) return 1;
return fib(n-1) + fib(n-2);
};
};
return f(h(h))(x);
};
};
fib(fib)(10);
然后将 f
部分提取出来:
define f = function(fib) {
return function(n) {
if (n <= 2) return 1;
return fib(n-1) + fib(n-2);
};
};
define fib = function(h) {
return function(x) {
return f(h(h))(x);
};
};
fib(fib)(10);
这里发现,利用柯里化,就能得到 Y 组合子,现在对其进行包装:
define f = function(fib) {
return function(n) {
if (n <= 2) return 1;
return fib(n-1) + fib(n-2);
};
};
define Y = function(f) {
let warp = function(h) {
return function(x) {
return f(h(h))(x);
};
};
return warp(warp);
};
define fib = Y(f);
fib(10);
现在回头看 Y 组合子的定义,Y(f) 就得到了 f 的不动点。那么现在就可以很友好的得到 fib
函数:
define Y = function(f) {
let warp = function(h) {
return function(x) {
return f(h(h))(x);
};
};
return warp(warp);
};
define fib = Y(function(fib) {
return function(n) {
if (n <= 2) return 1;
return fib(n-1) + fib(n-2);
};
});
fib(10);
现在,当然这样的 Y 函数依然有限制,不过已经实现了预期的需求:匿名递归 lambda。