谈谈 Swift 中的 map 和 FlatMap 第二篇 - 另一层思维方式

前端之家收集整理的这篇文章主要介绍了谈谈 Swift 中的 map 和 FlatMap 第二篇 - 另一层思维方式前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。

上一篇文章中,我们介绍了 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<U>(f: (Wrapped) -> U) -> U?



这个 map 方法接受这样一个闭包 (Wrapped) -> U 。 它的参数是 Wrapped 类型。 Wrapped 又是什么类型呢, 我们再进一步看 Optional 的定义:



@H_404_37@public enum Optional<Wrapped> {
@H_404_37@    case None
@H_404_37@    case Some(Wrapped)
@H_404_37@}



Wrapped 其实是一个泛型, 代表的 Optional 解包后的值类型。 比如我们有这样一个变量 var a:Int?,它是 Optional 类型,它里面的 Wrapped 类型所指的就是 Int


再回到我们刚才的 map 方法中, 它的闭包接受的其实是已经解包后的值。 我们来看一个实际的例子:



@H_404_37@var num:Int? = 2
@H_404_37@var added = num.map { $0 + 3 } // 5



我们在闭包中,将 num 的值加 3, 然后返回给 added 变量。 并且闭包中我们是直接引用的参数变量 $0, 而没有使用 $0! 或者 if let 这样的解包语法。 也就是说解包的过程, map 方法已经替我们做了。


Optional 中的 map 方法, 还有一个额外效应, 就是可以帮我们解包 Optional 变量。 甚至还能帮我们过滤掉 nil 的情况:



var nilNum:Int?
@H_404_37@var addResult = nilNum.map{ $// nil



这次 nilNum 变量中没有任何的值, map 方法就会过滤掉这个它, 闭包中的代码也不会被执行。 上面的代码相当于我们使用 if let 来解包的语法:



var nilNum:Int?
@H_404_37@
@H_404_37@var addResult:Int?
@H_404_37@
@H_404_37@if let num = nilNum {
@H_404_37@    
@H_404_37@    addResult = num
@H_404_37@
@H_404_37@}



相比之下, 使用 map 来完成这个操作,会让代码简洁很多。


对 map 方法合理的使用,能让我们的代码看起来更加舒服。


Optional 中 map 的实现原理


那么 map 方法到底是如何实现呢,它是如果将 nil 值过滤掉的呢? 还是来看看它的源码:



@H_404_37@public map<U>(@noescape f: (Wrapped) throws -> U) rethrows -> U? {
@H_404_37@    
@H_404_37@    switch self {
@H_404_37@    case .some(let y):
@H_404_37@        return .some(try f(y))
@H_404_37@    case .none:
@H_404_37@        return .none
@H_404_37@    }
@H_404_37@    
@H_404_37@}



看,它的实现如此的简单, 它先判断了一下自身的状态, 如果是 some 状态(也就是有值), 那么就将它解包,然后传入闭包。 如果是 none 状态, 那么就直接返回 nil。 闭包根本不会执行。 关于 Optional 的基础知识这里就不多说了。


大家可以看看之前关于 Optional 的这篇文章了解: 浅谈 Swift 中的 Optionals


看完源代码之后, 就非常清楚了。 定义在 Optional 中的 map 方法, 实际上就是讲自身解包后传入闭包, 然后将闭包的返回值再次打包成 Optional 中返回的这么一个过程。


Optional 中的 flatMap


说完 map 之后,再来看看 Optional 中定义的 flatMap 方法。 还是先来看看它的定义:



flatMap<U>(@noescape f: (Wrapped) throws -> U?) rethrows -> U?



去掉无关紧要的内容之后:



flatMap<U>(f: (Wrapped) -> U?) -> U?



它的方法签名几乎和 map 相差无几, 唯一不同的就是闭包参数的返回值类型。 大家回忆一下 map 的闭包类型,是不是这样的: (Wrapped) -> U 而 flatMap 的闭包类型是这样的 (Wrapped) -> U?


没错! 就在最后多了一个问号。


Optional 的 flatMap 和 map 的唯一区别就是,它的闭包也可以返回一个 Optional 值, 也就是说,也可以返回 nil。


我们接下来看一下 flatMap 的例子:



var value: Double? = 10
@H_404_37@var intVal = value.flatMap { Int($0) }



这次我们使用了 flatMap, 在它的闭包中,我们将解包后的值用 Int 进行初始化, 因为 Int 构造方法的返回结果是 Optional 类型,所以我么你这里用 flatMap 是没有问题的。


为什么这样设计


看到这里, 大家可能会产生很多疑问了。 为什么多出这个 flatMap 函数? 数组和 Optional 两个看起来完全不同的东西,为何都会有 map 和 flatMap?


这其实涉及另外一个维度的概念, Functors 和 Monads。 明白这个概念之后,你就会发现这其中的关联,以及为什么会有 map 和 flatMap 这两个函数存在了。

  1. Functors


首先来说说什么是 Functors。 我们首先要了解一个概念, 叫做封装值, 我们定义的普通变量, 比如 let intNum = 3,这样的值不叫封装值。 我们可以直接将他们传递给函数



add(num: Int) -> Int{
@H_404_37@    return num + 1
@H_404_37@}
@H_404_37@var intNum = 1
@H_404_37@
@H_404_37@add(intNum) // 2



intNum 是普通值, 可以将他们直接传递给 add 函数。 然而如果是这样一个变量呢:



var optionalNum:Int? = 1
@H_404_37@add(optionalNum) //报错



optionalNum 就是一个封装值, 如果我要把它传递给 add 函数, 首先需要对它解包:



@H_404_37@add(optionalNum!)



封装值的概念咱们就说到这里, 继续回到 Functors, 其实 Functors 就是将封装值直接传递给函数的一种行为。 对应到我们的实际代码上,就是 map 函数了,再来看一个例子:



1
@H_404_37@}
@H_404_37@
@H_404_37@1
@H_404_37@optionalNum.map(add) // 2



