Swift 2.0 :揭秘 Map 和 FlatMap

Swift 2.0 :揭秘 Map 和 FlatMap,第1张

概述作者:uraimo,原文链接,原文日期:2015-10-08 译者:靛青K;校对:Channe;定稿:shanks 这是一篇 Swift 2.0 的文章,本文源码在 GitHub ,你也可以直接下载 zipped。 Swift 依然是一个有些不稳定的语言,每次发布新版本,都带来新的功能和特性。许多人都已经写了 Swift 的函数的相关内容以及如何用更“纯”的函数式的方法处理问题。 <center>

作者:uraimo,原文链接,原文日期:2015-10-08
译者:靛青K;校对:Channe;定稿:shanks@H_404_3@

这是一篇 Swift 2.0 的文章,本文源码在 GitHub ,你也可以直接下载 zipped。@H_404_3@

Swift 依然是一个有些不稳定的语言,每次发布新版本,都带来新的功能和特性。许多人都已经写了 Swift 的函数的相关内容以及如何用更“纯”的函数式的方法处理问题。@H_404_3@

<center>

</center>@H_404_3@

考虑到 Swift 语言依然在初期状态,往往在尝试去理解一些特定的话题时,最后你会发现许多文章都是用 Swift 2.0 之前的语法,或者更糟糕的一些,内容混杂着多个版本语法。有时,搜索 flatMap 的文章,你发现不止一篇好文章都会在 Swift 中解释 Monads。@H_404_3@

相关概念缺乏全面介绍的新文章,现在的许多文章常常用一些不是通俗易懂的例子和生硬的隐喻,一些人甚至使用一些难以理解的方式思考问题。@H_404_3@

在这篇简短的文章中(这是 Swift与函数式系列文章中的一篇),我将通过引用当前库的头文件,试着对【如何在 Swift 2.0 中对不同的类型使用 mapflatMap】,给出一个清晰全面的解释。@H_404_3@ 目录

Map@H_404_3@

在 Optionals 上使用 Map@H_404_3@

在 SequenceTypes 上使用 Map@H_404_3@

FlatMap@H_404_3@

在 Optionals 上使用 FlatMap@H_404_3@

在 SequenceTypes 上使用 FlatMap@H_404_3@ Map

map 和 flatmap 方法中, map 有着更清晰的行为,它简单的对输入执行一个闭包,和 flatMap 一样,它可以用在 Optionals 和 SequenceTypes 上(如:数组、词典等)。@H_404_3@ 在 Optionals 上使用 Map

下面是 Optionals 上 map 方法的原型:@H_404_3@

public enum Optional<Wrapped> : ... {    ...    /*        如果 `self == nil` ,直接返回 `nil` 。否则返回 `f(self!)` 。    */    public func map<U>(f: (Wrapped) throws -> U) rethrows -> U?    ...}

这个 map 方法期望一个签名为 (Wrapped) -> U的闭包 ,如果这个可选值有值,那就解包并执行这个函数,之后再用一个可选值包裹这个结果并返回这个可选值(言外之意是指这是一个隐式可选解包,但这并没有引入什么不同的行为,只需要知道 map 并没有真的返回一个可选值)。@H_404_3@

注意到输出类型可以和输入类型不同,这就带来了大量有用的特性。@H_404_3@

老实说,这里不需要多余的解释了,让我们直接看这篇文章 Playground 上的代码吧:@H_404_3@

