點燈坊

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

重新認識 chain()

Sam Xiao's Avatar 2020-12-17

Ramda 的 chain() 是很神奇的 Function,除了視為 flatMap() 外,也可用於 Monad,只要在 Monad 內將值也改為 Monad 時,亦可使用 chain()

Version

Ramda 0.27.1

chain()

chain()
Chain m => (a → m b) → m a → m b

chain() 所支援的 container 並不是 Functor,而是很獨特的 Chain,簡單說 Chain 就是 chainable,也就是支援 chain() 的 container

map()
Functor f => (a → b) → f a → f b

對比 map() 是不是非常類似呢 ?

差異僅在第一個 argument,chain()a -> m b,而 map()a -> b

bind()

在 FP 裡,其實 signature 為 (a → m b) → m a → m b 稱為 bind(),但因為 ECMAScript 已經有 bind(),且 Ramda 也提供 bind(),因此原本 FP 的 bind() 在 Ramda 只好改用 chain()

Monad

根據定義,Monad 必須支援 return()bind()

  • **return()**:a -> m b
  • **bind()**:(a -> m b) -> m a -> m b

可發現 bind()map() 差異是 bind() 直接傳入 return(),而 map() 只傳入 a -> b projection function。

但無論如何,傳入的 f() 都將直接改變 m a 內部資料。

若以 function 角度,傳入 a -> b 使的 m a 變成 m b ,這很容易理解。

但若傳入 a -> m b,理論上應該成為 m m b,但為什麼結果還是 m b 呢 ?

可推論 m a 內部除了 map() 外,還組合了 flatten(),才可能使 m m b 變成 m b

這也是為什麼當 chain() 用在 Array 時相當於 ECMAScript 的 flatMap(),或者相當於 pipe(map, flatten)

從另外一個角度,當 Maybe 內回傳 Maybe 或 Either 內回傳 Either 時,也是使用 chain(),因為 chain() 會將兩層 Maybe 或兩層 Either 攤平。

Conclusion

  • chain() 用於 Array 時俗稱 flatMap(),本文以 map()bind() 的角度,也可推論出 chain() 內部就是 flatMap()
  • chain() 若用於 Monad 則視為 bind(),只要支援 chain() 的 container 就是 Chain
  • 當 Maybe 內回傳 Maybe,或 Either 內回傳 Either 也會使用 chain()

Reference

Ramda, chain()