我们虽然不能直接把 optionalNum 传递给 add 函数, 但我们可以用 map 函数将 add 作为闭包传递进来。 这样的效果实际上就等于将 optionalNum 传递给 add 函数。 也就是 Functors 所定义的将封装的值直接传递给函数这个行为了。


怎么样, 聪明的你思考一下一定会理解这其中的意义了。


如果把这个思维再发散一下, 其实数组也是一种封装值。 数组中的元素你不能直接引用的, 你需要通过下标的方式才能将这个元素从数组中”解包”出来:



var array = [1,2,152);">3]
@H_404_37@add(array[1]) // 3



如果你想把封装值直接应用 add 函数怎么办呢, 依然是用 map 方法



let res = array.// [2,3,4]



这个操作实际上也是 Functors 的行为。


总的来说 Functors 就是将一个封装值直接传递给函数,并且返回的结果依然是封装值的一种行为。 我们调用定义在 Optional 的 map 函数, 会用闭包将 Optional 中的值进行操作,然后返回值还是一个 Optional。


同样,我们对数组调用 map 函数, 会用闭包将数组中的值进行一些操作, 然后返回值还是一个数组。


从这个维度来思考,是不是就能理解为什么 Optional 和数组,这两个看似没有任何关联的类型,为什么都有 map 和 flatMap 方法了?


Functors 是一个比较理论的概念,它不止存在于 Swift 中,而是一个数学概念, Swift 中的 map 函数只是这个概念的一个实现, 有兴趣的话你可以看看维基百科上对这个概念的解释 https://en.wikipedia.org/wiki/Functor


嗯, 上升到理论层面, 还是比较抽象的哈, 欢迎大家来研究~

  1. Monads


我们再来看看第二个概念 - Monads。 这又是什么鬼~ 简单来说嘛, Functors 对应的是 map 函数, Monads 对应的就是 flatMap 函数啦。


Monads 用一句话来说的话就是, 它将一个封装值传递给一个返回值类型是封装值函数


听着听绕嘴~, 我们用一个实际的例子来说明, 我们看一下刚刚定义的 add 函数的返回值类型:



1
@H_404_37@}



是 Int 没错吧, 也就是说 add 函数的返回值类型不是封装值。 所以它是不能作为 flatMap 的闭包的。 我们回想一下, 是不是无论是 Optional 或是数组的 flatMap 方法接受的闭包参数都有一个共性,他们的返回值依然是封装置,我们再来看一下定义:



@H_404_37@// Optional
@H_404_37@public flatMap<U>(f: (Wrapped) -> U?) -> U?
@H_404_37@
@H_404_37@// 数组
@H_404_37@flatMap<T>(transform: (Self.Generator.Element) -> T?) -> [T]
@H_404_37@flatMap<S : SequenceType>(transform: (Self.Generator.Element) -> S) -> [S.Generator.Element]



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 方法

原文链接:https://www.f2er.com/swift/821372.html

猜你在找的Swift相关文章