點燈坊

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

自行實作 Either

Sam Xiao's Avatar 2021-08-19

Either 也是看似很玄的概念,讓我們不用處理 Exception,且可使用 mapchain 直接改變 Either 內部 Value,其實只要 20 行左右就可自行實作 Either,讓我們對 Either 更加了解。

Version

Fantasy Land 5.0.0

Either

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

let Either = x => {
  let _x = x

  let map = f => (_x.dir === 'Left') ? Either (_x) : Either ({ value: f (_x.value), dir: 'Right' })

  let ap = x => (_x.dir === 'Left') ? Either (_x) : x.map (f => f (_x.value))

  let chain = f => (_x.dir === 'Left') ? Either (_x) : f (_x.value)

  let getValue = x => _x.value

  return {
    map,
    ap,
    chain,
    getValue
  }
}

let of_ = x => Either ({ value: x, dir: 'Right'})
let getValue = x => x.getValue ()

let Right = x => Either ({ value: x, dir: 'Right' })
let Left = x => Either ({ value: x, dir: 'Left' })

let inc_ = of_ (inc)
let inc__ = compose (of_, inc)

let data0 = Right (1)
compose (getValue, map (inc)) (data0) // ?
compose (getValue, ap (data0)) (inc_) // ?
compose (getValue, chain (inc__)) (data0) // ?

let data1 = Left ('error')
compose (getValue, map (inc)) (data1) // ?
compose (getValue, ap (data1)) (inc_) // ?
compose (getValue, chain (inc__)) (data1) // ?

Either 是 Monad,因此需滿足 4 個 typeclass:

  • Functor:實現 map 將 Functor 與 function 綁定
  • Apply:實現 ap 將 Apply 與包進 Apply 的 function 綁定
  • Applicative:實現 of 由 value 建立 Applicative
  • Chain:實現 chain 將 Chain 與回傳 Chain 的 function 綁定

第 4 行

let _x = x
  • 相當於 constructor 將 x 值存進 _x
  • _x 為 Object 包含 valuedir 兩個 property
    • value:儲存 value
    • dir:儲存 RightLeft

第 6 行

let map = f => (_x.dir === 'Left') ? Either (_x) : Either ({ value: f (_x.value), dir: 'Right' })

Monad 必須實現 Functor。

根據 Fantasy Land 定義:

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

Object 必須有 map 才是 Functor。

若為 Left 則繼續回傳 Either 不做事,否則從 _x 取出 value 經過 f 運算後,使用 Either 包成新 Either 回傳。

第 8 行

let ap = x => (_x.dir === 'Left') ? Either (_x) : x.map (f => f (_x.value))

Monad 必須實現 Apply。

根據 Fantasy Land 定義:

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

Object 必須有 ap 才是 Apply。

若為 Left 則繼續回傳 Either 不做事,否則從傳入 Either 取出 f 後,從 _x 取出 value 經過 f 運算後,改變 Either 回傳。

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

10 行

let chain = f => (_x.dir === 'Left') ? Either (_x) : f (_x.value)

Monad 必須實現 Chain。

根據 Fantasy Land 定義:

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

Object 必須有 chain 才是 Chain。

若為 Left 則繼續回傳 Either 不做事,否則將傳入回傳 Either 的 function 套用 _x.value 回傳。

12 行

let getValue = x => _x.value

無論 Left 或 Right,使用 getValue 取出 Either 內部 value。

22 行

let of_ = x => Either ({ value: x, dir: 'Right'})

Monad 必須實現 Applicative。

根據 Fantasy Land 定義:

fantasy-land/of :: Applicative f => a -> f a

必須提供 of free function 將 valuedir 包進 Applicative。

由於 of 在 ECMAScript 為 keyword 無法使用 arrow function 建立 of function,故改由 of_ 取代

23 行

let getValue = x => x.getValue ()

提供 free function 方便使用:

  • getValue:呼叫 Either 的 getValue

25 行

let Right = x => Either ({ value: x, dir: 'Right' })
let Left = x => Either ({ value: x, dir: 'Left' })

提供 free function 建立 Either:

  • Right:建立 Right
  • Left:建立 Left

28 行

let inc_ = of_ (inc)
let inc__ = compose (of_, inc)
  • inc_:將 inc 包在 Either 內
  • inc__inc 回傳 Either

31 行

let data0 = Right (1)
compose (getValue, map (inc)) (data0) // ?
compose (getValue, ap (data0)) (inc_) // ?
compose (getValue, chain (inc__)) (data0) // ?
  • data0 為 Right
  • 使用 Ramda 的 map 將 Either 與 inc 綁定,確認自行建立的 Either 符合 Fantasy Land 規格
  • 使用 Ramda 的 ap 將 Either 與包進 Either 的 function 綁定,確認自行建立的 Either 符合 Fantasy Land 規格
  • 使用 Ramda 的 chain 將 Either 與回傳 Either 的 function 綁定,確認自行建立的 Either 符合 Fantasy Land 規格

either000

Conclusion

  • Either 之所以能不執行後續 function,關鍵在無論是 mapapchain 都會先判斷是否為 Left,若為 Left 則直接回傳 Either 不會對傳入 function 做任何運算,因此其效果等效於 Exception