flatMap does not filter out nil when ElementOfResult is inferred to be Optional
Asked Answered
R

1

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"
}
Riyal answered 13/2, 2017 at 22:41 Comment(2)
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.Farica
Also see this other Q&A: Swift flatMap on array with elements are optional has different behavior (possible dupe?)Farica
L
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 })
Lasonde answered 13/2, 2017 at 22:58 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.