var o1:Int? = nilvar o1m = o1.map({
var a1 = [1,2,3,4,5,6]var a1m = a1.map({
/*    返回一个对 `self` 每个元素进行变换后的结果数组
* 2})a1m /* [Int] 类型,结果为 [2,6,8,10,12] */let ao1:[Int?] = [1,6]var ao1m = ao1.map({(Self.Generator.Element) throws -> T! * 2})ao1m /* [Int] 类型,结果为 [2,12] */var a1ms = a1.map({ (value) -> String in String(value * 2)}).map { (stringValue) -> Int? in Int(stringValue)}a1ms /* [Int?] 类型,结果为 [.some(2),.some(4),.some(6),.some(8),.some(10),.some(12)] */
* 2})o1m /* Int? 类型,结果为 nil */o1 = 1o1m = o1.map({map * 2})o1m /* Int? 类型,结果为 2 */var os1m = o1.map({ (value) -> String in String(value * 2)})os1m /* String? 类型,结果为 2 */os1m = o1.map({ (value) -> String in String(value * 2)}).map({"number "+flatMap})os1m /* String? 类型,结果为 "number 2" */

如果我们总是需要修改原始的可选值,使用 map 就可以保留原始的值,(map 只是在可选值有值的时候才执行这个闭包,否则就只是返回 nil)。但最令人兴奋的特性是我们可以自由的连接多个 map *** 作,他们会有序的执行,这多亏了调用 map 总是会返回一个可选值。这样,我们就能够进行可选值的链式调用了。@H_404_3@ 在 SequenceTypes 上使用 Map

但是在 SequenceTypes 上,比如数组和字典,使用 map 方法就很难跳过为空的可选值:@H_404_3@

var s1:String? = "1"var i1 = s1.map {    Int(flatMap)}i1 /* Int?? 类型,结果为 1 */var ar1 = ["1","2","3","a"]var ar1m = ar1.map {    Int(ar1)}ar1m /* [Int?] 类型,结果为 [.some(1),.some(2),.some(3),nil] */ar1m = ar1.map {    Int(map)    }    .filter({flatMap != nil})    .map {map! * 2}ar1m /* [Int?] 类型,结果为 [.some(2),.some(6)] */

这时我们调用的 map 方法在 SequenceTypes 下定义成这个样子:@H_404_3@ flatMap

复杂度: O(N).
*/@H_404_3@

map<T>(@noescape transform: (Self.Generator.Element) throws -> T) rethrows -> [T]@H_404_3@

这个变换的闭包类型 flatMap ,会应用到集合中的每个成员,之后用一个相同的类型打包进一个数组中。和上文可选值的例子一样,有序的 *** 作可以像管道(pipeline)一样在上一个 map *** 作返回的结果上调用 map。@H_404_3@

这些基本就是你可以用 flatten 做的事情了,但在开始

public enum Optional<Wrapped> : ... {    ...    /*        如果 `self` 是 nil ,直接返回 `nil` ,否则返回 `f(self!)` 。    */    public func flatMap<U>(f: (Wrapped) throws -> U?) rethrows -> U?    ...}
前,我们再看三个例子:@H_404_3@ flatMap

并不是每个 String 都可以转成 Int ,所以我们的整数转换闭包总是返回一个 Int? 类型 。那在第一个例子中发生了什么?为什么返回的是 Int?? ,也就是结尾为什么是一个可选值的可选值,在执行 map 后多了一个可选包裹。解包两次才可以得到真正包含的值,虽然不是什么大问题。但当我们需要链式添加 map *** 作符时就会显得很麻烦。我们即将看到, (Wrapped) -> U?) 会帮我们解决这个问题。@H_404_3@

在这个数组的例子中,如果一个 String 不能转换成 Int ,就像 map 的第四个值返回的就是 nil 。但是再想一下,如果我们希望在第一个 map *** 作再链式添加一个 map *** 作,这个 *** 作后能获得一个更短的、只有数字没有 nil 的数组,该怎么办呢?@H_404_3@

好了,我们只需要在中间过滤出可用的元素,并且为下一个 map *** 作准备好数据流。把这些行为嵌入到一个 flatMap 中是不是很麻烦?我们来看看另一种使用

var fo1:Int? = nilvar fo1m = fo1.flatMap({flatMap * 2})fo1m /* Int? 类型,结果是 nil */fo1 = 1fo1m = fo1.flatMap({flatten * 2})fo1m /* Int? 类型,结果是 2 */var fos1m = fo1.flatMap({ (value) -> String? in    String(value * 2)})fos1m /* String? 类型,结果是 "2" */var fs1:String? = "1"var fi1 = fs1.flatMap {    Int((拆箱)unBoxing)}fi1 /* Int? 类型,结果是 1 */var fi2 = fs1.flatMap {    Int((装订)bind)    }.map {flatMap*2}fi2 /* Int? 类型,结果是 2 */
的方法。@H_404_3@ FlatMap

/// 返回一个将变换结果连接起来的数组    ///    ///     s.flatMap(transform)    ///    /// 等价于    ///    ///     Array(s.map(transform).flatten())    ///    /// - 复杂度: O(*M* + *N*),这里的 *M* 是指 `self` 的长度    ///    *N* 是变换结果的长度    func flatMap<S : SequenceType>(transform: (Self.Generator.Element) throws -> S) rethrows -> [S.Generator.Element]    /// 返回一个包含非空值的映射变换结果    ///    /// - 复杂度: O(*M* + *N*),这里的 *M* 是指 `self` 的长度    ///   *N* 是变换结果的长度    func flatMap<T>(transform: (Self.Generator.Element) throws -> T?) rethrows -> [T]
flatMap 的差别看起来不大,但它们是有明显区别的。@H_404_3@

虽然 flatMap 依然是一个类似 map 的 *** 作,但它在 mapPing 解析后额外调用了

var fa1 = [1,6]var fa1m = fa1.flatMap({map * 2})fa1m /*[Int] 类型,结果是 [2,12] */var fao1:[Int?] = [1,nil,6]var fao1m = fao1.flatMap({flatMap})fao1m /*[Int] 类型,结果是 [1,6] */var fa2 = [[1,2],[3],[4,6]]var fa2m = fa2.flatMap({flatMap})fa2m /*[Int] 类型,结果是 [1,6] */
。让我们用类似上一节的代码来分析 flatMap 的功能。@H_404_3@ 在 Optionals 上使用 FlatMap

这个方法的定义有一些不同,但功能是相似的,只是改写了一下注释的含义:@H_404_3@

var far1 = ["1","a"]var far1m = far1.flatMap {    Int(flatMap)}far1m /* [Int] 类型,结果是 [1,3] */far1m = far1.flatMap {        Int(bind)    }    .map {map * 2}far1m /* [Int] 类型,结果是 [2,6] */

就闭包而言,这里有一个明显的不同,这次 flatMap 期望一个 闭包。@H_404_3@

对于可选值, flatMap 对于输入一个可选值时应用闭包返回一个可选值,之后这个结果会被压平,也就是返回一个解包后的结果。@H_404_3@

本质上,相比 , 也就是在可选值层做了一个解包。@H_404_3@

最后一段代码包含了一个链式调用的例子,使用 就不需要额外的解包。@H_404_3@

接下来我们再来看一看在 SequenceType 下的 *** 作,这是一个将结果压平的步骤。@H_404_3@

*** 作只有一个对嵌套的容器进行 功能。容器可以是一个数组,一个可选值或者是其他能包含一个的值的容器类型。考虑一个可选值包含另一个可选值,这和我们将在下一小节遇到的数组包含数组的情况类似。@H_404_3@

这个行为附带着单子(Monad)上的 *** 作,要了解更多可以阅读这篇以及这篇。@H_404_3@ 在 SequenceType 上使用 FlatMap

SequenceType 提供了下面默认的 实现:@H_404_3@

对序列中的每个值应用这些转换的闭包,然后将他们打包到一个和输入值类型相同新的数组。@H_404_3@

这两个注释的闭包描述了两个 功能:序列压平和可选过滤。@H_404_3@

我们来看看是什么意思:@H_404_3@

虽然第一个例子和之前使用 没什么区别,但这很清晰的让后面两个代码片段表明出它的实用性,不需要再手动的使用压平或者过滤。@H_404_3@

实际上,有许多使用 的场景会提高你的代码可读性,并且出错更少。@H_404_3@

对于上一个部分最后的代码片段的一个例子,我们现在可以使用 改进一下代码:@H_404_3@

在这个场景看起来只是一点点的改进,但随着更长的链式,使用 会极大的提高可读性。@H_404_3@

让我重申一遍,也是在这个情况下, Swift 中的 flatMap 行为与 Monads 的 是一致的(并且通常 "flatMap" 和 "bind" 是一个意思),你可以从这篇以及这篇中了解到更多。@H_404_3@

在这个系列的下篇文章 (SwiftGG 译文)你可以学到更多关于 SequenceType 和 GeneratorType 协议的知识。@H_404_3@

译者注:事实上从源码理解 和 效果可能更好一些,推荐一篇文章:Swift 烧脑体 *** (四) - map 和 flatMap 。
本文由 SwiftGG 翻译组翻译,已经获得作者翻译授权,最新文章请访问 http://swift.gg。@H_404_3@

总结

以上是内存溢出为你收集整理的Swift 2.0 :揭秘 Map 和 FlatMap全部内容,希望文章能够帮你解决Swift 2.0 :揭秘 Map 和 FlatMap所遇到的程序开发问题。

如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。

欢迎分享,转载请注明来源:内存溢出

原文地址:https://54852.com/web/1090694.html

(0)
打赏 微信扫一扫微信扫一扫 支付宝扫一扫支付宝扫一扫
上一篇 2022-05-27
下一篇2022-05-27

发表评论

登录后才能评论

评论列表(0条)

    保存