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