上一篇文章中,我们介绍了 map 和 flatMap 在数组中的应用和实现, 这次我们继续延伸上次的注意,看一看 map 在 Optioanl 中的定义。
你还可以看一下上篇文章: 谈谈 Swift 中的 map 和 flatMap
Optional 中的 map
我们查看 Swift 的文档, 还会发现除了数组类型定义了 map 方法, 同样 Optional 也存在这个方法。 我们来看一下 Optional 中关于 map 的定义:
@H_404_37@func map<U>(@noescape f: (Wrapped) throws -> U) rethrows -> U? |
去掉一些无关项之后, 就是这样:
这个 map 方法接受这样一个闭包 (Wrapped) -> U
。 它的参数是 Wrapped 类型。 Wrapped 又是什么类型呢, 我们再进一步看 Optional 的定义:
Wrapped 其实是一个泛型, 代表的 Optional 解包后的值类型。 比如我们有这样一个变量 var a:Int?
,它是 Optional 类型,它里面的 Wrapped 类型所指的就是 Int
。
再回到我们刚才的 map 方法中, 它的闭包接受的其实是已经解包后的值。 我们来看一个实际的例子:
我们在闭包中,将 num 的值加 3, 然后返回给 added 变量。 并且闭包中我们是直接引用的参数变量 $0, 而没有使用 $0! 或者 if let
这样的解包语法。 也就是说解包的过程, map 方法已经替我们做了。
Optional 中的 map 方法, 还有一个额外效应, 就是可以帮我们解包 Optional 变量。 甚至还能帮我们过滤掉 nil 的情况:
这次 nilNum 变量中没有任何的值, map 方法就会过滤掉这个它, 闭包中的代码也不会被执行。 上面的代码相当于我们使用 if let
来解包的语法:
相比之下, 使用 map 来完成这个操作,会让代码简洁很多。
Optional 中 map 的实现原理
那么 map 方法到底是如何实现呢,它是如果将 nil 值过滤掉的呢? 还是来看看它的源码:
看,它的实现如此的简单, 它先判断了一下自身的状态, 如果是 some 状态(也就是有值), 那么就将它解包,然后传入闭包。 如果是 none 状态, 那么就直接返回 nil。 闭包根本不会执行。 关于 Optional 的基础知识这里就不多说了。
大家可以看看之前关于 Optional 的这篇文章了解: 浅谈 Swift 中的 Optionals
看完源代码之后, 就非常清楚了。 定义在 Optional 中的 map 方法, 实际上就是讲自身解包后传入闭包, 然后将闭包的返回值再次打包成 Optional 中返回的这么一个过程。
Optional 中的 flatMap
说完 map 之后,再来看看 Optional 中定义的 flatMap 方法。 还是先来看看它的定义:
去掉无关紧要的内容之后:
它的方法签名几乎和 map 相差无几, 唯一不同的就是闭包参数的返回值类型。 大家回忆一下 map 的闭包类型,是不是这样的: (Wrapped) -> U
而 flatMap 的闭包类型是这样的 (Wrapped) -> U?
。
没错! 就在最后多了一个问号。
Optional 的 flatMap 和 map 的唯一区别就是,它的闭包也可以返回一个 Optional 值, 也就是说,也可以返回 nil。
我们接下来看一下 flatMap 的例子:
这次我们使用了 flatMap, 在它的闭包中,我们将解包后的值用 Int 进行初始化, 因为 Int 构造方法的返回结果是 Optional 类型,所以我么你这里用 flatMap 是没有问题的。
为什么这样设计
看到这里, 大家可能会产生很多疑问了。 为什么多出这个 flatMap 函数? 数组和 Optional 两个看起来完全不同的东西,为何都会有 map 和 flatMap?
这其实涉及另外一个维度的概念, Functors 和 Monads。 明白这个概念之后,你就会发现这其中的关联,以及为什么会有 map 和 flatMap 这两个函数存在了。
Functors
首先来说说什么是 Functors。 我们首先要了解一个概念, 叫做封装值, 我们定义的普通变量, 比如 let intNum = 3
,这样的值不叫封装值。 我们可以直接将他们传递给函数:
intNum
是普通值, 可以将他们直接传递给 add 函数。 然而如果是这样一个变量呢:
optionalNum 就是一个封装值, 如果我要把它传递给 add 函数, 首先需要对它解包:
封装值的概念咱们就说到这里, 继续回到 Functors, 其实 Functors 就是将封装值直接传递给函数的一种行为。 对应到我们的实际代码上,就是 map 函数了,再来看一个例子:
我们虽然不能直接把 optionalNum
传递给 add 函数, 但我们可以用 map 函数将 add 作为闭包传递进来。 这样的效果实际上就等于将 optionalNum 传递给 add 函数。 也就是 Functors 所定义的将封装的值直接传递给函数这个行为了。
怎么样, 聪明的你思考一下一定会理解这其中的意义了。
如果把这个思维再发散一下, 其实数组也是一种封装值。 数组中的元素你不能直接引用的, 你需要通过下标的方式才能将这个元素从数组中”解包”出来:
如果你想把封装值直接应用 add 函数怎么办呢, 依然是用 map 方法:
这个操作实际上也是 Functors 的行为。
总的来说 Functors 就是将一个封装值直接传递给函数,并且返回的结果依然是封装值的一种行为。 我们调用定义在 Optional 的 map 函数, 会用闭包将 Optional 中的值进行操作,然后返回值还是一个 Optional。
同样,我们对数组调用 map 函数, 会用闭包将数组中的值进行一些操作, 然后返回值还是一个数组。
从这个维度来思考,是不是就能理解为什么 Optional 和数组,这两个看似没有任何关联的类型,为什么都有 map 和 flatMap 方法了?
Functors 是一个比较理论的概念,它不止存在于 Swift 中,而是一个数学概念, Swift 中的 map 函数只是这个概念的一个实现, 有兴趣的话你可以看看维基百科上对这个概念的解释 https://en.wikipedia.org/wiki/Functor。
嗯, 上升到理论层面, 还是比较抽象的哈, 欢迎大家来研究~
Monads
我们再来看看第二个概念 - Monads。 这又是什么鬼~ 简单来说嘛, Functors 对应的是 map 函数, Monads 对应的就是 flatMap 函数啦。
Monads 用一句话来说的话就是, 它将一个封装值传递给一个返回值类型是封装值的函数。
听着听绕嘴~, 我们用一个实际的例子来说明, 我们看一下刚刚定义的 add 函数的返回值类型:
是 Int 没错吧, 也就是说 add 函数的返回值类型不是封装值。 所以它是不能作为 flatMap 的闭包的。 我们回想一下, 是不是无论是 Optional 或是数组的 flatMap 方法接受的闭包参数都有一个共性,他们的返回值依然是封装置,我们再来看一下定义:
Optional 的 flatMap 函数接受的闭包是 (Wrapped) -> U?, 它返回的还是 Optional 类型。 数组的 flatMap 的两个重载接受的闭包分别是 (Self.Generator.Element) -> T? 和 (Self.Generator.Element) -> S, 他们返回的依然还是数组。
map 和 flatMap 的主要区别就是他们所接受闭包的返回类型, map 的闭包返回的是一个普通值, flatMap 的闭包返回的是一个封装值。
怎么样,从这个维度去思考,就更容易理解为什么 Swift 会这样设计它的 API 了吧。
结尾
Swift 中的 map 和 flatMap 两个函数,实际上是对 Functors 和 Monads 这两个数学概念的实现,基于这两个概念,才有了这两个函数。 也就解释了为什么 Optional 和数组这两个看似无关的类型为什么都会有 map 和 flatMap 方法。