我们知道 Swift 语言支持函数式编程范式,所以函数式编程的一些概念近来比较火。有一些相对于 OOP 来说不太一样的概念,比如 Applicative
, Functor
以及今天的主题 Monad
. 如果单纯的从字面上来看,很神秘,完全不知道其含义。中文翻译叫做 单子
,但是翻译过来之后对于这个词的理解并没有起到任何帮助。
我的理解很简单, Functor
是实现了 map
函数的容器, Monad
就是实现了 flatMap
方法的容器,比如在 Swift 里, Optional
, CollectionType
等等都可以称为 Monad。
既然有了 map, flatMap 又有什么作用呢?两者有什么联系和区别呢?
map 和 flatMap 的共同点都是接受一个 transform 函数,把一个容器转换为另外一个容器。
下面主要从 维度
这一块来解释两者的区别,我们先来简单的定义一下 维度
:
对于类型 T,如果类型 S 是一个容器,且元素的类型包含 T,那我们就说:S(维度) = T(维度) + 1
举个:chestnut:, [Int] (维度) = Int (维度) +1, Int? (维度) = Int(维度) + 1.
map 和 flatMap 的区别是,对于 map,容器里的一个元素经过 transform 后只产生一个元素,是 one-to-one 的关系,也就是说经过转换后,纬度是不变的。比如:
var intArray: [Int] = [1, 2, 3, 4, 5]
var stringArray: [Int] = intArray.map { (value: Int) -> String in
return "/(value)"
}
//stringArray: ["1", "2", "3", "4", "5"]
这个 transform 函数的是 Int -> Int
的,两边的维度是一致的。
对于 flatmap,容器里的一个元素经过 transform 可能转换成 0 个,1 个 或者多个元素,也就是 one-to-any 的关系,既然是 any 的关系,就需要一个容器来存放 any 个元素,所以经过 transform 的返回值通常是一个容器,所以 transform 函数执行之后,相当于维度+1。
var oddIntArray: [Int] = intArray.flatMap { (value: Int) -> Int? in
return value % 2 == 1 ? value : nil
}
//oddIntArray: [1, 3, 5]
这里的 transform 是 Int -> Int?
的,我们知道 Int? 是 Int 的包装类型,所以说 transform 相当于对每个元素都包了一层,提升了一个维度.
但是我们看一下上面例子里 stringArray 和 oddIntArray 的类型,都是 [Int],也就是说 flatMap 函数对 transform 函数的返回值做了降维处理。那么 flat 的意思在这里也就知道了,就是把 transform 返回的容器 降维攻击
(拍扁),拿出里面的元素。
flatMap 函数为什么要这么做呢?在函数式编程中,通常会对一个值 / 操作进行链式操作,为了保证后面还可以继续方便的进行链式操作,一般需要保持维度不变。其实可以看作一个约定,大家都遵循一定的规则,才都有得玩。
从上面可以看到 map 对 transform 的返回值没有做特殊的处理,flatMap 对于 transform 的返回值会做降维处理,比如 unwrap optional 值等。
其实可以反推,如果给定的 transform 函数会对调用者容器里的每个元素做升维,那我们需要用 flatMap 对它的结果进行降维,来保证调用 flatMap 前后维度保持一致。如果说 transform 调用前后维度没有变化,使用 map 方法就行了。
首先看看 Optional<Wrapped> 的 map 和 flatMap 方法:
/// If `self == nil`, returns `nil`. Otherwise, returns `f(self!)`.
@warn_unused_result
public func map<U>(@noescape f: (Wrapped) throws -> U) rethrows -> U?
/// Returns `nil` if `self` is `nil`, `f(self!)` otherwise.
@warn_unused_result
public func flatMap<U>(@noescape f: (Wrapped) throws -> U?) rethrows -> U?
map 的 transform 是 Wrapped -> U
维度不变, flatMap 的 transform 方法是 Wrapped -> U?
,维度 +1
继续看看 CollectionType:
public func map<T>(@noescape transform: (Self.Generator.Element) throws -> T) rethrows -> [T]
public func flatMap<T>(@noescape transform: (Self.Generator.Element) throws -> T?) rethrows -> [T]
public func flatMap<S : SequenceType>(transform: (Self.Generator.Element) throws -> S) rethrows -> [S.Generator.Element]
有一个 map 函数和两个 flatMap, map 的 transform 函数是 Element -> T
维度不变,两个 flatMap 的 transform 函数分别是 Element -> T?
和 Element -> S: SequenceType
,维度都升了一级。
特别感谢我的同事王轲, 本文的很多思路都得益于和他的讨论。