點燈坊

失くすものさえない今が強くなるチャンスよ

實務上使用 FP 開發一年心得總結

Sam Xiao's Avatar 2020-12-16

隨著一年來都以 ECMAScript 為主力,前後端都搭配 Ramda 以 FP 開發,從一開始抱持著懷疑與摸索心態,逐漸地將 FP 原則套用在實際開發上後,發現 FP 並不只是理論而已,而是真的可以用在實戰,雖然學習門檻較高,但只要跨越之後,Codebase 可讀性與可維護性日後都會賺回來。

Pure Function 是可以實戰的

很多人懷疑 pure function 只能做 transformation,都不能做 side effect 怎麼實戰呢 ?

只有 pure function 當然無法實戰,pure function 必須站在其他 higher order function 巨人肩膀上才能發揮威力,只靠 ECMAScript 的 Array.prototypeObject.prototype 是不夠的,如 Ramda 就提供非常豐富的 higher order function,除了 Array 與 Object 外,還提供如 function、logic、relation、math … 等 function,藉由 Ramda 的 higher order function,自己就可專心的寫 pure function。

那 side effect 呢 ? 實務上有兩種方式:

  • 將 side effect 集中在最前面與最後面,如一開始寫 impure function 取得資料,拿到資料後就是一系列 higher order function + pure function 做 transformation,最後再用 forEach()andThen() 處理 side effect 寫入資料,這種方式雖然不完美,但已經比 imperative 到處 side effect 發散好很多,最少已經將 side effect 縮小在可控範圍內
  • 以 monad 將 side effect 包起來,由 monad 處理 side effect,對於 monad 一樣使用 pure function 操作,這也能將 side effect 縮小在可控範圍內

組合好 Function 再做事

傳統寫程式都是在處理 data,呼叫 method 就是為了取得 data 放在 variable,然後再以 statement 去處理 variable,所這就是典型 Imperative。

但 FP 並不是這種思維,寫程式重點在於處理 function,所以拿到問題後,都在思考如何透過 higher order function 組合 function,最後再將 data 一次灌進 function 得到結果,也由於都在組合 function,因此沒有 variable,只會看到不斷的以 expression 定義 function 或 higher order function 組合 function 而已。

由 Ramda 幫你組合 Callback

傳統 imperative 的 argument 都是 data,也就是該 method 已經決定了功能,你能做的只是塞資料而已。

但 FP 的 argument 則是 function,也就是 callback,這就造就了無限可能,可由 Ramda 的 higher order function 組合出各種 callback 傳入,因此該 function 的功能就大大提升。

現在只要遇到 API 或 library 提供 callback 就超開心,這表示你能使用 Ramda 組合出各種 callback 來 惡搞,而不像傳統只能被 library 侷限住功能。

別急著將 Promise Await

ECMAScript 的 Promise 其實有 Functor 與 Monad 特性,將非同步的 side effect 包在 monad 內,讓你在 then() 間寫 pure function,並在最後一個 then() 處理 side effect,但自從有了 async await 之後,大家幾乎馬上將 Promise 透過 await 從 Promise 取出,然後回到 Imperative 的 impure function 世界混戰,這是非常可惜的,這使得 Promise 提早回到 impure function 處理 side effect,並非 Promise 設計初衷。

async await 目的是讓只懂 Imperative 的人快速上手處理非同步,但不可過度依賴此 syntatic suguar,重點是透過 Promise 習慣以 Functor 與 Monad 特性處理 side effect,這對將來學習 Maybe, Either 有很大幫助。

實務上對於 Promise 建議兩種寫法:

  • 由於 Promise 有 Functor 特性,將 then() 視為 map() 使用,在每個 then() 中使用 pure function 做 transformation
  • Promise 亦支援 composition law,可將所有 then() 中的 pure function 先使用 pipe() 組合成 funtion,再交給 then() 一次執行

Conclusion

  • 以上四點是我以 FP 用在實務上的領悟,FP 絕對不是只有 no side effect 這麼單純而已,它事實上是很多配套環環相扣而構成完整 paradigm
  • 想要 OOP 結合 FP 實務上也很困難,畢竟兩者是完全不同 paradigm,很多原則都互相矛盾,建議學習 FP 就走純 FP,並搭配 FP 的配套 design pattern,如 higher order function、immutable、curried function、partial application、function composition、function pipeline、point-free、monad、functor、maybe、either …. 等,OOP 跟 FP 混搭很容易走火入魔,最後變成兩種 paradigm 都不是的四不像,既違反 OOP 又違反 FP 而裡外不是人
  • ECMAScript 並不是 pure functional language,因此有些 FP 感覺很難單純在 ECMAScript 抓到,建議行有餘力可學習 Haskell,藉由這個純 FP 語言訓練 FP 手感,進而用在 ECMAScript 上