在剛接觸 Monad 這名詞時,我相信一定很多人都很好奇 Monad 是甚麼,所以就會開始上網搜尋,然後就會找到下面這個版本,在這個版本中,使用了透明盒子來比喻成 Functor, Applicative 與 Monad 在做的事,這是其中一篇: http://adit.io/posts/2013-04-17-functors,_applicatives,_and_monads_in_pictures.html 。
這個版本很棒,很淺顯易懂,很推薦給大家讀讀。至於他說了什麼呢?簡單的來說,Functor 在做的事情,就是把一個東西從盒子拿出來,然後做一些計算,再把它放回盒子中。而這個盒子,其實就是我在這系列中一直提到的“容器”。這“容器”呢,有著超級無敵多的別稱,有 Box, Context, Effect, Container 等等,他們都是在說明著同一件事,這件事就是,將某些東西“封裝”起來了,所以我可以不理會封裝起來的東西,直接對抽象進行操作。
封裝了什麼東西就是 Monad 中的重點了,如果封裝的是 side effect, Functional Programming 能做的事情就不再被侷限住,脫離 pure function 的範圍,跨到 non-pure function 的範疇來做操作(其實最後還是變成 pure function 了)。在我們日常開發中,有很簡單的需求,不需要任何外部相依,直接對資料進行操作就能完成的需求,像是比大小、排序、特定演算法等等,這些都可以很簡單的用 pure function 來實作。但是還有另外一種需求是,要跟外部系統做一定程度的連結,像是顯示畫面,或是接收手指點擊的位置,GPS 座標,電力狀態等等,為了要完成這些需求,就會寫出 non-pure function。這時候,“容器”就是我們的救贖,藉由這樣的封裝,我們就產生出各種不同的函式,而這個函式呢,他的回傳值是這個容器( 函式的型別 T → F<T>
)。
解決了一個問題,還有下一個問題要解決,這些回傳值是容器的函式,我們還是得要有辦法把他們都組合起來,但是這些組合非常困難,因為這些函數的頭尾根本對不上( A → M<B>
下一個接 B → M<C>
,怎麼樣也對不起來),所以現在怎麼辦呢?就是這麼剛好的, Monad 提供了組合這樣函式的可能性,其中的 bind
也就是 flatMap
,函式型別是 M<A> -> (A -> M<B>) -> M<B>
,接受的輸入是一個 A → M<B>
的函式,在處理完第一個函式之後呢,還可以再用 flatMap
組合下一個 B → M<C>
的函式,至此,side effect function 的組合問題完美的被解決了,使用了"容器"再加上 Monad 的特性,就能一直將 side effect 封裝在“容器”裡面,隨意的進行組合、拆解,直到需要時,再把“盒子”給打開,做回原本應該要做的“骯髒”事。
“容器”可以看待成一個黑盒子,一個不打開就不知道裡面運作機制的盒子(IO Monad, Try Monad),也可以當成一段“小程式”,一些你不希望馬上就執行的程式片段(像是 Reader Monad 跟 State Monad),或是未來才會發生的事(Observable 跟 Promise),或是一個抽象的資料型別,因為同時也是個 Algebraic Data Type ,所以也同時享受著 Functor、Monad 所帶來的好處(List, Map, Either)。 正因為有如此多樣而廣泛的使用情境,才讓 Monad 得到重視,被這麼多 functional programming 的相關文獻一再提起。
但是說了這麼多,最終我們寫程式用到的,還是只有 flatMap
這個 operator。知道了 Monoid
的存在、了解 Monad Laws ,說實在的也不會對開發上有非常大的幫助。相對的,Monad 封裝了 side effect,安全的組合各式各樣的 function ( map
與 flatMap
,因為 Monad 本身也同時是 Functor),才是最最最重要應該要知道的事情。