三載前,吾嘗書 Javascript 中 Y 組合子的推導 。前日,夜半欲眠,突思此物。試無憑而推,竟得。javascript
第二天,觀前文,覺冗長晦澀,故做此文。java
爲了解決匿名函數的調用問題
寫出一個遞歸函數很簡單,以階乘函數爲例: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 不是惰性求值的語言,以前的等價代換不徹底正確,
對於 f
和 x => 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
成功。