Consider the following array.
let marks = ["86", "45", "thiry six", "76"]
I've explained my doubts in the following two cases.
Case#1
// Compact map without optionals
let compactMapped: [Int] = marks.compactMap { Int($0) }
print("\(compactMapped)")
Result - [86, 45, 76]
Case#2
// Compact map with optionals
let compactMappedOptional: [Int?] = marks.compactMap { Int($0) }
print("\(compactMappedOptional)")
Result - [Optional(86), Optional(45), nil, Optional(76)]
Why there is "nil" in the result of Case#2? Can anyone explain why is it not like this [Optional(86), Optional(45), Optional(76)] in Case#2? (PFA playground)
I submitted this behavior as a bug at bugs.swift.org, and it came back as "works as intended." I had to give the response some thought in order to find a way to explain it to you; I think this re-expresses it pretty accurately and clearly. Here we go!
To see what's going on here, let's write something like compactMap
ourselves. Pretend that compactMap
does three things:
Maps the original array through the given transform, which is expected to produce Optionals; in this particular example, it produces Int?
elements.
Filters out nils.
Force unwraps the Optionals (safe because there are now no nils).
So here's the "normal" behavior, decomposed into this way of understanding it:
let marks = ["86", "45", "thiry six", "76"]
let result = marks.map { element -> Int? in
return Int(element)
}.filter { element in
return element != nil
}.map { element in
return element!
}
Okay, but in your example, the cast to [Int?]
tells compactMap
to output Int?
, which means that its first map
must produce Int??
.
let result3 = marks.map { element -> Int?? in
return Int(element) // wrapped in extra Optional!
}.filter { element in
return element != nil
}.map { element in
return element!
}
So the first map
produces double-wrapped Optionals, namely Optional(Optional(86))
, Optional(Optional(45))
, Optional(nil)
, Optional(Optional(76))
.
None of those is nil
, so they all pass thru the filter, and then they are all unwrapped once to give the result you're printing out.
The Swift expert who responded to my report admitted that there is something counterintuitive about this, but it's the price we pay for the automatic behavior where assigning into an Optional performs automatic wrapping. In other words, you can say
let i : Int? = 1
because 1
is wrapped in an Optional for you on the way into the assignment. Your [Int?]
cast asks for the very same sort of behavior.
The workaround is to specify the transform's output type yourself, explicitly:
let result3 = marks.compactMap {element -> Int? in Int(element) }
That prevents the compiler from drawing its own conclusions about what the output type of the map function should be. Problem solved.
[You might also want to look at the WWDC 2020 video on type inference in Swift.]