1. 程式人生 > >不完整解釋 Monad 有什麼用

不完整解釋 Monad 有什麼用

【預警】這篇文章沒有詳細解釋 Monad,我承諾我會抽空寫。

關於我為什麼自己打臉回來寫文章,不想解釋太多。只想說掘金真香。

本打算這週末寫文章解釋下 Monad 的,但是最近比較忙,還是再拖一會。

新工作挑戰比較大。第一次遇到這麼複雜的業務和開發流程,一開始適應的不是很好。開會全程懵逼,不知道別人在講什麼。最近主要精力還是要花在熟悉業務和新的工作環境,學習分享上會緩一緩。

先簡單介紹一下 Monad 的用處預熱一下吧。可能看完這篇你不會全懂,那是因為我沒仔細解釋,留待下次吧,抱歉了。這裡要展示的程式碼主體部分是我的練習改寫,後半部輔助函式和示例是我模仿改寫的。

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 節點樣式屬性。函數語言程式設計的一個主要挑戰就是把計算和作用分離開來,計算的過程中不能產生作用。

回到程式碼看是怎樣做到的。首先 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 的時候,計算才會被觸發,作用才會被釋放。

線上 Demo 戳這裡

這篇文章也發表在我的中文部落格上 Lambda Academy