天天看點

flatMap與Monad(Swift)函數定義:代碼實踐分析總結什麼是Monad

  最近在看一些函數式程式設計方面的東西,有一個概念被反複的提及:Monad.為了弄明白這個詞的含義,我看了不少的文章,但是看了半天也基本是雲裡霧裡的,似懂非懂的,感覺十分抽象。不過我注意到了一點,很多地方都提到:如果一個類型實作了flatmap,那它則具有Monad的性質。由此可見,flatmap的實作似乎可以幫助我去了解Monad的概念。而正好,Swift中Array就支援flatmap,實踐出真知,于是我就想借助swift,通過具體的代碼定義與實作,嘗試去了解Monad概念。

函數定義:

  先來看看Array中map及flatmap的函數定義

public func map<T>(_ transform: (Element) throws -> T) rethrows -> [T]
           
public func flatMap<ElementOfResult>(_ transform: (Element) throws -> ElementOfResult?) rethrows -> [ElementOfResult]
           
public func flatMap<SegmentOfResult : Sequence>(_ transform: (Element) throws -> SegmentOfResult) rethrows -> [SegmentOfResult.Iterator.Element]
           

  似乎有些複雜,去掉一些不必要的資訊

func map<T>( (Element) -> T) -> [T]
           
func flatMap<ElementOfResult>((Element) -> ElementOfResult?) -> [ElementOfResult]
           
func flatMap<SegmentOfResult : Sequence>( (Element) -> SegmentOfResult) -> [SegmentOfResult.Iterator.Element]
           

  好些了,仔細閱讀,基本上能看懂函數的定義、輸入、輸出,但還是有些抽象,下面寫幾個小例子,具體調用一下看看

代碼實踐

map調用:

let testAry = ["1","2","abc","4","5"]
let mapAry = testAry.map{
    str in str.characters.count
}
print(mapAry)
           
let testAry = ["1","2","abc","4","5"]
let mapAry = testAry.map { str in Int(str)
}
print(mapAry)
           

  可以看到,傳入一個閉包,map會将這個閉包作用于每個元素,并将處理後的元素組成一個數組傳回。其中,第二段代碼中,Int(str)傳回的是可選型(str->Int是可能失敗的),最後可以看到mapAry是一個可選型數組

flatMap與Monad(Swift)函數定義:代碼實踐分析總結什麼是Monad

flatmap調用:

let testAry = ["1","2","abc","4","5"]
let flatMapAry = testAry.flatMap { str in Int(str)
}
print(flatMapAry)
           
flatMap與Monad(Swift)函數定義:代碼實踐分析總結什麼是Monad

  對比map的調用,結果産生了明顯的差別,可選型被解包了,flatMapAry是一個Int數組   可能你也注意到了,flatmap有兩個重載,上面這個例子中我們實際調用的是

func flatMap<ElementOfResult>((Element) -> ElementOfResult?) -> [ElementOfResult]
           

  因為我們的閉包傳回的是一個可選型,而不是遵循Sequence的對象(此處可以就簡單了解為數組)     下面,我們嘗試調用一下

func flatMap<SegmentOfResult : Sequence>( (Element) -> SegmentOfResult) -> [SegmentOfResult.Iterator.Element]
           
let testAry = ["1","2","abc","4","5"]
let flatMapAry = testAry.flatMap { str in Array(repeatElement(str, count: 2))
}
print(flatMapAry)
           
flatMap與Monad(Swift)函數定義:代碼實踐分析總結什麼是Monad

  對比調用map

let testAry = ["1","2","abc","4","5"]
let mapAry = testAry.map { str in Array(repeatElement(str, count: 2))
}
print(mapAry)
           
flatMap與Monad(Swift)函數定義:代碼實踐分析總結什麼是Monad

 可以看到,閉包被作用到了數組中的每個元素上,每個元素重複2次生成一個子數組(閉包傳回的是數組Sequence),map處理後傳回了二維數組(子數組的集合);而flatmap處理後則傳回的是一個數組,數組中每個元素重複2次。

分析總結

  下面我們來分析總結一下map與flatmap的差別   map和flatmap都是将一個閉包(函數),分别作用于Array中的每一個元素,然後産生一個新Array輸出,二者的差異主要展現在了閉包的定義及最終的函數傳回值處理兩個方面上。   先來看看閉包定義上的差異:

func map<T>( (Element) -> T) -> [T]
           

  map中閉包的定義(Element)->T,對每個element産生作用,并将每個element轉化為了T,對于數組來說,element就是數組中的每一個元素,map的閉包作用于每個元素并産生了新的元素T,相當于元素到元素之間的轉換。

