As @Adam says, it's due to the explicit type that you're supplying for your result. In your second example, this is leading to confusion caused by double wrapped optionals. To better understand the problem, let's take a look at the flatMap
function signature.
@warn_unused_result
public func flatMap<T>(@noescape transform: (Self.Generator.Element) throws -> T?) rethrows -> [T]
When you explicitly specify that the result is of type [Int?]
, because flatMap
returns the generic type [T]
– Swift will infer T
to be Int?
.
Now this causes confusion because the closure that your pass to flatMap
takes an element input and returns T?
. Because T
is Int?
, this closure is now going to be returning T??
(a double wrapped optional). This compiles fine because types can be freely promoted to optionals, including optionals being promoted to double optionals.
So what's happening is that your Int?
elements in the array are getting promoted to Int??
elements, and then flatMap
is unwrapping them back down to Int?
. This means that nil
elements can't get filtered out from your arr1
as they're getting doubly wrapped, and flatMap
is only operating on the second layer of wrapping.
Why arr2
is able to have nil
filtered out of it appears to be as a result of the promotion of the closure that you pass to flatMap
. Because you explicitly annotate the return type of the closure to be Int?
, the closure will get implicitly promoted from (Element) -> Int?
to (Element) -> Int??
(closure return types can get freely promoted in the same way as other types) – rather than the element itself being promoted from Int?
to Int??
, as without the type annotation the closure would be inferred to be (Element) -> Int??
.
This quirk appears to allow nil
to avoid being double wrapped, and therefore allowing flatMap
to filter it out (not entirely sure if this is expected behaviour or not).
You can see this behaviour in the example below:
func optionalIntArrayWithElement(closure: () -> Int??) -> [Int?] {
let c = closure() // of type Int??
if let c = c { // of type Int?
return [c]
} else {
return []
}
}
// another quirk: if you don't explicitly define the type of the optional (i.e write 'nil'),
// then nil won't get double wrapped in either circumstance
let elementA : () -> Int? = {Optional<Int>.None} // () -> Int?
let elementB : () -> Int?? = {Optional<Int>.None} // () -> Int??
// (1) nil gets picked up by the if let, as the closure gets implicitly upcast from () -> Int? to () -> Int??
let arr = optionalIntArrayWithElement(elementA)
// (2) nil doesn't get picked up by the if let as the element itself gets promoted to a double wrapped optional
let arr2 = optionalIntArrayWithElement(elementB)
if arr.isEmpty {
print("nil was filtered out of arr") // this prints
}
if arr2.isEmpty {
print("nil was filtered out of arr2") // this doesn't print
}
Moral of the story
Steer away from double wrapped optionals, they can give you super confusing behaviour!
If you're using flatMap
, then you should be expecting to get back [Int]
if you pass in [Int?]
. If you want to keep the optionality of the elements, then use map
instead.