在 API 實作過程中,我們都是先把 function 的型態定義好,然後在隨著型態去實現它,例如 map
就是用了 map2
和 unit
來實現,這些是自然的演變,也可以說是我們簡化了代數方程式。我們讓 API 有了代數性質,或者說當我們有了基礎定律後,然後我們照著這個代數性的遊戲規則做一連串操作;
就像所有設計選擇,定律選擇都有後果,它限制了操作的意義以及何種實現可行,讓我們用一個例子看一下 mapping 的定律。
首先我們創造一個看起來合理的定律如下(定律通常都是從具體的方程式範例而來),
map(unit(1))(_ + 1) == unit(2)
unit(1)
和 _ + 1
與 unit(2)
相等,以 Par 來說,就是 Future 裡的值相等,
然後定律和 function 有許多共同點,就像我們一般化 (抽象) function 那樣,我們也可以一般化定律,
map(unit(x))(f) == unit(f(x))
這裡不只限定了 1 和 _ + 1
,我們可以用任何的 f 來讓等號 2 邊相等,接下來我們可以繼續簡化定律,若我們將 f 替換成 function id 的話,
def id[A](a: A): A = a
我們可以簡化方程式 2 邊的表達式然後最後得到新的定律,
map(unit(x))(id) == unit(id(x))
map(unit(x))(id) == unit(x) // 簡化
map(y)(id) == y // 用 y 將 2 邊的 unit(x) 替換
我們新的定律現在不需要 unit 這個 function 了。
邏輯上來看,我們能自由做這些轉換,是因為 map 不可能對其接收的 function type 不同而有不同行為,因此,當給定 map(y)(id) = y
時,它等同於 map(unit(x))(id) == unit(id(x))
,
這個新的第二個定律通常也被稱為 free theorem。
這幾天我們設計了平行化計算和非同步 library,重點是透過設計 API,來了解遭遇問題時該怎麼以 purely functional 的設計思維去解決,我們使用容器型態 Par 來做為平行化計算的依據,將 執行(run) 這個真正初始化執行緒的動作分離出來,最後設計出核心 API 來使 Par 能自由組合、操作。