點燈坊

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

自行實作 Chain

Sam Xiao's Avatar 2021-08-16

Monad 必須實現 Functor、Apply、Applicative 與 Chain,其中學習 Chain 是必經過程。

Version

Fantasy Land 5.0.0

Fantasy Land

chain000

Chain 基於 Apply 與 Functor,也就是 Apply 與 Functor 所有特性 Chain 皆具備。

Chain

import { compose, map, ap, chain, inc } from 'ramda'

let Chain = x => {
  let _x = x

  let map = f => Chain (f (_x))

  let ap = x => x.map (f => f (_x))

  let chain = f => f (_x)

  let getValue = _ => _x

  return {
    map,
    ap,
    chain,
    getValue
  }
}

let getValue = x => x.getValue ()

let data = Chain (1)
let inc_ = Chain (inc)
let inc__ = compose (Chain, inc)

compose (getValue, map (inc)) (data) // ?
compose (getValue, ap (data)) (inc_) // ?
compose (getValue, chain (inc__)) (data) // ?

實現 Chain 需滿足 3 個 typeclass:

  • Functor:實現 map 將 Functor 與 function 綁定
  • Apply:實現 ap 將 Apply 與包進 Apply 的 function 綁定
  • Chain:實現 chain 將 Chain 與回傳 Chain 的 function 綁定

第 4 行

let _x = x

相當於 constructor 將 x 值存進 _x 內。

第 6 行

let map = f => Chain (f (_x))

Chain 必須實現 Functor。

根據 Fantasy Land 定義:

fantasy-land/map :: Functor f => f a ~> (a → b) → f a → f b

Object 必須有 map 才是 Functor。

將 Object 內部 _x 透過傳入 f 運算後,使用 Chain 包成新 Chain 回傳。

第 8 行

let ap = x => x.map (f => f (_x))

Chain 必須實現 Apply。

根據 Fantasy Land 定義:

fantasy-land/ap :: Apply f => f a ~> f (a -> b) -> f b

Object 必須有 ap 才是 Apply。

從傳入 Chain 取出 f 後,將 Object 內部 _x 傳入 f 運算後,改變 Chain 回傳。

由於 ap 需呼叫 map 取得 f,因此 Apply 必須先實現 Functor 的 map

10 行

let chain = f => f (_x)

根據 Fantasy Land 定義:

fantasy-land/chain :: Chain m => m a ~> (a -> m b) -> m b

Object 必須有 chain 才是 Chain。

將傳入回傳 Chain 的 function 套用 Object 內部 value 回傳。

13 行

let getValue = _ => _x

使用 getValue 取出 Chain 內部 value。

getValue 並非 Fantasy Land 所要求,只是方便取出 Object 內部 value

22 行

let getValue = x => x.getValue ()

提供 free function 方便使用:

  • getValue:呼叫 Chain 的 getValue

24 行

let data = Chain (1)
let inc_ = Chain (inc)
let inc__ = compose (Chain, inc)
  • data 為 Chain
  • inc_:將 inc 包進 Chain
  • inc__inc 回傳 Chain

28 行

compose (getValue, map (inc)) (data) // ?
compose (getValue, ap (data)) (inc_) // ?
compose (getValue, chain (inc__)) (data) // ?
  • 使用 Ramda 的 map 將 Chain 與 inc 綁定,確認自行建立的 Chain 符合 Fantasy Land 規格
  • 使用 Ramda 的 apApply 與包進 Chain 的 function 綁定,確認自行建立的 Chain 符合 Fantasy Land 規格
  • 使用 Ramda 的 chain 將 Chain 與回傳 Chain 的 function 綁定,確認自行建立的 Chain 符合 Fantasy Land 規格

chain001

Conclusion

  • Chain 必須實現 Apply 與 Functor,所以也必須實現 apmap

Reference

Fantasy Land, Chain