最近在看一些函数式编程方面的东西,有一个概念被反复的提及: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调用:
let testAry = ["1","2","abc","4","5"]
let flatMapAry = testAry.flatMap { str in Int(str)
}
print(flatMapAry)
对比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)
对比调用map
let testAry = ["1","2","abc","4","5"]
let mapAry = testAry.map { str in Array(repeatElement(str, count: 2))
}
print(mapAry)
可以看到,闭包被作用到了数组中的每个元素上,每个元素重复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性质的。