Note: The goal of this post is to give the reader an intuition about the usage of a map versus a flatMap, instead of playing around with return types of each operation, until you pick the one that suits.
A recurring topic during our interview process is "what's the difference between a
map and a
flatMap". For a lot of people, this particular question, might seem a nitpick, but for us it's quite important:
- Our codebase makes extensive use of functional aspects:
partial application, usage of the
|>operator (pipe) and others.
- As you might know, based on the themes I explore in this blog, we use FRP (via ReactiveSwift/ReactiveCocoa) across our entire stack (from the network level to the UI).
- Swift, in one way or another, pushes some of these concepts to every developer via
Bottom line: independently of our interested, or lack of, in FP, these concepts are present in our day to day.
So let's first define
flatMap from a signature point of view with an
func map<U>(_ f: (T) -> U) -> Optional<U>
func flatMap<U>(_ f: (T) -> Optional<U>) -> Optional<U>
Now that we have settled on a signature, what's the difference?
I use the follow example to demonstrate it:
If you want to transform an
String, would you use a
map seems to work fine:
let foo: Int? = 2 let bar = foo.map(String.init)
If you look at one of
String.init's signature, it matches exactly the signature that the
map is expecting:
Int -> String.
Let's look at the reverse:
If you want to transform a
Int, would you use a
map might seen to fit the bill, when you get, for example an
"1", or a
"2". In the other hand, if you get an
"a", the transformation will fail.
This is the key point, when it comes to a
flatMap takes into consideration the unhappy path, which is something
map can't cope with, simply because the transformation doesn't allow it:
T -> U.
This is not only applicable to
Optional<T>, but also to things like
Result<T, E: Error>,
SignalProducer<T, E: Error>,
Observable<T> and others.
Next time you are not sure which operator to use, thinking about the happy versus unhappy path will probably give you the answer you are looking for almost immediately. 1
P.S: You can use a
map when trying to transform a
String to an
Int, but instead of a
Optional<Int>, you will get a
Optional<Optional<Int>>, which is not what you want.
There is more to this, specifically if you compare a Functor to a Monad, from where the map and the flatMap come, respectively. Consider also the case when the transformation is not synchronous, but this is a topic for another time. ↩