【預警】這篇文章沒有詳細解釋 Monad,我承諾我會抽空寫。css
關於我爲何本身打臉回來寫文章,不想解釋太多。只想說掘金真香。數據庫
本打算這週末寫文章解釋下 Monad 的,可是最近比較忙,仍是再拖一會。編程
新工做挑戰比較大。第一次遇到這麼複雜的業務和開發流程,一開始適應的不是很好。開會全程懵逼,不知作別人在講什麼。最近主要精力仍是要花在熟悉業務和新的工做環境,學習分享上會緩一緩。閉包
先簡單介紹一下 Monad 的用處預熱一下吧。可能看完這篇你不會全懂,那是由於我沒仔細解釋,留待下次吧,抱歉了。這裏要展現的代碼主體部分是個人練習改寫,後半部輔助函數和示例是我模仿改寫的。app
function IO(effectFn) {
const __val = effectFn
const map = fn => IO(() => fn(__val()))
const performUnsafeIO = __val
const chain = fn => IO(() => fn(__val()).performUnsafeIO())
return Object.freeze({
map,
chain,
performUnsafeIO,
})
}
const curry = fn => (...args) =>
args.length >= fn.length ? fn(...args) : curry(fn.bind(undefined, ...args))
const compose = (...fns) => fns.reduce((f, g) => (...args) => f(g(...args)))
const map = curry((fn, monad) => monad.map(fn))
const chain = curry((fn, monad) => monad.chain(fn))
const setStyle = curry((sel, props) => IO(() => $(sel).css(props)))
const getItem = key => IO(() => localStorage.getItem(key))
const applyPreferences = compose(
chain(setStyle('#main')),
map(JSON.parse),
getItem
)
applyPreferences('preferences').performUnsafeIO()
複製代碼
這個 IO Monad 在一些 ADT 庫裏面也叫 Effect,它是來處理應用中的做用的。先看示例部分。這個應用的主要功能就是從 localStorage 讀取用戶的樣式偏好,讀到以後再改掉頁面對應的樣式。這個簡單例子涉及到兩個做用(effects),注意做用和反作用(side effects)是兩個不一樣的概念。這兩個做用是讀取數據庫和改變 DOM 節點樣式屬性。函數式編程的一個主要挑戰就是把計算和做用分離開來,計算的過程當中不能產生做用。ide
回到代碼看是怎樣作到的。首先 getItem
函數把根據傳入的 key 讀取 localStorage 的行爲扔進了 IO 函數。IO 函數把這個會產生做用的裏層函數存在閉包裏,並無當即執行。map 的做用就是,先執行傳進 IO 的函數,再把計算結果傳進 map 自身接受的回調函數。可是請注意,map 並無當即執行會產生做用的函數,它只是聲明瞭行爲。接着,到了最難理解的 chain 函數(若是你理解了 chain 在幹什麼,你就徹底理解 Monad 了)。chain 接受的回調函數自身也會返回一個 IO,這個時候就不能直接把回調函數執行的結果扔回給 IO 了,否則就是 IO 嵌套 IO,沒辦法 map 了。因此先把裏層 IO 的做用函數執行一遍,再把結果塞回 IO。一樣,這裏只是聲明行爲,沒有真的執行。函數式編程
程序運行到 applyPreferences("preferences")
的時候,就把應用功能所有描述完了,但只是定義了每一步的計算,還沒開始執行指令。到最後一部 performUnsafeIO
的時候,奇蹟纔會發生,做用纔會釋放。再回過頭看整個程序,是否是以爲很乾淨?無論你有沒感覺到,反正我感覺到了……函數
你可能會問:誰 TM 這樣子寫代碼找抽啊!!!學習
其實,RxJS 的原理差很少就是這樣的。Observable 就是個 IO Monad。RxJS 裏面聲明的計算,都是惰性的,只有在最後 subscribe 的時候,計算纔會被觸發,做用纔會被釋放。ui
本文示例主要目的是演示,仍是過於簡單化。注意 JSON.parse
可能會拋出異常,而拋出異常也屬於做用。有一個叫 Maybe 的數據類型專用來解決這類問題。我之前在一篇介紹 Ramda 的文章最後面有演示 Maybe 的用法。
線上 Demo 戳這裏。
這篇文章也發表在個人中文博客上 Lambda Academy