关于Y组合子的来龙去脉,我读过几篇介绍的文章,相比之下,还是王垠大神的著作 最易懂。但他原来所有的语言是scheme,我写一个 JS 版的,来帮助大家理解吧。
我们首先来看看如何实现递归。
lambda演算的语法非常简洁,一言以蔽之:
x | t t | λx.t
其中$x$表示变量,$t \ t$ 表示调用, $\lambda x.t$ 表示函数定义。
首先我们来定义一个阶乘函数,然后调用它。
fact = n => n == 1 ? 1 : n * fact(n-1)
fact(5)
lambda演算中不可以这么简单的定义阶乘函数,是因为它没有 =
赋值符号 。
现在我们看到在lambda定义中,存在fact的名字,如果我们想要无名的调用它,是不行的。如下
(n => n == 1 ? 1 : n * fact(n-1))(5) # there is still `fact` name
我们想要将名字消去,如何消去一个函数的名字呢?
首先,没有名字是无法定义一个递归函数的。
那么,我们不禁要问了,哪里可以对事物命名呢?
对了,将之变为参数,因为参数是可以随意命名的。
fact = (f, n) => n == 1 ? 1 : n * f(f, n-1)
fact(fact, 5)
嗯,很好,看起来不错。不过,要记住在 lambda 演算里面,函数只能有一个参数,所以我们稍微做一下变化。
fact = f => n => n == 1 ? 1 : n * f(f)(n-1)
fact(fact)(5)
你可能会说我在做无用功,别过早下结论,我们只需要将 fact
代入,就得到了完美的匿名函数调用。
(f => n => n == 1 ? 1 : n * f(f)(n-1)) (f => n => n == 1 ? 1 : n * f(f)(n-1)) (5)
看,我们成功了,这一坨代码,是完全可以运行的哦。这个叫做 穷人的Y组合子。可以用,但是不通用,你要针对每个具体函数改造。
于是我们继续改造。我们将把通用的模式提取出来,这个过程叫做 抽象。
首先我们看到了 f(f)
两次, fact(fact)
一次,这种pattern重复了3次,根据 DRY 原则,我们可以这么做
w = f => f(f)
w(fact) (5) # short version
w (f => n => n == 1 ? 1 : n * f(f)(n-1)) (5) # longer version
现在,我们就只有一个重复的模式了,那就是 f(f)
。但是因为它在函数内部(也就是在业务逻辑内部),我们要先把它解耦出来。也就是 factor out。
我们从 f => n => n == 1 ? 1 : n * f(f)(n-1)
开始
f =>
n => n == 1 ? 1 : n * f(f)(n-1)
我们令 g=f(f)
,然后
可以变成
f =>
(g => n => n == 1 ? 1 : n * g(n-1)) ( f(f) )
当然, f(f)
在call by value 时会导致栈溢出,所以我们就 $\eta$ 化一下
f =>
(g => n => n == 1 ? 1 : n * g(n-1)) ( v => f(f)(v) )
我们看到了 g => n => n == 1 ? 1 : n * g(n-1)
这个就是我们梦寐以求的阶乘函数的定义啊。
我们将这个(阶乘函数的定义)提取出来(再一次的factor out),将之命名为 fact0
(更接近本质的fact)。上面的可以改写成。
( fact0 => f =>
fact0 ( v => f(f)(v) )
) ( g => n => n == 1 ? 1 : n * g(n-1) )
不要忘记最初的w,那么如下:
w(
(fact0 => f => fact0 ( v => f(f)(v) ))
(g => n => n == 1 ? 1 : n * g(n-1))
)(5)
很自然我们会再一次把阶乘函数的定义factor out出来,当然,fact0 => f => fact0 ( v=>f(f)(v) )
中的fact0参数我们也会换成其他的名字,比如 s,而那个fact0的实参,那一大坨更加本质的定义我们也会抽象成一个参数,h
(h =>
w( (s => f => s ( v => f(f)(v) )) (h))
)
(g => n => n == 1 ? 1 : n * g(n-1)) (5)
好,大功告成,上面的那个括号里面的就是Y了。我们将之单独拿出来看。
(h =>
w(
(s => f => s ( v => f(f)(v) )) (h)
)
)
最中间一行的 h
可以apply一下,也就是化简:
(h =>
w(
(f => h ( v => f(f)(v) ))
)
)
当然, w这个名字也可以去除
(h =>
(f => h ( v => f(f)(v) ))
(f => h ( v => f(f)(v) ))
)
这就是最后的结果了。
名调用中,可以这么写:
或者使用更经典的形式