1

Swift documentation of flatMap reads:

Returns an array containing the non-nil results of calling the given transformation with each element of this sequence.

In the following examples when return type of ElementOfResult is left to the compiler to infer flatMap works as documented, yet on line 5 when ElementOfResult is specified, thus inferred to an Optional<String> type it seems that flatMap stops filtering out nil's.

Why is it doing that?

~ swift
Welcome to Apple Swift version 3.0.2 (swiftlang-800.0.63 clang-800.0.42.1). Type :help for assistance.
  1> let words = ["1989", nil, "Fearless", nil, "Red"]
words: [String?] = 5 values {
  [0] = "1989"
  [1] = nil
  [2] = "Fearless"
  [3] = nil
  [4] = "Red"
}
  2> words.flatMap { $0 }
$R0: [String] = 3 values {
  [0] = "1989"
  [1] = "Fearless"
  [2] = "Red"
}
  3> let resultTypeInferred = words.flatMap { $0 }
resultTypeInferred: [String] = 3 values {
  [0] = "1989"
  [1] = "Fearless"
  [2] = "Red"
}
  4> let resultTypeSpecified: [String?] = words.flatMap { $0 }
resultTypeSpecified: [String?] = 5 values {
  [0] = "1989"
  [1] = nil
  [2] = "Fearless"
  [3] = nil
  [4] = "Red"
}
Maxim Veksler
  • 29,272
  • 38
  • 131
  • 151
  • Remember that `flatMap(_:)` returns `[ElementOfResult]`, and the closure return is of type `ElementOfResult?`. Therefore if you annotate the return as `[String?]`, you're saying that the closure returns `String??`. Thus the optional elements are promoted to double wrapped optionals, and are all therefore `.some`, so they are 'non-`nil`'. Try annotating the return of `flatMap(_:)` as `[String]` instead, like I showed in [my answer to your previous question](http://stackoverflow.com/a/42213785/2976878). – Hamish Feb 13 '17 at 22:50
  • Also see this other Q&A: [Swift flatMap on array with elements are optional has different behavior](http://stackoverflow.com/q/37505787/2976878) (possible dupe?) – Hamish Feb 13 '17 at 22:51

1 Answers1

2

Here's the definition of flatMap()

public func flatMap<ElementOfResult>(_ transform: (Element) throws -> ElementOfResult?) rethrows -> [ElementOfResult]

When you set the type of resultTypeSpecified to [String?], you tell the compiler that ElementOfResult is Optional<String>.

Your transform closure has a type of (String?) -> Optional<Optional<String>>.

flatMap will take away 1 "layer" of optionals but not 2 layers.

Hopefully this example will makes things clearer:

let input: [String??] = [
    Optional.some(Optional.some("1989")),
    Optional.some(Optional.none),
    Optional.some(Optional.some("Fearless")),
    Optional.some(Optional.none),
    Optional.some(Optional.some("Red"))
]

let output = input.flatMap({ $0 })
Kevin
  • 16,696
  • 7
  • 51
  • 68