重新理解 Monad
對于大多數(shù)剛剛?cè)腴T函數(shù)式編程的同學(xué)來說,monad(單子、又叫單體)可能是這里面的一道坎。你可能對 map 、 flatMap 以及 filter 再熟悉不過,可是到了高階的抽象層次上就又會變得一臉懵逼。其實每個人在學(xué)習(xí)的階段都會經(jīng)歷這個過程,不過希望這篇文章能讓你重新理解 monad 以及其他相關(guān)的概念。
Optional
Swift 作為一門類型安全的強類型語言,它在編譯階段就會對你的數(shù)據(jù)類型進(jìn)行比較多的檢查。因此,在 Swift 中我們遇到了一種新的數(shù)據(jù)類型,叫做?Optional?。它的定義如下:
Optional 是個枚舉類型,可以看到它有兩個值: none 以及 some 。簡單點講:
值要么存在(presence),要么不存在(absence)。
這也就意味著 Optional 可能是包含了一個某個類型的值( some ),也可能是什么都沒有( none )。在這里,Optional 就是個容器( container )。
對于基礎(chǔ)類型,在其后面直接加上 ? 就代表了這就是個可選值類型(Optional value)。默認(rèn)值即為 Optional 的預(yù)設(shè)值 nil (區(qū)別于 Objective-C)。
判斷一個可選值是否為空,我們通常會采用 if let 的寫法來解包(wrap an optional)。
Map
基礎(chǔ)類型的 map 函數(shù)調(diào)用是非常簡單的:
如果對于 Optional 呢?
我們發(fā)現(xiàn), map 函數(shù)作用在 Optional 上時:
- 值存在( .some ):值類型 Optional(Int) ,返回值類型 Optional(Int) ;
- 值不存在( .none ):返回值等同輸入( nil )。
map 函數(shù)定義在 Optional 上,最大的好處就在于空值( nil )的處理,不需要我們再去使用 if let 解包(空值并沒有乘法運算):
因此:
map 只對值存在的可選值進(jìn)行處理。
map 通常的表示方法:
在這里,容器 Container 就相當(dāng)于 Optional ,泛型 T 和 U 均為 Int 類型。在處理完 Int 值后 map 函數(shù)就把 Int 型轉(zhuǎn)換成了 Optional(Int) ,并返回。
Async Callback Trouble
處理異步的網(wǎng)絡(luò)請求是一件痛苦的事情。你一定碰到過:
為什么我的異步回調(diào)就沒有一種方式能夠告訴我在哪里出錯了呢?方案其實也很簡單,我們先在定義一下異步處理的結(jié)果( Result ):
有沒有發(fā)現(xiàn) Result 類型很像 Optional ?沒錯。它能包含一個成功的返回值;也能在沒有返回值時提供一個錯誤消息。
我們也同時希望 map 能幫我們處理 Result :如果有結(jié)果,就從 JSON 轉(zhuǎn)換到 String 、再轉(zhuǎn)換到其他類型;否則返回錯誤信息。
這樣的 map 函數(shù)怎么寫呢?不妨先來看一下:
舉個最基本的例子,我們希望將返回的 JSON 轉(zhuǎn)換成 String ,那在這里, map 所接受的高階變換 f 就是一個 JSON -> String 的函數(shù)。調(diào)用時, Result<JSON> 就會通過 map 最終轉(zhuǎn)換成 Result<String> 類型。
看上去很不錯!
Functor
在了解 monad 之前,我們先來了解一下它的孿生兄弟:functor(函子)。
從上面的例子中可以看到,在調(diào)用 map 函數(shù)后,我們還會把 String 類型的結(jié)果封裝成了一個可選值 Result<String> 。
像這樣能夠從容器(Container,這里即 Result )中取出元素,并通過某個函數(shù)將其轉(zhuǎn)換成可以再次被容器包裝的結(jié)果的類型就稱之為 functor。
還有些不懂?沒事,暫時就先記住有 functor 這么個玩意兒。
FlatMap
重新回到之前 JSON -> String 的例子上來。假設(shè)我們已經(jīng)將某個 json 轉(zhuǎn)換成了字符串,現(xiàn)在需要將字符串重新格式化,那我們應(yīng)該需要再調(diào)用一次 map :
不過我們多么希望返回的結(jié)果是個 Result<String> 的類型。不如寫一個函數(shù)來解包帶有兩層的 Result<T> 。
還有一點,在寫 flatten 函數(shù)的時候,我們也同時考慮了在 map 函數(shù)中出現(xiàn)轉(zhuǎn)換失敗的問題。轉(zhuǎn)換正確的時候的確我們的 map 的輸出是個 String 類型的值,隨之輸出 Result<String> 進(jìn)入下一層的 map ;如果失敗,則應(yīng)當(dāng)是被轉(zhuǎn)換成 .failure 的結(jié)果。
將 map 和 flatten 結(jié)合一下,我們就得到了所謂的 flatMap (又稱作 bind ):
通過 flatMap 我們可以非常輕松地處理中途出現(xiàn)的錯誤異常,并對給定類型進(jìn)行多次連續(xù)的類型轉(zhuǎn)換。
Monad
最后再來說什么是 monad。Chris Edihof 曾在他的文章中指出:
如果可以為某個類型定義它的 flatMap 方法,那么這個類型通常就是個 monad 。(If you can define flatMap for a type, the type is often called a monad .)
在這里,我們通過 map 和 flatten 實現(xiàn)了 Result 類型的 flatMap 。此時,我們就可以說 Result 這個類型就是一個 monad。
Deal with Monad
到現(xiàn)在你就可以非常輕松地處理你的異步請求了。
Summary
-
重新回顧一下 map 和 flatMap 在 Result<T> 上的工作方式:
-
Functor、monad 可以看作是一種運算的抽象。它們的目的都是為了更好的解決類型的封裝和轉(zhuǎn)換。
Further Reading
-
ReactiveCocoa
-
Promise & Future
-
Swift 中的 throw 及 rethrow
References
?
來自:http://blog.cee.moe/recap-monad.html
http://www.open-open.com/lib/view/open1478588965804.html
總結(jié)
以上是生活随笔為你收集整理的重新理解 Monad的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: KZWFoudation系列之Route
- 下一篇: Hadoop生态圈-Hive的自定义函数