map versus flatMap

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:

  1. Our codebase makes extensive use of functional aspects: currying, partial application, usage of the |> operator (pipe) and others.
  2. 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).
  3. Swift, in one way or another, pushes some of these concepts to every developer via Optionals.

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 map and flatMap from a signature point of view with an Optional<T>:

func map<U>(_ f: (T) -> U) -> Optional<U>  

and flatMap:

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 Int to a String, would you use a map or a flatMap?

Well, 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 String to an Int, would you use a map or a flatMap?

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 map vs flatMap. The 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.

Conclusion

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.

  1. 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.