promise is a monad?

fri3nds發表於2019-02-28

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 ]
複製程式碼

functor
可以理解 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 理解成一個容器。

  1. Promise.resolve(5) 或者 new Promise((resolve, reject) => resolve(6) 是不是就是 Monad 裡面的 return 方法
  2. 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!

相關文章