Sanctury 與 Ramda 最大的差別是 Ramda 有些 Function 會回傳 undefined
,這導致有些 Function 一不小心會造成 Runtime Error,但 Sanctuary 一律回傳 Maybe,讓我們不用處理 undefined
也不會出錯。
Version
Sanctuary 3.1.0
Ramda
import { pipe, head, toUpper } from 'ramda'
let data0 = ['foo', 'bar', 'baz']
let data1 = []
let f = pipe (
head,
toUpper
)
f (data0) // ?
f (data1) // ?
若我們想取得 Array 的第一個 element,並將所有英文字母轉大寫,Ramda 會組合 head
與 toUpper
,但 head
因為可能回傳 undefined
,只要傳入 Empty Array 就 runtime error 了。
import { pipe, head, toUpper, either, always as K } from 'ramda'
let data0 = ['foo', 'bar', 'baz']
let data1 = []
let f = pipe (
either (head, K ('')),
toUpper
)
f (data0) // ?
f (data1) // ?
Ramda 若要解決回傳 undefined
的 function,可用 either
包起來,若回傳 undefined
時改回傳 default value。
Maybe
import { pipe, map, toUpper, head } from 'sanctuary'
let data0 = ['foo', 'bar', 'baz']
let data1 = []
let f = pipe ([
head,
map (toUpper)
])
f (data0) // ?
f (data1) // ?
若改用 Sanctuary 的 head
則不用擔心 Empty Array,因為 head
會回傳 Maybe,遇到 Empty Array 則回傳 Nothing,因此不會執行 map
內的 toUpper
,所以不會有 runtime error。
Maybe Chain
import { pipe, map, head, toUpper, concat, append, flip } from 'sanctuary'
let data0 = ['foo', 'bar', 'baz']
let data1 = []
let f = pipe ([
head,
map (toUpper),
map (concat ('_')),
map (flip (concat) ('_')),
])
f (data0) // ?
f (data1) // ?
Maybe 價值在於後續處理。
若你想在 toUpper
之後繼續在 string 前後加上 _
,如 _FOO_
,可繼續 map
concat
與 flip(concat)
,而不用擔心過程中出現 undefined
,反正過程中只要出現 undefined
就會回傳 Nothing 並終止後續 map
處理。
反之若你提早 fromMaybe
成一般值,後面的運算就必須自行承擔 undefined
風險。
所以我們都會希望 function 能回傳 Maybe,只有在最後要顯示時才用 fromMaybe
從 Maybe 內取出。
Composition Law
import { pipe, map, head, toUpper, concat, append, flip } from 'sanctuary'
let data0 = ['foo', 'bar', 'baz']
let data1 = []
let transform = pipe ([
toUpper,
concat ('_'),
flip (concat) ('_')
])
let f = pipe([
head,
map (transform)
])
f (data0) // ?
f (data1) // ?
第 6 行
let transform = pipe ([
toUpper,
concat ('_'),
flip (concat) ('_')
])
let f = pipe([
head,
map (transform)
])
Maybe 必須使用 map
才能改變其內部值,因此可能發現一堆 map
。
Maybe 支援 composition law,可將 pure function 先使用 pipe
組合起來,一次傳給 map
即可。
maybe
import { pipe, map, head, toUpper, concat, append, flip, maybe, I } from 'sanctuary'
let data0 = ['foo', 'bar', 'baz']
let data1 = []
let transform = pipe ([
toUpper,
concat ('_'),
flip (concat) ('_')
])
let f = pipe ([
head,
map (transform),
maybe ('') (I)
])
f (data0) // ?
f (data1) // ?
Maybe 畢竟無法用於顯示,因此必須將結果從 Maybe 內取出。
13 行
let f = pipe ([
head,
map (transform),
maybe ('') (I)
])
使用 maybe
從 Maybe 中取出內部值。
maybe()
b -> (a -> b) -> Maybe a -> b
從 Maybe 取出內部值
b
:提供 Nothing 預設值
a -> b
:傳入處理 Just 的 function
Maybe a
:data 為 Maybe
b
:回傳 Maybe 內部值
fromMaybe
import { pipe, map, head, toUpper, concat, append, flip, fromMaybe } from 'sanctuary'
let data0 = ['foo', 'bar', 'baz']
let data1 = []
let transform = pipe ([
toUpper,
concat ('_'),
flip (concat) ('_')
])
let f = pipe ([
head,
map (transform),
fromMaybe ('')
])
f (data0) // ?
f (data1) // ?
12 行
let f = pipe ([
head,
map (transform),
fromMaybe ('')
])
若需求只是要處理 Nothing 如何顯示,可簡單使用 fromMaybe
。
fromMaybe
a -> Maybe a -> a
從 Maybe 取出內部值
a
:提供 Nothing 預設值
Maybe a
:data 為 Maybe
a
:回傳 Maybe 內部值
Conclusion
- 實務上 Ramda 會回傳
undefined
的 function 都應使用 Sanctuary 取代,如head
與find
就是典型例子,如此可避免忘記判斷undefined
而造成 runtime error,且可繼續使用 pure function 組合 - Maybe 支援 composition law,可將 pure function 先抽出來,避免一堆
map
出現 - 應該盡量避免過早從 Maybe 取出值,盡量維持在 Maybe 內運算,如此可避免
undefined
造成 runtime error,直到最後顯示時再使用maybe
或fromMaybe
取出