Why doesn’t compactMap() exist?

If I just want to get the non-nil elements of an optional collection (and fix it’s type to be of non-optional elements) it seems the best way is

let x: [Int?] = [1,2,3,nil,5]
let y = x.compactMap { $0 }

Why isn’t the default parameter of compactMap the closure { return $0 } so we could just call compactMap() instead?

3 Likes

I find it a brilliant idea, +1 :slightly_smiling_face:

1 Like

Afair there have been plans to add compact() — don't know why it is not here yet, though.

1 Like

Certainly map in the name indicates the application of a transform closure, and the name for this operation, if it doesn't take a closure, likely ought to be different.

5 Likes

That's a very good point that I did not consider. But e.g. compact() (or something similar) still does not exist, or does it?

It seems like there must have been a discussion before or maybe even a reason why it shouldn't exist or why it's not necessary (e.g. because there is a better way).

IMO a slightly more elegant solution is to have a func id<T>(_ v: T) -> T { v } in the standard library which enable us to write blah.compactMap(id). This id/identity function can further compose with operators in ReactiveX libraries, for example.

2 Likes

with SE-0249 we should be able to use the keypath \.self in this way
blah.compactMap(\.self)

But that still feels like more work than necessary.

3 Likes

compact() would strip out nulls from a container. compacted() would return a new container that's been stripped. IMO.

6 Likes

You’re proposing a method called compactMap() that doesn’t actually map anything, merely filters out nils.

If compactMap had never existed, being composible instead from map and some sansNils, then you would just use sansNils in isolation for this purpose.

The existence of compactMap is a bit of a mistake, I think - one people largely accept because it’s still fairly practical. But making it worse would be, well, even worse.

Sidenote: I don’t like the use of the term compact in any of this, because it’s way too ambiguous - it doesn’t say to me “removes nilspecifically. Why doesn’t it mean to reallocate the underlying memory as one contiguous block? Or switch the internal representation from a linked list to an array? Or resize the backing memory to exactly the size of the current contents? Or apply bzip2 compression? Or reduce the type of the collection’s contents to the smallest type that still holds all the values (e.g. Int -> Int8)? Or remove duplicates? All of those are forms of compaction. It seems it only exists because somehow it got established as convention - some kind of chicken-and-egg paradox.

3 Likes

It's totally possible to implement compact() as shown e.g. in SE-0218 — Introduce compactMapValues to Dictionary - #20 by pyrtsa.

But in absense of parameterised generics (something like extension<T> Sequence where Element == T? { ... }), I think overeager autocompletion might still be the issue preventing the adoption of this somewhat hacky implementation in the stdlib; we don't want to suggest xs.compact() when xs is not a sequence of optionals.

2 Likes

I was just thinking this a few days ago... Seemed strange. :)

IIRC we don't have compact because we don't have higher-kinded-types in Swift yet. If you use compact on a Set you want that the result remains a Set and is not transformed to an Array.


The best workaround right now is as already mentioned above compactMap(\.self), but since the proposal that enabled that feature didn't land yet you may need to create a custom operator for the time being and write compactMap(^\.self).

1 Like

since the proposal that enabled that feature didn't land yet

It's available on master.

3 Likes

Oh so it got recently merged after all. Thank you for mentioning that.

1 Like