Monad 一直是學習 Functional Programming 的一大關卡,事實上天天在用的 Maybe、Either 與 Future 都是 Monad,而 Promise 則是很像 Monad 但又不是真正 Monad。
Version
Fantasy Land 5.0.0
Overview
簡單來說,Monad 是一種特殊的 Object,能將 value 包進 Object 內,且該 Object 具備一些特殊 method,最後須從 Object 內取出 value。
Fantasy Land
根據 Fantasy Land 定義,必須實現 Functor、Apply、Applicative 與 Chain 四個 typeclass,才能算完整 Monad。
Functor
map
Functor f => (a -> b) -> f a -> f b
將 Functor 與 一般 function 綁定改變 Functor
- 根據 Fantasy Land 定義,支援
map
的 Object 就是 Functor,這也是為什麼 Sanctuary 對於map
的 type signature 會出現Functor
原因,算是最基本的 typeclass - 對於 Monad 來說,必須也實現
map
支援 Functor,這表示所有 typeclass 為 Functor 的 function 都適用於 Monad map
使我們能將 Functor 與一般 function 綁定改變 Functor
Apply
ap
Apply f => f (a -> b) -> f a -> f b
將 Apply 與包進 Apply 的 function 綁定改變 Apply
- 根據 Fantasy Land 定義,支援
ap
的 Object 就是 Apply,這也是為什麼在 Sanctuary 對於ap
的 type signature 會出現Apply
原因 - Apply 還必須支援 Functor
- 對於 Monad 來說,必須也實現
ap
支援 Apply,這表示所有 typeclass 為 Apply 的 function 都適用於 Monad ap
使我們能將 Apply 與包進 Apply 的 function 綁定改變 Apply
Haskell 稱支援
ap
的 Object 就是 Applicative;而 Fantasy Land 稱為 Apply
Applicative
of
Applicative f => TypeRep f -> a -> f a
將 value 包進 Applicative
- 根據 Fantasy Land 定義,支援
of
的 Object 就是 Applicative,這也是為什麼 Sanctuary 對於of
的 type signature 會出現Applicative
原因 - Applicative 還必須支援 Functor 與 Apply
- 對於 Monad 來說,必須也實現
of
支援 Applicative,這表示所有 typeclass 為 Applicative 的 function 都適用於 Monad of
使我們能將 value 包進 Applicative
Haskell 稱為
return
;而 Fantasy Land 稱為of
Chain
chain
Chain m => (a -> m b) -> m a -> m b
將 Chain 與回傳 Chain 的 function 綁定改變 Chain
- 根據 Fantasy Land 定義,支援
chain
的 Object 就是 Chain,這也是為什麼 Sanctuary 對於chain
的 type signature 會出現Chain
原因 - Chain 還必須支援 Functor 與 Apply
- 對於 Monad 來說,必須也實現
chain
支援 Chain,這表示所有 typeclass 為 Chain 的 function 都適用於 Monad chain
使我們能將 Chain 與回傳 Chain 的 function 綁定改變 Chain
Maybe、Either、Future
Maybe、Either 與 Future 是 FP 中典型的三大 Monad:
- Maybe:FP 的
null
與undefined
取代方案 - Either:FP 的 Exception 取代方案
- Future:FP 的 Promise 取代方案
Maybe、Either 與 Future 都是 Monad,因此採用相同處理方式,其最重要的哲學是:
將所有運算在 Monad 內處理,直到最後再從 Monad 內取出 value
這樣的優點是:
- Maybe:在 Maybe 內不用特別處理
null
與undefined
,可維持 happy path coding,直到最後再從 Maybe 內取出 value 時再處理 Nothing 即可 - Either:在 Either 內不用特別處理 Exception,可維持 happy path coding,直到最後再從 Either 內取出 value 時再處理 Left 即可
- Future:在 Future 內不用特別處理 Rejected,可為持 happy path coding,直到最後再從 Future 內取出 value 時再處理 Rejected 即可
也由於都在 Monad 內運算,這就面臨了該如何改變 Monad 內部 value 挑戰:
map
:將 function 透過map
與 Monad 綁定,可直接改變 Monad 內部 valuechain
:引入 Maybe、Either 與 Future 後,會面臨到另外一個問題,若傳入 function 回傳 Maybe、Either 或 Future 該如何處理:- Maybe:如 Sanctuary 的
head
、find
… 等都是回傳 Maybe - Either:如自行 validation 時會回傳 Either
- Future:如呼叫 API 時會回傳 Future
將回傳 Monad 的 function 透過
chain
與 Monad 綁定,可直接改變 Monad 內部 value,並避免產生兩層 Monad- Maybe:如 Sanctuary 的
Promise
Promise 常被爭議是否算 Monad ?
- Promise 如 Monad 將 value 包在內部,須從內部取出 value
- 可將 function 傳入
then
改變 Promise 內部 value - 可將回傳 Promise 的 function 傳入
then
改變 Promise 內部 value
如此種種,Promise 的確很像 Monad。
但很可惜 Promise 並沒有遵循 Fantasy Land 規範,這使得 Promise 搭配 Sanctuary 時隔隔不入,所有支援 Functor、Apply、Applicative 與 Chain 的 function 也無法套用在 Promise,因此並非真正 Monad。
但 Future 則完全遵循 Fantasy Land 規範,也提供了 Sanctuary 的 type definition,所有支援 Functor、Apply、Applicative 與 Chain 的 function 也都能套用在 Future,因此是真正 Monad。
Monad.test
Primitive
import { Monad } from 'sanctuary-type-classes'
Monad.test (1) // ?
Monad.test ('') // ?
Monad.test (true) // ?
Monad.test ({}) // ?
Monad.test ([]) // ?
Monad.test (() => {}) // ?
Sanctuary 提供了 Monad.test
可判斷 value 是否為 Monad。
- Number、String 與 Boolean 皆非 Monad
- Object 不是 Monad
- Array 與 Function 皆為 Chain
ADT
import { Just, Right, Pair } from 'sanctuary'
import { resolve } from 'fluture'
import { Monad } from 'sanctuary-type-classes'
Monad.test (Pair (1) (2)) // ?
Monad.test (Just (1)) // ?
Monad.test (Right (1)) // ?
Monad.test (resolve (1)) // ?
Monad.test (Promise.resolve (1)) // ?
- Pair 並非 Monad
- Maybe、Either 與 Future 皆為 Monad
- Promise 並非 Monad
Conclusion
- Monad 必須實現 Functor、Apply、Applicative 與 Chain 四個 typeclass,因此 Sanctuary 所有支援 Functor、Apply、Applicative 與 Chain 的 function 都能使用
- Maybe、Either 與 Future 三者都是 Monad,因此用起來觀念完全相同
- Promise 很像 Monad,但因為沒有遵循 Fantasy Land 規格,使得 Sanctuary 很難與 Promise 搭配,建議改用 Future 取代 Promise
- 若不確定 value 是不是 Monad,可使用
Monad.test
判斷
Reference
Adit Bhargava, Functors, Applicatives, And Monads in Pictures
Fantasy Land, Monad