Consider this code snippet:
var a: String? = "abc"
var b: String?
let result = [a, b].compactMap { $0 }
After executing it, result
will be
["abc"]
which is the expected result. The element of result (ElementOfResult
) here is String
.
print(type(of: result))
Array<String>
Now to the interesting part. After changing the snippet to
var a: String? = "abc"
var b: Int?
let result = [a, b].compactMap { $0 }
and executing it, result
will be
[Optional("abc"), nil]
The element of result (ElementOfResult
) here is Any
which makes sense, because Any
is the common denominator of String
and Int
.
print(type(of: result))
Array<Any>
Why was a nil
result returned by compactMap
which contradicts its definition?
From Apple's compactMap
documentation
compactMap(_:)
Returns an array containing the non-nil results of calling the given transformation with each element of this sequence.
Declaration
func compactMap(_ transform: (Self.Element) throws -> ElementOfResult?) rethrows -> [ElementOfResult]
Returns an array containing the non- nil results of calling the given transformation with each element of this sequence.
If you need to simply transform a value to another value, then use map . If you need to remove nil values, then use compactMap . If you need to flatten your result one level down, then use flatMap . It is also possible to chain these functions to achieve the intended result.
This is because [a, b]
is considered a [Any]
. When the element types in an array literal are entirely unrelated (Int?
and String?
), the array type is inferred to be [Any]
.
In the closure passed to compactMap
, you returned $0
, which is of type Any
. This means that $0
can never be nil
. All the optionals inside the array are all wrapped in an Any
the moment you put them in the array. Because you never return a nil
in the closure, all the elements stay in the result array.
The compiler can warn you about wrapping optionals in non-optional Any
s:
var a: String? = "abc"
let any: Any = a // warning!
But unfortunately it doesn't warn you when you create arrays.
Anyway, you can get the expected behaviour by specifying that you want a [Any?]
:
let result = ([a, b] as [Any?]).compactMap { $0 }
So you kind of unwrap them from Any
.
Or:
let result = [a as Any?, b as Any?].compactMap { $0 }
Why can an optional type be wrapped inside an
Any
?
According to the docs (In the Type Casting for Any
and AnyObject
section):
Any
can represent an instance of any type at all, including function types.
Thus, Optional<T>
undoubtedly can be represented by Any
.
You create an Any-array and compactMap over its elements gives compactMap
only Any-elements, no Optional<Any>
which it could think about being nil or not, so all emenents stay.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With