是的!不是!

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) 傳回的函式叫作 yesyes(a)(b) 無論如何一定傳回 a,如果 c 不成立,when(c) 傳回的函式叫作 nono(a)(b) 一定傳回 b,那麼就會是想要的結果,於是先把 yesno 定義下來:

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 + 23 + 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 + 2false ? (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) 傳回 no1 + 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 最後不用加上 ()

無論如何,既然已經有了 yesno,也有了 when,應該會想試試看,用它們來取代 truefalse?: 吧!那就下一篇再來談了… XD