func flatMap<ElementOfResult>((Element) -> ElementOfResult?) -> [ElementOfResult]        (1)
           
func flatMap<SegmentOfResult : Sequence>( (Element) -> SegmentOfResult) -> [SegmentOfResult.Iterator.Element]     (2)
           

  再來看看flatmap,定義(1)裡,閉包處理完每個元素,傳回的是可選型(ElementOfResult?), 定義(2)裡,閉包處理完每個元素,傳回的是數組(SegmentOfResult:Sequence), flatmap的閉包作用于每個元素,而産生的則是另一種包裝後的類型。   最終在map及flatmap的輸出上,因為map中閉包做的是元素到元素之間的轉換,最終map在輸出上,直接也就是以新元素(T)組成了一個新數組輸出,沒有做任何别的中間環節處理。而上面我的例子也印證了它的定義,将str轉換成Int,産生一個可選型,最後結果就是可選型數組;将每個元素重複兩遍生成數組,最後結果就是數組的數組(二維數組)。   而flatmap的閉包做的是元素到一個新封裝類型之間的轉換,flatmap則針對兩種不同實作,分别在結果數組傳回前做了解包以及子數組周遊、歸一的處理(屏蔽掉了中間由閉包産生的ElementOfResult?及SegmentOfResult),最終輸出一個新的數組。   而上面這些,我們看到的定義,得到的結果,看到的差異,和Monad又有什麼關系呢?

什麼是Monad

  先看看别的大牛博文裡對Monad的描述:

Monad:應用會傳回封裝過的值的函數到封裝過的值(這句子太拗口了,我用不同顔色标注出應該怎麼斷句......)

  套用在flatmap上我們看看是什麼意思

func flatMap<ElementOfResult>((Element) -> ElementOfResult?) -> [ElementOfResult]       
           
func flatMap<SegmentOfResult : Sequence>( (Element) -> SegmentOfResult) -> [SegmentOfResult.Iterator.Element]   
           

  會傳回封裝過的值的函數:要求函數的傳回值要是一個封裝過的值類型.上面兩個閉包(閉包就是函數)(Element) -> ElementOfResult?、(Element) -> SegmentOfResult,它們一個傳回ElementOfResult?(可選型),一個傳回SegmentOfResule(Sequence數組),可選型與數組都不是單純的值類型,它們都是經過自定義、封裝好的“進階”類型。   到封裝過的值:這個閉包是作用于Array上的,Array是一個封裝過的類型   其實Monad描述的主要是針對閉包的要求,就Array而言,Array是封裝類型,其裡面的每個元素就是未封裝的類型,map中的閉包是元素(element)(未封裝值)到元素(T)(未封裝值)的轉換。而flatMap中的閉包則是元素(element)(未封裝值)到一個封裝值(ElementOfResult?、SegmentOfResult)的轉換,這其實就正好符合了上面所說的Monad的描述,會傳回封裝過的值的函數,是以flatMap就是符合Monad的。   上一部分中還提到了map、flatmap函數最終在傳回值的處理上也有差異,flatmap針對ElementOfResult?及SegmentOfResult,做了解包及子數組周遊、歸一的操作,其實這不屬于Monad的定義範疇,但又與之相關,正是因為flatmap中的閉包産生了新的封裝過的值,是以在函數最終傳回時,有必要(非必需)對這個中間值(封裝過的值)進行内部的轉化與處理(使其對調用者透明),傳回一個我們預期的,新的最終目标結果。

  仔細體會體會,最後寫寫我個人的了解:   我們可以對一個封裝過的類型(Array)拆包并進行計算(閉包、函數),計算後産生的結果又會是一個封裝過的類型(ElementOfResult?、SegmentOfResult),這其實就是Monad. Monad引入了新的封裝類型,但不怕,在内部我們可以對這個中間類型進行解包,并把解包出來的值,再次封裝成另一個我們預期的封裝類型(非必需)。預期的封裝類型同樣具有Monad能力(上述能力),我們又可以循環往複上面的操作,最後經過一連串不同的計算處理,得到我們最終的想要的結果,而整個過程中,我們隻需要把每一步的結果層層傳遞即可,每個環節内部的拆包、計算、容錯、再封包等等細節都不需要我們關心,也不會對外界産生影響,完全的隔離、封閉。   Monad其實就是一個規則,一種能力的要求,可以類比成協定的概念。如果一個類遵循了Monad相應的要求,不管是通過flatmap(還是叫什麼别的都好),提供了相應要求的能力,就是具備Monad性質的。