是的!不是!
December 14, 2021來整理一下至今做過的事情,除了 undefined
之外(下一篇會來討論它),基本上已經將 List 使用箭號函式來構造:
let pair = l => r => f => f(l)(r);
let left = p => p(l => _ => l);
let right = p => p(_ => r => r);
let nil = pair(undefined)(undefined);
let con = h => t => pair(h)(t);
let head = left;
let tail = right;
之前在對 List 操作的時候,使用到了 ?:
、+
、-
、===
等操作,這些操作對應的符號對人來說比較容易理解,然而,也許會想進一步地,有沒有辦法將它們也用箭號函式來表示?
由於一直都是逆向在做這件事,野心先別太大好了,來考慮一下 ?:
,這其實就是開發者熟悉的 if...else
,有的語言將 if...else
當成是運算式,不過 JavaScript 不是,因而在這邊才會使用 ?:
,在指定給 ?
的條件成立時傳回 :
前的值,否則傳回 :
後的值。
定義 when
如果用個箭號函式如何表達 ?:
呢?因為 if
在 JavaScript 中被使用了,那就用 when
吧!如果有個 when(c)(x)(y)
,c
成立時傳回 x
否則傳回 y
,就相當於 ?:
了,那麼 when
該怎麼定義?
let when = c => x => y => c ? x : y;
完全沒有解決問題嘛!還是用上了 ?:
,寫這樣也不對:
let when = c => x => y => when(c)(a)(b);
根本沒有終止條件,無限循環!
定義 yes/no
換個角度想想,想要的是 when(c)(a)(b)
,這表示 when(c)
必須傳回一個函式,如果 c
成立,when(c)
傳回的函式叫作 yes
,yes(a)(b)
無論如何一定傳回 a
,如果 c
不成立,when(c)
傳回的函式叫作 no
,no(a)(b)
一定傳回 b
,那麼就會是想要的結果,於是先把 yes
、no
定義下來:
let yes = x => y => x;
let no = x => y => y;
如果 c
代表成立,when(c)
等價 yes
,若 c
代表不成立,when(c)
等價於 no
,如果用 yes
來代表成立,no
代表不成立呢?when(yes)(a)(b)
可轉換為 yes(a)(b)
,而 when(no)(a)(b)
可轉換為 no(a)(b)
,這表示 when(c)
做的事只有一件…直接將 c
傳回:
let when = c => c;
老實說 when
根本就不做事嘛?多這一層只不過是增加語義,在人類閱讀上,when(yes)(a)(b)
比 yes(a)(b)
清楚一些,而 when(no)(a)(b)
比 no(a)(b)
清楚一些罷了。
無論如何,現在有 when
了,when(yes)(1)(2)
結果會是 1,when(no)(1)(2)
會是 2,對於簡單的運算這夠用了,然而,因為這一路上為了能看看轉換為箭號函式之後是否正確,都一直依賴著 JavaScript 執行環境,也就因此,when(yes)(1 + 2)(3 + 4)
或 when(no)(1 + 2)(3 + 4)
在 JavaScript 環境中執行時,都會依序運算 1 + 2
與 3 + 4
以便套用函式,例如,when(yes)(1 + 2)(3 + 4)
,會成為 yes(1 + 2)(3 + 4)
,接著 yes(3)(3 + 4)
,然後 (y => 3)(3 + 4)
,進一步 (y => 3)(7)
,7 => 3
,最後傳回 3。
簡單來說,(1 + 2)(3 + 4)
一定會運算,然而,true ? (1 + 2) : (3 + 4)
的話,只會運算 1 + 2
,false ? (1 + 2) : (3 + 4)
的話,只會運算 3 + 4
,這表示之前實作的遞迴函式,例如 len
:
let len = lt => isEmpty(lt) ? 0 : 1 + len(tail(lt));
在 isEmpty(lt)
為 true
時,並不會去運算 1 + len(tail(lt))
,因此遞迴會終止;然而,如果將之用 when
來取代:
let len = lt => when(isEmpty(lt))(0)(1 + len(tail(lt)));
就算 isEmpty(lt)
傳回 no
,1 + len(tail(lt))
一定會運算,也就是 len
一定會被呼叫,也就造成了 len
的遞迴永不終止。
惰性求值
如果對於 yes(1 + 2)(3 + 4)
,可以是 (y => 1 + 2)(3 + 4)
、(3 + 4) => (1 + 2)
、1 + 2
,最後才運算 1 + 2
而傳回 3 就好了,這表示 1 + 2、3 + 4 必須能惰性求值,然而 JavaScript 環境沒有這個功能。
然而,若是寫成 yes(_ => 1 + 2)(_ => 3 + 4)()
的話,就會是 (y => (_ => 1 + 2))(_ => 3 + 4)()
,((_ => 3 + 4) => (_ => 1 + 2))()
,(_ => 1 + 2)()
,最後執行函式問題就解決了。
也就是說,如果 len
的定義,可以寫成 lt => when(isEmpty(lt))(_ => 0)(_ => 1 + len(tail(lt)))()
,也就是最後記得 when
最後記得加上 ()
,就可以解決遞迴永不終止的問題,然而,一來最後這個 ()
常會忘了加上,二來這會讓語法看來有點不自然。
既然如此,那就乾脆把 yes
改成:
let yes = x => y => x();
這樣的話,yes(_ => 1 + 2)(_ => 3 + 4)
的轉換會是 (y => (_ => 1 + 2)())(_ => 3 + 4)
、((_ => 3 + 4) => (_ => 1 + 2)())
、(_ => 1 + 2)()
而得到 3。類似地,也就可以將 no
改成:
let no = x => y => y();
若是如此定義之後,在使用 when
時,還是要撰寫 when(yes)(_ => 1 + 2)(_ => 3 + 4)
這樣的形式,不過倒也提醒了 when
具有惰性求值的效果,然而 when
最後不用加上 ()
。
無論如何,既然已經有了 yes
、no
,也有了 when
,應該會想試試看,用它們來取代 true
、false
、?:
吧!那就下一篇再來談了… XD