EZTABLE IDEAS 是 EZTABLE 成員揮灑熱情和大家分享專業及創意的網誌。 EZTABLE 讓消費者 24 小時都可以在網路訂位全台灣最優質的餐廳,同時提供餐廳經營者 e 化的訂位管理系統 (雲端、iPad、智慧型手機)

Haskell Monad 簡介

四月 12 2014 Published by under Engineering

 

Monad 其實是從數學的某個分支 Category Theory 而來,Category Theory 是說一群 Object 搭配 Morphisms ,Morphisms 是 Object 的轉換。在 Haskell 語言中,整個 Type System 就是一個 Category 稱為 Hask 。

Hask:

Objects 對應到 Types
Morphisms 對應到 Functions
Morphisms 可以做 Composition ,當然 Function 也可以,就是常用的 (.) 。

如此就是一個基本的 Cateogry 。

 

接下來要介紹 Functor , Functor 是 category 間的轉換,它可以轉換任意 object 到另一個 category 的 object ,也可以轉換 morphism 到另一個 category 的 morphism 。

 

在 Hask 裡面,有許多 subcategory ,比如 List, Maybe 等,其實這兩個都是 Functor ,用 List 說明如下

List a 是一個 fucntion 可以將 type a 轉換成 List a ,比如 Int 可以轉換成 List Int ,這就是一種 type 的轉換。另外定義一個 fmap 來對 morphisms 做轉換,這個 fmap 便是我們常用的 map function 。

 

看一下 map 的定義

map : (a -> b) -> [a] -> [b]

 

解讀起來,便是傳入一個 function ,將 a 對應到 b 。就得到另一個 function 是 [a] 對應到 [b]。

 

這些 function 也不能亂定義,是要滿足以下條件:

* fmap id = id
* fmap (f . g) = fmap f . fmap g

證明就不寫了,代幾個簡單的例子就可以知道的確符合。

 

接下來就可以進入重點 Monad , Monad 其實是一個特殊的 functor ,在相同的 Category 間做轉換。有兩個很重要的 function

* unit :: x -> M(x)
* join :: M(M(x)) -> M(x)

 

通常一些教材都會說 Monad 是一個 Computation 的封裝, unit(return) 就是初始化,把素材丟進去箱子內,那 join 就可以看成是拆箱子的概念。不過在 Haskell 不是用 join 定義 monad ,而是改用 bind (>>=) ,但其實它跟 join 都是可以交替定義的。bind 用抽象的概念解釋的話,有點像是你只要指定如何計算,剩下的讓 monad 裝箱幫你解決,比如讓運算可以被串聯,錯誤處理, side effect 等。當然 monad 跟 functor 一樣,也必須滿足三個 laws ,在這邊就不繼續寫下去,讓我們來個經典例子了解一下到底 Monad 為我們解決了什麼問題。

 

假設我們現在要寫一段程式追蹤複製羊的實驗,我們想要知道一支小羊的爸爸媽媽是誰,但是小羊因為是複製的,不一定都有爸媽。所以 father, mother 的 function 可能可以寫成這樣:

 

father :: Sheep -> Maybe Sheep
father = …
mother :: Sheep -> Maybe Sheep
mother = …

 

Maybe 是一個 monad ,通常用來封裝計算完可能沒有結果,所以 father 跟 mother 的結果有可能是 Nothing 。看來也沒有什麼,但假設接下來又有一個需求是問外公,我們需要 maternalGrandfat 這個 function 。

maternalGrandfat :: Sheep -> Maybe Sheep
maternalGrandfather s = case (mother s) of
                                                          Nothing -> Nothing
                                                          Just m -> father m

 

很簡單,判斷一下結果是不是 Nothing ,是 Nothing 就不用算了。但假設還要繼續追下去呢?

mothersPaternalGrandfather :: Sheep -> Maybe Sheep
mothersPaternalGrandfather s = case (mother s) of
                                                                         Nothing -> Nothing
                                                                         Just m -> case (father m) of
                                                                                                      Nothing -> Nothing
                                                                                                      Just gf -> father gf

 

可想而知,再下去會很深,程式碼也很醜。讓我們用 monad 特有的 bind (>>=) 來幫我們解決問題

bind 定義:

(>>=) :: Maybe a -> (a -> Maybe b) -> Maybe b
Nothing >>= _ = Nothing
(Just x) >>= f = f x

mothersPaternalGrandfather :: Sheep -> Maybe Sheep
mothersPaternalGrandfather s = (Just s) >>= mother >>= father >>= father

 

是不是簡單多了, bind 幫我們處理了是否 Nothing 這件事情,你只要提供計算方法就好。

 

Monad 滿多都有一種特性,就是 “一入侯門深似海",像上面只要運算結果是 Maybe 後面的結果都就會是 Maybe 。 IO 也是很明顯的例子,一旦進入 IO 就離不開 IO 這個 monad ,這也是 Haskell 隔離 IO 的一種方式。上面提到 List 也是 Monad ,他則是封裝了"不確定性",所以 List 的 bind 是 map 所有的 element ,把所有不確定性都考慮進去。

 

Monad 一直公認是個門檻,建議初學者要用夾攻的方式來學習。從 Category Theory 思考,不難想像 Haskell 基本元素就是 function 跟 type ,所以可以推導出如此多東西。跟從一些使用 Monad 解決問題的例子,推敲出為什麼需要 Monad ,相信會有更多收獲。


Related Posts Plugin for WordPress, Blogger...

No responses yet

發表迴響