前一天有提過,Functor、Applicative、Monad 是 FP 裡面最玄幻的概念,我想原因是因為整個思維體系跟非 FP 語言(mutable 、有變數的高階語言)相差太多。而到了 Applicative ,我已經想不到任何可以在非 FP 的程式語言可以對應的概念了,所以只能 try my best 。
如果真的很想了解純 FP 是怎麼回事的話,歡迎入坑 haskell XD。
快速複習一下 Functor。在 Day05 我們提到過, Functor 是一種計算語境(computational context),Maybe Functor 就是隱含了計算失敗的 context,成功的話 Maybe a
= Just a
,失敗的話 Maybe a
= Nothing
。Functor 的另一個特色: 可以被 map over。
fmap :: (Functor f) -> (a -> b) -> f a -> f b
我們可以把普通的輸入 a 型別回傳 b 型別的函數,應用在包在 Functor f 這個 context 的 f a 裡面,產生一個同樣在這個 context 下面的 f b。
我們也用了 Promise 來當作比喻,雖然 Promise 並不是 Functor,但他有部分 Functor 的特性:包含 context(帶有會被 resolve 或被 reject 的可能),以及可以被 map over(Promise.then 的 callback 可以作用在包在 Promise 中的值)。
而 Applicative,其實就是加強版的 Functor。所謂加強版的意思就是,他滿足 Functor 的特性,並且還有額外的特性。
前面提過,Functor 是可以把一個普通函數 map over 到 context 中的 value,那如果現在,我們想要把在 context 中的函數作用在在 context 中的 value (好拗口)呢?
讓我們回想一下,在 Day01 Immutable 提到,所有的值都可以視為函數,因此把函數作為值包在 context 中應該也是可行的,合理吧?所以滿足 把 context 中的函數應用在在 context 中的值,並返回在 context 中的值的這個特性的就是 Applicative。
在 Haskell 裡 Applicative 的 Typeclass 定義是這樣:
class Functor f => Applicative f where
pure :: a -> f a
(<*>) :: f (a -> b) -> f a -> f b
可以看見 Functor f => Applicative f
這一行約束了一個 f 要成為 Applicative 的話必須要先是一個 Functor。
而 <*>
這個運算子(fun fact:運算子也是函數)的作用就是將在 context 中的函數 map over 到在 context 中的 value,我們比較一下 fmap
跟 <*>
:
fmap :: Functor f => (a -> b) -> f a -> f b
<*> :: Functor f => f (a -> b) -> f a -> f b
是不是幾乎一模一樣?差別只在於應用的是 context 中的函數而已。
所以 Applicative 到底有什麼用呢?
這問題實在是難倒我了。在非 FP 的語言裡幾乎不需要 Applicative 的性質,我們可以想像成用 Promise 包了一個函數當作 callback,作用在另一個 Promise 中包的值裡面,類似下面這樣的程式碼:
const callback = new Promise(...)
const value = new Promise(...)
value.then(callback)
但這樣在語法上是行不通的,畢竟 Promise 並不是 Applicative,但我想多少可以表達 Applicative 的概念。畢竟 JS 本質仍不是一個 FP 語言,所以應用到了 Functor 的概念應該已經仁至義盡了 XD,真的對純 FP 有愛的讀者快來學 Haskell 吧(趁亂推坑)。
* 本文圖片來自這裏