Either 也是看似很玄的概念,讓我們不用處理 Exception,且可使用 map
與 chain
直接改變 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 包含value
與dir
兩個 propertyvalue
:儲存 valuedir
:儲存Right
或Left
第 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 將 value
與 dir
包進 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
:建立 RightLeft
:建立 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 規格
Conclusion
- Either 之所以能不執行後續 function,關鍵在無論是
map
、ap
或chain
都會先判斷是否為 Left,若為 Left 則直接回傳 Either 不會對傳入 function 做任何運算,因此其效果等效於 Exception