flatMap API 合约如何将可选输入转换为非可选结果?
Posted
技术标签:
【中文标题】flatMap API 合约如何将可选输入转换为非可选结果?【英文标题】:How flatMap API contract transforms Optional input to Non Optional result? 【发布时间】:2017-02-13 21:12:37 【问题描述】:这是 Swift 3.0.2 中 flatMap 的合约
public struct Array<Element> : RandomAccessCollection, MutableCollection
public func flatMap<ElementOfResult>(_ transform: (Element) throws -> ElementOfResult?) rethrows -> [ElementOfResult]
如果我采用 [String?]
数组,则 flatMap 返回 [String]
let albums = ["Fearless", nil, "Speak Now", nil, "Red"]
let result = albums.flatMap $0
type(of: result)
// Array<String>.Type
这里ElementOfResult
变成String
,为什么不是String?
?泛型类型系统如何从表达式中去掉 Optional 部分?
【问题讨论】:
【参考方案1】:当您使用恒等变换 $0
时,编译器将推断ElementOfResult?
(变换的结果)等价于Element
(变换的参数)。在这种情况下,Element
是 String?
,因此 ElementOfResult?
== String?
。这里不需要可选的提升,所以ElementOfResult
可以推断为String
。
因此在这种情况下flatMap(_:)
返回一个[String]
。
在内部,这种从闭包返回的ElementOfResult?
到ElementOfResult
的转换只是通过有条件地解包可选项来完成,如果成功,解包的值会附加到结果中。你可以看到exact implementation here。
作为附录,请注意as Martin points out,闭包主体仅在它们是单语句闭包时参与类型推断(请参阅this related bug report)。 Jordan Rose in this mailing list discussion 给出了这个理由:
Swift 的类型推断目前是面向语句的,因此没有简单的方法来进行 [多语句闭包] 推断。这至少部分是编译时的问题:Swift 的类型系统比 Haskell 或 OCaml 允许更多可能的转换,因此解决整个多语句函数的类型不是一个简单的问题,可能不是一个易于处理的问题。
这意味着对于具有多个语句的闭包,这些语句传递给 map(_:)
或 flatMap(_:)
(其中结果类型是通用占位符)等方法,您必须显式注释闭包的返回类型,或方法返回自身。
例如,这不会编译:
// error: Unable to infer complex closure return type; add explicit type to disambiguate.
let result = albums.flatMap
print($0 as Any)
return $0
但是这些可以:
// explicitly annotate [ElementOfResult] to be [String] – thus ElementOfResult == String.
let result: [String] = albums.flatMap
print($0 as Any)
return $0
// explicitly annotate ElementOfResult? to be String? – thus ElementOfResult == String.
let result = albums.flatMap element -> String? in
print(element as Any)
return element
【讨论】:
也许强调返回类型是为单表达式闭包自动推断的。albums.flatMap e in print(e); return e
不编译。
虽然繁重且令人惊讶,但从错误报告讨论中可以看出这实际上不是错误!
@matt 是的,它只是一个功能,可以减少你的编译时间 :) 但是考虑到类型检查器已经能够完成的其他惊人事情的纯粹列表,这完全令人惊讶做。
另一件值得注意的事情可能会解决 OP 的困惑点,那就是 flatMap
过滤了集合,使得原始集合中的 nil 元素不会出现在输出集合中。也就是说,["a", "b", nil, "c", nil].flatMap $0
接受一个类型为隐式 [String?]
的数组(因为它包含 nils)并生成 ["a", "b", "c"]
,其类型保证为 [String]
,因为它可以防止 nil 元素出现在输出中。跨度>
@MaximVeksler flatMap
做了两件完全(?)不相关的事情——我一直讨厌这样。它要么 (1) 将数组数组展平为单个数组,要么 (2) 安全解包 Optionals 数组,消除任何 nil
元素。以上是关于flatMap API 合约如何将可选输入转换为非可选结果?的主要内容,如果未能解决你的问题,请参考以下文章