英文版:這裡

這篇文章不探討要怎麼使用monad
如果你想知道怎麼使用它們
你應該先理解它們的實例,如List, Maybe, Writer, State … 等
那這篇文章要做什麼呢
相信大家在學習monad時
應該都知道它有3個law
可是它們看起來就像是巫術一樣難以理解
我想在讓讀者不必真正理解category theory的狀況下了解那三個law為什麼會是「顯而易見」的

一開始的問題是有好幾種定義monad的方式
它們定義出來的東西都是等價的
但Haskell用的那種是最機掰的
這邊我介紹一種在category theory中相對常見的定義方式
它在Control.Monad模組裡的宣告如下:

(>=>) :: Monad m => (a -> m b) -> (b -> m c) -> (a -> m c)

為什麼要特別提到這個函數呢?
一般的monad定義是這樣的

class Monad m where
  return :: a -> m a
  (>>=)  :: m a -> (a -> m b) -> m b

但存在一種表達力相等的定義方法如下

class Monad m where
  return :: a -> m a
  (>=>)  :: (a -> m b) -> (b -> m c) -> (a -> m c)

這兩種不同的monad定義都可以被用來定義對方
我把它當作給讀者的作業

如果用後者的定義來寫出三個law
他們會是這樣:

return >=> f = f
f >=> return = f
(f >=> g) >=> h = f >=> (g >=> h)

雖然很破梗
但以下是monoid的三個law

mempty <> x = x
x <> mempty = x
(x <> y) <> z = x <> (y <> z)

為什麼我要提這個呢?
你仔細一看
會發現這和monad的三個law根本一模模一樣樣啊!
把上面的f, g, h換成a, b, c
return換成mempty
>=>換成<>就變成下面了
我把第一個law叫做left identity
意思是在左邊加上「空無」後進行運算的值應該和原本一樣
第二個叫right identity
在右邊加上「空無」後進行運算的值應該和原本一樣
第三個叫結合律
這代表進行運算的順序是不重要的
傳統上加法和乘法的結合律只是它的特例

唯一有點不同的是這點:
對於所有monoid的值a, b來說
a <> b永遠是被定義的
但提到>=>的參數
那些參數的型別並不只是個像Int或者是[Bool]之類的一般型別
而是一個函數型別a -> m b
它們具有某些內部的結構
亦即ab兩者
隨便取兩個a -> m bc -> m d
>=>並不總是被定義的
有可能會直接跳type error啊
事實上
唯一不跳type error的狀況是當b ~ c
用這樣的方式想
return>=>並不形成一個monoid
而是一個比它更強型別的概念
強型別於第一個參數的回傳型別和第二個參數的接收型別必須有某些相關性
而這樣「強型別的monoid」
在數學上有個名字:category

上面一種monad定義方式的解釋就到此結束
接下來是另一種
數學家更愛可是對入門者來說可能比較難懂的定義
首先
我假定你已經知道什麼是Functor
你應該也知道所有Monad都是Functor
在此
我要把Monad定義為多滿足某些條件的Functor
用程式碼寫成這樣:

class Functor m => Monad m where
  return :: a -> m a
  join   :: m (m a) -> m a

先來解釋為什麼這種定義可以推導至上面兩種
假設你有a -> f bb -> f c
f僅僅是個Functor
那麼你可以在餵了一個參數給第一個函數後把它丟出的結果拿去用第二個函數map
這麼一來
丟進a之後
你會得到f (f c)
問題是如果這樣子做了很多詞動作之後f會越疊越多層
很討厭啊
不能把它全部消去成一層嗎?
如果fjoin這個方法當然可以
這就是join在做的事啊!

現在有個巧妙的做法
我們可以只注意型別為* -> *Functor本身
而忽視Functor之下的型別
為了方便起見
我使用一種奇特的語法
join的型別寫作mm -> m
他把靠在一起(事實上是互相嵌套)的兩個同樣的Monad合併成一個
return的型別要怎麼用這種奇特的語法表示呢?
你應該知道有個什麼都不做的FunctorIdentity
a的特性和Identity a一模一樣
畢竟Identity只是個空殼子而已
我在此把它簡寫成I
return的型別相等於I a -> m a
用奇特的語法寫就是I -> m

接下來可以來再次介紹Monad的定義了
Monad能被定義為一個有著returnjoin動作的Functor
它們同樣要滿足三個law
分別如下

  1. 我們可以把一個m的左邊加上一個I(事實上是外面套一層I
    之後把這個Ireturn變成m
    怎麼變的?
    用奇怪語法的方式想
    returnI -> m
    所以它本來就是用來把I變成m的啊!
    現在我們有兩層m
    也就是mm
    接著我能用join(mm -> m)變回一層
    多加一層再變回一層這樣的動作應該和什麼都不做一樣

  2. 我可以把m的右邊加上一個I
    不用奇怪語法的角度想
    我只要把m裡面的型別a mapI a就好了
    用奇怪語法的角度想就是在右邊加上I
    現在我可以同樣用returnmI變成mm
    然後再joinm
    同樣這樣子的動作應該和什麼都不做一樣

  3. 假如有三層m
    也就是mmm
    我可以join兩次把它變成m
    有兩種方式
    第一種方式是先join左邊兩個m
    第二種是先join右邊兩個m
    但順序應該不影響結果

但這就是Monoid的三個law啊!
第一個就和left identity一樣
左邊加上空無應該不讓結果改變
第二個就是right identity
右邊也能加上空無
第三個就是結合律
運算的順序不影響結果
這難道還不夠理所當然嗎?

也就是說

A monad is just a monoid in the category of endofunctors, what’s the problem?