For-loops are so 2014. Swift provides us with two new functions that can handle jobs that were traditionally done with a loop. Functional programming languages have been using
flatMap() for years, but they are also extremely useful in object-oriented languages. And Swift handles these functions with speed, safety, and style.
Let’s jump right in with a quick example. Let’s say we are building a music app and we want to turn a bunch of dictionaries into Song objects. This pattern should be very familiar to anyone who writes an app that connects to a web service. Let’s define a few things first:
The old way: with a for-loop
And now let’s transform the array of dictionaries into an array of Songs:
But can it map?
map() is a function that is performed on a collection. It takes one parameter, which is a function called
transform. It applies the
transform function to every element in the collection, and then builds a new collection with the new elements. Let’s see how we can rewrite the above loop using
Ok that’s pretty ugly. Let’s quickly make a few stylistic changes. First, we can remove the parameter declaration in the closure and replace the parameter with a $0.
That looks better, but we can keep going. We don’t really need the closure at all. We can just provide the name of the
dictionaryToSong(_) function. Swift will implicitly pass along the $0 so we don’t need that either:
So now the whole thing is one one line. But what have we really accomplished here?
So map is just a weird-looking for-loop, right?
The first time I saw
map, I thought “cool story, but that’s not as readable as the for-loop was”. But after using
map for a while I have to disagree with that statement. Now I think that it is more readable than the for loop and more powerful as well. Here are two reasons why.
map is Declarative
A for-loop doesn’t inherently mean much. It just says “for each element in a collection, do this.”
map on the other hand explicitly says “transform the elements of this collection into something new”. When you see a map in code, you know exactly what’s happening.
map is like saying “transform this collection of cars into a collection of badass robots”
Results are Immutable
Mutability leads to bugs, bugs lead to anger, anger leads to the Dark Side. Fortunately the result of
map is an immutable collection. Unfortunately the result of a transformation via for loop is mutable. Take a look:
flatMap: It’s like map, but without nils
Often we have a transformation function that can produce nil. In our example that could happen if we have a dictionary that cannot be transformed into a Song. Like this:
What can we do in this case? You got it: use
It’s that simple.
flatMap will throw away any nils rather than add them to the collection. What
flatMap does behind the scenes is actually really interesting, but that’s the subject of a future post. For now all you need to know is that
flatMap will get rid of nils.
map and flatMap are Extra Cool
There are several other benefits you get when using
flatMap. These are a little bit advanced but they’re good to know about.
Pipelining sounds like a surfing concept but programmers have their own definition of it. It means that you can pass the results of one function to another.
Ride the pipeline! Gnarly, dude!
This is really awesome, but difficult to show. Check it out:
lazy keyword tells Swift to only perform the
map function when we actually go to use the values. If you had an enormous collection this could be very performant. Try doing that with your for-loop!
Refactoring and Parallelization
map we’ve taken a common action (transforming a collection) and hidden it away behind a function. This is great because we don’t care how the function is implemented. And this allows other people (the Swift team, LLVM, or whomever) to refactor the functions and make them faster or safer.
One suggestion we may see in the future is this: since
map doesn’t have to go through the collection sequentially, it would be possible for Swift to hand off pieces of the collection to different processors on a multi-core device, thereby performing
map in parallel across multiple cores. In that case we could see a 2x, 4x, or more increase in performance!
So we’ve learned that
flatMap are awesome. We can use them to transform collections in a safe, fast, and expressive way. But we’ve really just brushed the surface of what these functions, especially
flatMap, actually do. I’ll cover that in an upcoming post but until then, take a look a this code:
What’s going on there? Let me know on twitter what you think is happening!
You can find a playground for this article here.