flatMap API契约如何将可选输入转换为非可选结果?

时间:2017-02-13 21:12:37

标签: swift generics functional-programming optional

这是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 :(得分:4)

当您使用标识转换{ $0 }时,编译器将推断ElementOfResult?(转换的结果)等效于Element(转换的参数)。在这种情况下,ElementString?,因此ElementOfResult? == String?。这里不需要可选的促销,因此ElementOfResult可以推断为String

因此,flatMap(_:)在这种情况下会返回[String]

在内部,从闭包返回ElementOfResult?ElementOfResult的这种转换只需通过有条件地解包可选项来完成,如果成功,则将未包装的值附加到结果中。您可以看到exact implementation here

作为附录,请注意as Martin points out,闭包只有在单一陈述闭包时才会参与类型推断(请参阅this related bug report)。乔丹·罗斯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
}