Mapping non-nil elements in Swift Array

Nobody said it was easy, no one ever said it would be so hard.

Hilton Pintor
Apple Developer Academy | UFPE
4 min readJul 17, 2019

--

Cool image to appear on the thumbnail.

So, let’s say we have an array of optional elements, and we want to do two things:

  1. Remove all elements that are nil;
  2. Map all elements that are not nil;

There are a few ways to do that in Swift, and I’m gonna walk us through them, and why I decided to make my approach.

Example problem case:

We have a function that we want to apply to all numbers of an array of Optionals, resulting in an array with only the resulting mapped numbers:

1. Plain old for loop:

Probably the first solution that comes to mind is our old friend, the for loop. And that might look something like this:

It works fine, but we have to make doubledNumbers a mutable variable and use the forced unwrapping which I’ve never been a fan.

With the for loop approach we can’t get rid of mutability, but we can avoid the forced unwrapping:

We traded mutability for an increase in the cognitive load by using the continue instruction, and also, this mutable array is not going to let me sleep at night, so let’s try and find another solution.

2. Filtering and mapping:

Let’s turn to our functional friends: filter and map. So we can get rid of those pesky nils and keep our data structures immutable:

We now achieved immutability but reintroduced the forced unwrapping on the map function, since we can’t skip iterations using the continue instruction as done before.

Fortunately, Swift is our friend, and it gives us a function that looks promising: compactMap(_:).

3. Compact mapping

The Swift docs describe compactMap as:

Returns an array containing the non-nil results of calling the given transformation with each element of this sequence.

In other words, it applies a function to each element of the array, and it drops the nil values, precisely what we set out to accomplish. But if we try passing our double function to compactMap then the compiler is not going to be too happy with us:

The reason we can't do this is because double is of type (Int) -> Int but compactMap expects a function that can handle the Optionals in our numbersWithNils array and that might also return nil which means, in our case, that the function needs to be (Int?) -> Int?.

I don't feel comfortable changing the double function since there is no reason for it to receive accept an optional parameter other than the one we're facing currently. So this is the best thing we can do with compactMap for now:

This seems like a good solution since there is no more mutability or forced unwrapping. But I still wasn't satisfied.

Applying a function (that does not accept Optionals) to an array of Optionals, removing the nil values seem like a necessity that might be reoccurring, so in order to eliminate the boilerplate of the solution above, I asked friends for help, and we found a way to generalize it.

4. Convenience method (our approach)

Besides the functionality we already have, the goal now is to generalize it to work with any array of Optionals, and with no boilerplate. So let's add a method to the Array class, via an extension:

The new mapNonNils function works for every array, assuming that it's elements are of type Optional<E> and that the transform parameter maps an element of type E to another of type T. BeingE and T any types (generics FTW!).

So now we can solve our initial problem in the following way:

Let's evaluate this solution:

  • Removes all elements that are nil;
  • Maps all elements that are not nil;
  • No mutability;
  • No forced unwrapping;
  • Single loop;
  • Works for every type and function;
  • No boilerplate required;

Community improvements

The solutions and improvements bellow came from the community, in order to build an even better solution. This list will be updated as I receive more feedback.

1. Daniel Oliveira's Optional map:

The first improvement came while reviewing the draft of this article. Daniel astutely pointed out that Swift provides a map function for Optionals. This map applies the transform function only if the Optional instance is not nil, otherwise, it returns nil.

This not very well known feature goes really well with compactMap, making our code more concise:

Some might even argue that this makes removes the need for the extension, since now the boilerplate is very small. But for me, it's still boilerplate and providing a method that abstracts all boilerplate gives a more convenient API for developers. Calling compactMap with another map as a transform function does not feel very intuitive to me, but I'd love to hear your opinions: do you think mapNonNils is no longer necessary?

Conclusion

The solution we’ve come up with seems pretty good, but I do believe there is room for improvement. The other reason for this article is to enlist the community into fiding those improvement opportunities, so I look forward to seeing what you guys come up with on the commentaries and update the article accordingly.

Thanks!

--

--