Javascript 中 Y 组合子的【再】推导

三载前,吾尝书 Javascript 中 Y 组合子的推导 。前日,夜半欲眠,突思此物。试无凭而推,竟得。javascript

第二天,观前文,觉冗长晦涩,故做此文。java

Y 组合子的目的

为了解决匿名函数的调用问题

写出一个递归函数很简单,以阶乘函数为例:segmentfault

let F = x => x ? x * F(x-1) : 1;
F(5) //120

但对于匿名函数,没有变量赋值的状况下,如何解决上面的问题?
这就用到 Y 组合子。函数

推导

变量与参数的概念其实很是类似,咱们能够用参数,替代变量赋值code

(f => x => x ? x * f(x-1) : 1)

咱们只要给 f 这个参数传这个函数自身,就达到了目的,首先尝试把函数直接复制一份做为参数传入:递归

(f => x => x ? x * f(x-1) : 1)(f => x => x ? x * f(x-1) : 1)

容易发现,若是前半部分中的 f 是后面这个函数:ip

(f => x => x ? x * f(x-1) : 1)(f => x => x ? x * f(x-1) : 1)
//                            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

那么 f 接收的参数应该是它自身,像上面 f(x-1) 这样的调用方式是错误的。
咱们把上面的表达式改造一下:get

(f => x => x ? x * f(f)(x-1) : 1)(f => x => x ? x * f(f)(x-1) : 1)
//                 ^^^^                             ^^^^

上面就是咱们的基本形式,能够开始化简了。class

化简

首先左右两大块是如出一辙的,能够用一个变量代替:匿名函数

let a = f => x => x ? x * f(f)(x-1) : 1;
a(a);

还记得上面咱们说的变量赋值 ↔ 参数的关系吗?咱们将它改造为函数:

(a => a(a))(f => x => x ? x * f(f)(x-1) : 1)

咱们的目标是分离出下面的部分:

(f => x => x ? x * f(x-1) : 1)

观察原式,只须要把下面的 f(f) 替换为 f 就能很方便的提取最后的结果了

(a => a(a))(f => x => x ? x * f(f)(x-1) : 1)
//                            ^^^^

易知,对于函数 f, g (咱们这里讨论的全是单参数函数)

1) f 等价于 x => f(x)
2) (x => f(g(x)))(x) 等价于 (x => f(x))(g(x))

运用 1)

(a => a(a))(b => (f => x => x ? x * f(f)(x-1) : 1)(b))
//         ^^^^^                                  ^^^

运用 2)

(a => a(a))(b => (f => x => x ? x * f(x-1) : 1)(b(b)))
//         ^^^^^                    ^          ^^^^^

运用 1)2)

(c => (a => a(a))(b => c(b(b))))(f => x => x ? x * f(x-1) : 1)
//^^^                  ^        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

也就是说,前半部分就是咱们要找的 Y 组合子,即

Y = c => (a => a(a))(b => c(b(b)))

重命名参数

Y = f => (x => x(x))(x => f(x(x)))

尝试调用

(f => (x => x(x))(x => f(x(x))))(f => x => x ? x * f(x - 1) : 1)(5)

然而,当咱们用上面的式子调用时会爆栈:Maximum call stack size exceeded

惰性求值

下面来解决爆栈问题

还记得

f => (x => x(x))(x => f(x(x)))
//                      ^^^^

是怎么来的吗?是咱们从递归中 ?: 部分中抽离出来的,是递归条件为真的状况。递归之因此能结束,是由于函数有终止条件。而如今咱们把 x(x) 抽离出来,至关于把递归移到了判断以前,在没有终止条件的状况下自身调用自身,最终形成了爆栈。

(a => a(a))(b => (f => x => x ? x * f(f)(x-1) : 1)(b))
//                          ^^  ^^^^^^^^^^^^^   ^
//                          判断    递归       返回

(a => a(a))(b => (f => x => x ? x * f(x-1) : 1)(b(b)))
//                                             ^^^^^
//                                             递归

其实由于 js 不是惰性求值的语言,以前的等价代换不彻底正确,
对于 fx => f(x) 它们不是彻底等价,区别是 f 自己若是是表达式,当以 f 的形式出现时,表达式会当即计算值,而若是以 x => f(x) 的形式出现,只有调用时才会求 f 的值。

运用 1)懒求值特性,把

Y = f => (x => x(x))(x => f(x(x)))

改造为:

Y = f => (x => x(x))(x => f(y => x(x)(y)))
//                          ^^^^^    ^^^

这样,就获得了在 js 中可用的 Y 组合子。

Y = f => (x => x(x))(x => f(y => x(x)(y)))

尝试调用

Y(f => x => x ? x * f(x-1) : 1)(5)
// 120

成功。

相关文章
相关标签/搜索