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

成功。

相關文章
相關標籤/搜索