今天來介紹 Reader Monad,其主要處理的就是 dependency injection,
說到 dependency injection 如果最壞的情況就是要將值不斷的傳下去,但中間的函式都不會用到該值
const toUpper = str => str.toUpperCase();
const exlaim = str => str.concat('!');
const deps = {
"i18n": {
"HELLO FP!!": "嗨 函數式編程!"
}
}
const f = (str, deps) => deps.i18n[str]
const g = (upper, deps) => f(exlaim(upper), deps)
const h = (str, deps) => g(toUpper(str), deps)
h('hello fp!', deps) // "嗨 函數式編程!"
可以看到 g
以及 h
就只是將 deps
傳下去而已,本身根本沒用到,這樣不是很冗餘嗎? 而 Reader Monad 就是來解決這個痛點的! 首先先來看看最初的起點 Function Modeling
什麼是 Functions Modeling 呢? 回想一下前面幾章無論是 Either, Maybe 又或是 IO 都是放入單一型別(Object
, String
, ...) ,但如果現在想要放入的是函式呢?
舉例來說
const Function = (run) => ({
run,
})
如果想要讓 Function
變成是一個 Functor 呢?
const Function = run => ({
run,
map: f => Function(x => f(run(x)))
})
可以看到 map
就是將函式進行 compose,
Function(toUpper)
.map(exlaim)
.run('hello fp!') // HELLO FP!!
那想要將兩個 Function Monad
進行 compose 呢?
const Function = run => ({
run,
map: f => Function(x => f(run(x))),
chain: f => Function(x => f(run(x)).run(x))
})
接下來就可以將兩個 Monad 進行 compose 了!
Function(toUpper)
.chain(upper => Function(x => exlaim(upper)))
.run('hello fp!') // HELLO FP!!
也可將 Function 變成 pointed functor
Function.of = x => Fn(() => x)
大家這時應該就會好奇,那 x
是什麼呢? 可以先印出來看看
Function(toUpper)
.chain(upper => Function(x => console.log(x, exlaim(upper))))
.run('hello fp!') // hello fp! HELLO FP!!
沒錯 x
就是原本放入 run
的參數,而這有什麼用呢? 如果現在要做 dependency injection,例如 DB 的 config 注入等等,就可以派上很大的用場!
所以來改寫上述痛點!
Function.of('hello fp!')
.map(toUpper)
.chain((upper) => Function((deps) => deps.i18n[exlaim(upper)]))
.run(deps); // 嗨 函數式編程!!
另外,Function ADT 通常會有一個優化的 API,ask
,其主要就是取 run
傳入的值(deps)
Function.ask = Function(x => x)
再將上面程式碼套用 ask
Function.of('hello fp!')
.map(toUpper)
.map(exlaim)
.chain((str) => Function.ask.map((deps) => deps.i18n[str]))
.run(deps); // 嗨 函數式編程!!
而這也被稱為 Reader Monad!
感謝大家閱讀!!