Promise 是很好解決 js 非同步的方案。
Monad 單子
Monad 是一個 FP 中的專有名詞。
A monad is just a monoid in the category of endofunctors.
Monad 就是自函子範疇上的么半群。
Functor 函子
在範疇論中,函子是範疇間的一類對映。函子也可以解釋為小范疇內的態射。
態射是範疇內物件之間的對映關係。函子與它類似,函子是範疇與範疇間的對映關係,也就是可以通過一個函子,把一個範疇對映到另一個範疇。
可以將 Functor 理解成一個容器!
我們用 js 中的一些東西來解釋一下可能更清楚。
const addThree = (x) => x + 3
const array = [ 2, 4, 6 ]
const mappedArray = array.map(addThree)
console.log(mappedArray)
// => [ 5, 7, 9 ]
複製程式碼
可以理解 Array 就是一個 Functor, 而 array 就是 Array 這個 Functor 的例項。
我們可以把 array 看成一個集合或者一個範疇。
- array 裡面的 2、4、6,應用了 addThree 與 mappedArray 中的 5、7、9 一一對應;
當然 Array 得是一個 Functor 的話,它還得滿足 Functor 的其他特性,這裡就不說了。
簡單來說,函子就是一個可以將 function 應用到函子 value 的一個容器。
endoFunctor 自函子
把一個範疇對映到自身的函子叫做自函子。
假設我們現在把我們的範疇定義的更大一些,array 的元素是這個有理數集合,那麼 Array 就是一個自函子。
現實中的自函子有哪些呢,掛鐘就是一個自函子。
semigroup(半群) 與 monoid(么半群)
google到數學裡定義的群(group): G為非空集合,如果在G上定義的二元運算 *,滿足
封閉性(Closure):對於任意a,b∈G,有a*b∈G
結合律(Associativity):對於任意a,b,c∈G,有(a*b)*c=a*(b*c)
么元 (Identity):存在么元e,使得對於任意a∈G,e*a=a*e=a
逆元:對於任意a∈G,存在逆元a^-1,使得a^-1*a=a*a^-1=e
複製程式碼
如果僅滿足封閉性和結合律,則稱G是一個半群(Semigroup);如果僅滿足封閉性、結合律並且有么元,則稱G是一個含么半群(Monoid)。
比如自然數這個非空集合G,加上 + 這個二元運算,就是一個么半群。(滿足封閉性、結合律,0 就是么元)
單子(Monad)是這樣一個自函子範疇:
-
其自函子物件是:
M: C → C -
有以下兩種自然變換:
- unit(X): I → M X 是 C 中的物件,I 是 id 自函子
- join(X): M × M → M
顯然,在這個自函子範疇上構成了一個么半群,這個么半群的集合是所有自函子,其二元運算是由join決定結果的自函子複合。
在Haskell中的monad是這樣的:
class Monad m where
fmap :: (a -> b ) -> f a -> f b
return :: a -> m a
(>>= ) :: m a -> (a -> m b) -> m b
複製程式碼
Promise 和 Monad
OK, 那 Monad js 中的 Promise 有什麼關係呢?
haskell 中 Monad 是用來隔離副作用的,Promise 在 js 中也是用來隔離副作用的,所以我們本能可以將二者聯絡起來。
把 Promise 理解成一個容器。
Promise.resolve(5)
或者new Promise((resolve, reject) => resolve(6)
是不是就是 Monad 裡面的 return 方法Promise.then
可以近似理解成 Monad 裡面的>>=
方法。
(pure 10 :: Maybe Int) >>= x -> return (x * x)
複製程式碼
Promise.resolve(10).then(x -> x * x)
複製程式碼
Promise 和 Monad 不僅形似,而且神似。
那麼再回過頭講講 Monad。
Functor、Applicative 與 Monad
haskell GHC 7.8 重新定義了之前的 Functor、Applicative 與 Monad 的關係。
Functor ⟹ Applicative ⟹ Monad
那麼看看 Applicative 是什麼。
Applicative
Functor 的定義
class Functor f where
fmap :: (a -> b) -> f a -> f b
複製程式碼
class Functor f => Applicative f where
(<*>) :: f (a -> b) -> f a -> f b
複製程式碼
ps: <*>
就是 fmap
Functor 函子
fmap :: (a -> b) -> f a -> f b
Applicative 應用函子
(<*>) :: f (a -> b) -> f a -> f b
Applicative 必須是一個 Functor,而 Functor 只能將一個 value a 裝進容器變成一個,然後與函式 a -> b 運算;
但是 Applicative 可以將函式 a -> b 裝進容器,與原有的容器 f a 運算。
Applicative 與 Monad 的區別
[ x + y | x <- [1..3], y <- [1..x]]
-- [2, 3, 4, 4, 5 ,6]
[1..3] >>= x -> [1..x] >> y -> return (x + y)
-- [2, 3, 4, 4, 5 ,6]
複製程式碼
(+) <$> [1..3] <*> [1..x]
-- Not in scope: `x`
複製程式碼
y 的取值是依賴於 x 的,使用 Monad 是可以的,但是使用 Applicative 是無法做到的!
也就是說 Monad 後面的計算可以依賴於前面計算的結果,但是 Applicative 中的每個引數的計算是獨立的,後面的結果不能依賴於前面的。
通俗一些說 Monad 可以表達 上下文(context)
的計算,Applicative 是不可以的。
Monad在計算的時候,後一個計算問題可以用到前面的引數,也就是說各個計算之間不是互相獨立的,而是有依賴關係的。
總結
同樣的 Promise 是沒有上下文的。
Promise.resolve([1, 2, 3])
.then(x => [1..x])
.then(y => (x + y))
複製程式碼
ps: 上述 js 的例子部分是虛擬碼
個人覺得,從 Monad 的嚴格定義上, 很多約束條件 Promise 的實現都是沒有滿足或者沒有嚴格滿足,但是其形式及其相似。可以說 Promise is a monad;
然而,從 context 這個核心上來看,Promise 現有的實現不能滿足。可以說 Promise is not a monad。
但是可以肯定的是 Promise 在隔離副作用上和 Monad 有異曲同工之妙。不知在確立 Promise 規範的時候,有沒有借鑑 Monad!