Step Up Your Functional Game — Map and FlatMap Tricks
Swift has been out for more than three years now, and as I’ve learned the language, I’ve tried to incorporate more functional programming techniques into my code. Coming from an extensive background in Objective-C, I’ll admit that I really didn’t understand map and flatMap when first learning Swift. Eventually, I was comfortable enough with the concepts to start using them to replace some of my for
loops. Until recently, that’s all I really considered map and flatMap to be useful for. It wasn’t until I attended Neem Serra’s Map and FlatMap Magic talk at try! Swift NYC 2017 that my eyes were opened to a world of other great uses for map and flatMap. Much of the content in this post is based on Neem’s presentation, so make sure to check out her talk on Realm Academy when you get a chance!
Map
The most basic definition I could come up with for map is that it transforms data into a different type. Typically, it’s used to loop over a collection and transform each element so you can do things like create an array of String
s from an array of Int
s.
FlatMap
Like map, flatMap transforms data into a different type. FlatMap also has a special property of “removing a layer.” One example is removing a layer of nesting, where a 2-dimensional array is “flattened” into a 1-dimensional array. Another example is removing nil
elements from an array that’s populated with optionals.
Map and FlatMap Basics
Setup
As in Neem’s talk, I’m going to showcase map and flatMap techniques using cupcakes. Here’s the model we’ll be working with:
As you can see, the model is dead simple. A Cupcake has a flavor
property and there is one method to retrieve that flavor. In reality, there’s no need for the getFlavor()
method since we could just access the flavor
property directly. But for our example, we’ll leave it as is.
Up next, we’ll create some Cupcake
s and stick them in a cupcakes
array:
The rest of the examples will use this basic setup to illustrate different map and flatMap techniques. A playground is also provided at the end of the article.
Map — The Old Way
Does this code look familiar? I know I was guilty of writing code in this style when first learning Swift. Here, we’re transforming and array of Cupcake
s into an array of String
s.
Map — The New Way
Using map, we can reduce the 4 lines of code above into a single line. Neat!
Map — The Wrong Way
You should really only use map to transform elements. While it’s technically possible to generate elements using map, you should avoid the urge to do so, if possible. A good rule of thumb is to watch for ignoring the closure parameter. If you’re writing something like .map { _ in ... }
(note the underscore), then you’re probably not using map correctly.
Mapping with Optionals
Map can also be used on optionals. For reference, let’s look at how optionals normally behave when you try to use them without first unwrapping them. You’ll most definitely see a compiler error complaining about the optional not being unwrapped.
You can use map to work around this, using it to unwrap the optional, if it exists, and give you a chance to work with the value.
It’s important to note that the type of the newCount
variable above remains an Int?
or Optional<Int>
. An easy way to think about it is when you pass an optional to map, map will unwrap it and let you do something with the value inside of the closure. Map will then re-wrap it as an optional and return it to you.
FlatMap — The Old Way
When dealing with nested, or multi-dimensional arrays, our code would often look something like this when we needed to reduce it down to a single-dimensional array.
FlatMap — The New Way
Because of flatMap’s ability to “remove a layer,” we can perform the same operation in one line.
FlatMapping with Optionals
Like map, flatMap can also be used with optionals. When you pass an optional in to flatMap, flatMap will unwrap the optional if it exists, giving you a chance to do something with the value, or return nil. A neat trick is using the nil-coalescing operator to then provide a default value in case the unwrapping fails.
Let’s say on some days there is a “featured cupcake,” which becomes the flavor of the day. Whenever there is no “featured cupcake,” we’ll say that vanilla is going to be the default flavor of the day. We can model this scenario in a single line using flatMap.
Note that the type of flavorOfTheDay
is not an optional. Since we’re using the nil-coalescing operator to provide a default value, flavorOfTheDay
is simply a String
.
Map Tricks
Add Optional Subview — The Old Way
UIView
’s addSubview(…)
method takes a non-optional UIView
so you would usually find yourself having to unwrap the optional view first.
Add Optional Subview — The New Way
Since map only runs its closure if the optional value exists, we can simplify the above code down to one line. No more if let
for this simple operation!
String Interpolation — The Old Way
Let’s say we’re needing to display the number of cupcakes to the user. If the numberOfCupcakes
variable is nil
, we want to display the text “Unknown number.” Otherwise, we need to generate a String
describing how many cupcakes are in the box. Without map, we may have done it like this.
You may be familiar with the ternary operator. Maybe we could use it to simplify things.
Unfortunately, the use of the ternary operator breaks our requirements. When numberOfCupcakes
is not nil
, there will be undesirable output because the full description of the optional will be used instead of its value (Optional(0)
instead of 0
).
The nil-coalescing operator can be used to fix. By adding ?? 0
, it allows the value of numberOfCupcakes
to be automatically unwrapped, falling back to the default value of 0
if the unwrapping fails. However, this is a bit ugly. Because the ternary operator will cause the code to short circuit to “Unknown number” when numberOfCupcakes
is nil
, the code path created with the nil-coalescing operator will never be executed.
String Interpolation — The New Way
Using map, in combination with the nil-coalescing operator, we can adhere to our requirements while avoiding the creation of “unrunnable” code paths.
Shortcut on Init — The Old Way
As we’ve seen before, we can use map to transform elements in an array into a different type. Using the shorthand notation, the implicit $0
parameter can be passed into the initializer of the desired type.
Shortcut on Init — The New Way
Since functions are first-class citizens in Swift, we can pass the init
function directly to map as a parameter and avoid writing a closure at all.
I’m still on the fence about adopting this technique throughout my code. On one hand, it’s kind of a nice simplification to how you call map. On the other hand, it can’t be used in every situation so it would create minor inconsistencies in your usage of map throughout your codebase.
FlatMap Tricks
Filter Objects That Fail on Init — The Old Way
Here’s another snippet of code that probably looks familiar if you come from an Objective-C background like myself. We have some identifiers that represent images that need to be loaded from the bundle into an array. Since UIImage
’s image(named:)
initializer is failable, we need to use an if let
when creating the images.
Filter Objects That Fail on Init — The New Way
Let’s look at what happens when we use map.
Since map attempts to unwrap an optional and then rewrap it, we actually get back an array of optional images — [UIImage?]
. Not quite what we want.
Because flatMap removes a layer, it will return the value of the optional, if it exists, or nil
. This means the images array is of type[UIImage]
. Whenever the initializer fails, nothing will be added to the array instead of an Optional<UIImage>
with a nil
value.
In the attached playground, the array is empty because there are not actually any image resources included.
Get Data for Onscreen Cell — The Old Way
Sometimes you need to get the data associated with a cell that’s shown in a table or collection view and doesn’t have a direct reference to the corresponding IndexPath
. Typically, you would use if let
to unwrap an IndexPath
using UITableView
’s indexPath(for:)
method. Once you have the index of the cell, you typically use the row
property as the index for your data array.
Get Data for Onscreen Cell — The New Way
FlatMap’s property of transforming an optional, if it exists, or returning nil
can provide an alternative implementation. If the indexPath(for:)
method returns a valid IndexPath
, we can use it to access the corresponding piece of data inside of the flatMap closure.
Why Use Map and FlatMap?
Be Functional
Functional programming is cool, and we’ve only recently been able to take advantage of this tool thanks to the release of Swift.
One great aspect of functional programming is the ability to avoid mutable state. Not only can this help avoid potential bugs, but I think it also reads better, requiring less overall cognitive overhead.
When you see something is let
, you know immediately that the value of that variable will not change for the rest of its scope. When it comes time to actually define the value of the variable, I find that transforming an optional using flatMap then using the nil-coalescing operator to define a default value reads very well. To me, a single line is easier and quicker to understand than having to track the value of a variable through several lines of an if
/else
block.
Visually Explicit Chaining
Visually Explicit Chaining refers to the ability to chain together several map and flatMap operations to define a stream of sequential changes that transform data over time.
Borrowing a slide from Neem’s presentation, the instructions are to combine boxes, remove nil cupcakes, frost the cupcakes, and then add sprinkles.
The result is an array of sprinkled Cupcake
s. If you’re reading closely, you may have also noticed that the print(cupcakes)
statement should actually be print(deliciousness)
. 😉
Conclusion
A Swift playground is available as a GitHub Gist that contains all of the examples shown here. It was written in Swift 4 / Xcode 9.
Map and flatMap have many more use cases beyond the obvious one of transforming arrays into different types. I hope I’ve introduced you to at least one cool new trick that you may start adopting in your Swift code. Do you know of any other great uses for map and flatMap? If so, feel free to drop a comment and help us all step up our functional game!