前一篇文章我們談到了如何實作一個 Maybe
Monad,而其主要的功能就是處理無值的情境,今天我們要來介紹另外一種寫法去實作 Maybe
Monad。
如果有用 rxjs 或是 fp-ts 之類的函式庫,可能會很熟悉這種寫法
pipe(1, R.add(2), R.add(3)) // 6
沒錯,在 rxjs v6 之後都改用 pipe 的方式執行操作符, fp-ts 也是以 pipe 的方式為主,那相較於傳統的鍊式寫法,用 pipe 的方式寫有什麼優點呢?
筆者認為是
那廢話不多說,開始改寫吧!
在這之前都假設各位讀者都知道什麼是 pattern match 了!
在這之前筆者參考 fp-ts 的命名,在 fp-ts 其 Maybe Monad 稱為 option,不同於 Maybe 的 Nothing
跟 Just
, option 是用 none
表示無值, some
表示有值。
然而我們是用 pipe 執行,那就需要先有該函式,來實作一個吧
實作 pipe
const pipe = (init, ...fns) =>
fns.reduce((prevValue, fn) => fn(prevValue), init);
跟 Function Composition 那章提到得概念一樣, 這次我們只是將我們要執行的初始值放在第一個,接下來再放入我們要執行 piping 的函式。
Constructor
Option a = some a | none
const none = {_tag: "None"}
const some = value => ({_tag: "Some", value})
const match = (onNone, onSome) => (fa) => {
switch (fa._tag) {
case 'None':
return onNone;
case 'Some':
return onSome(fa.value);
default:
break;
}
};
const of = (x) => some(x);
Functor
const map = (g) =>
match(
() => none,
(b) => some(g(b))
);
pipe(of(10), map(R.add(2))) // some(12)
pipe(none, map(R.add(2))) // none
map
就是將目前 option 的狀態 (None || Some) 去辨別在 match 時是要執行 onNone
還是 onSome
,而概念都跟前一章提到得一樣,就不在此贅述了。
Applicative Functor
const ap = (f1) => (f2) =>
pipe(
f2,
match(
() => none,
(f) =>
pipe(
f1,
match(
() => none,
(a) => some(f(a))
)
)
)
);
const lift2 = R.curry((g, f1, f2) => pipe(f1, map(g), ap(f2)));
lift2(R.add, of(2), of(2)) // some(4)
lift2(R.add, of(2), none) // none
這邊就稍微複雜了一點,以範例來講,由於 ap
是 curried 函式,所以當我們放入 ap(some(2))
的時候不會立即執行,而是會等到 piping 之後的 some(R.add(2))
傳入函式才會被執行,也就是 ap
函式參數 f2
,以此例子來說, f2
是 some(R.add(2))
並不是 none
, match 函式就會 callback onSome
,接下來 f1
是 some(2)
所以 match
也會 callback 執行 onSome
,最終就會回傳 some(4)
。
Chain
const chain = (g) => match(() => none, g);
const safeHead = xs => xs.length === 0 ? none : some(xs[0])
pipe(of([1, 2, 3]), chain(safeHead)) // some(1)
pipe(of([]), chain(safeHead)) // none
chain 也跟前一章提到的一樣,就是打平,而跟 map
再 join
的方式不同,在執行的時候就不會包著 x => some(g(x))
而是直接執行該函式。
改寫前篇 chain
的寫法就會變成,而概念也是一樣的
class Maybe {
...
chain(f) {
return f(this.val)
}
}
Option
const option = (dv) => match(dv, R.identity);
pipe(
of([1, 2, 3]),
chain(safeHead),
option([])
); // 1
pipe(
of([]),
chain(safeHead),
option([])
); // []
而 option 就是把值真正取出來,所以如果最終是有值的情況,我們就是用 identity 函式取值出來,空值則是回傳 default value.
不知道大家覺得哪種寫法比較讚呢? dot chain 還是 pipe?
感謝大家閱讀
Either Monad