Welcome to ShenZhenJia Knowledge Sharing Community for programmer and developer-Open, Learning and Share
menu search
person
Welcome To Ask or Share your Answers For Others

Categories

This is the contract of flatMap in Swift 3.0.2

public struct Array<Element> : RandomAccessCollection, MutableCollection {
    public func flatMap<ElementOfResult>(_ transform: (Element) throws -> ElementOfResult?) rethrows -> [ElementOfResult]
}

If I take an Array of [String?] flatMap returns [String]

let albums = ["Fearless", nil, "Speak Now", nil, "Red"]
let result = albums.flatMap { $0 }
type(of: result) 
// Array<String>.Type

Here ElementOfResult becomes String, why not String? ? How is the generic type system able to strip out the Optional part from the expression?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
thumb_up_alt 0 like thumb_down_alt 0 dislike
1.3k views
Welcome To Ask or Share your Answers For Others

1 Answer

As you're using the identity transform { $0 }, the compiler will infer that ElementOfResult? (the result of the transform) is equivalent to Element (the argument of the transform). In this case, Element is String?, therefore ElementOfResult? == String?. There's no need for optional promotion here, so ElementOfResult can be inferred to be String.

Therefore flatMap(_:) in this case returns a [String].

Internally, this conversion from the closure's return of ElementOfResult? to ElementOfResult is simply done by conditionally unwrapping the optional, and if successful, the unwrapped value is appended to the result. You can see the exact implementation here.


As an addendum, note that as Martin points out, closure bodies only participate in type inference when they're single-statement closures (see this related bug report). The reasoning for this was given by Jordan Rose in this mailing list discussion:

Swift's type inference is currently statement-oriented, so there's no easy way to do [multiple-statement closure] inference. This is at least partly a compilation-time concern: Swift's type system allows many more possible conversions than, say, Haskell or OCaml, so solving the types for an entire multi-statement function is not a trivial problem, possibly not a tractable problem.

This means that for closures with multiple statements that are passed to methods such as map(_:) or flatMap(_:) (where the result type is a generic placeholder), you'll have to explicitly annotate the return type of the closure, or the method return itself.

For example, this doesn't compile:

// error: Unable to infer complex closure return type; add explicit type to disambiguate.
let result = albums.flatMap {
    print($0 as Any)
    return $0
}

But these do:

// 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
}

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
thumb_up_alt 0 like thumb_down_alt 0 dislike
Welcome to ShenZhenJia Knowledge Sharing Community for programmer and developer-Open, Learning and Share
...