11

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]

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
tonymontana
  • 5,728
  • 4
  • 34
  • 53
  • What is more puzzling is why the type of `result` is `[Any]`. I would have expected it to be `[Any?]` – user1046037 Aug 18 '18 at 08:50
  • 1
    @user1046037 `compactMap` is there to remove nil. What sense does it make to return [Any?] if `compactMap` already made sure none of them is nil? – Fabian Aug 18 '18 at 09:02

2 Answers2

6

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 Anys:

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.

Sweeper
  • 213,210
  • 22
  • 193
  • 313
1

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.

Fabian
  • 5,040
  • 2
  • 23
  • 35