點燈坊

失くすものさえない今が強くなるチャンスよ

深入淺出 Monad

Sam Xiao's Avatar 2021-07-11

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

monad000

根據 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 的 nullundefined 取代方案
  • Either:FP 的 Exception 取代方案
  • Future:FP 的 Promise 取代方案

Maybe、Either 與 Future 都是 Monad,因此採用相同處理方式,其最重要的哲學是:

將所有運算在 Monad 內處理,直到最後再從 Monad 內取出 value

這樣的優點是:

  • Maybe:在 Maybe 內不用特別處理 nullundefined,可維持 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 內部 value

  • chain:引入 Maybe、Either 與 Future 後,會面臨到另外一個問題,若傳入 function 回傳 Maybe、Either 或 Future 該如何處理:

    • Maybe:如 Sanctuary 的 headfind … 等都是回傳 Maybe
    • Either:如自行 validation 時會回傳 Either
    • Future:如呼叫 API 時會回傳 Future

    將回傳 Monad 的 function 透過 chain 與 Monad 綁定,可直接改變 Monad 內部 value,並避免產生兩層 Monad

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

monad001

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

monad002

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