Swift: map & flatMap

31 Oct

I’ve read many articles recently that were trying to shed light over the use of map and flatMap in Swift… some tried to explain it through plain examples whereas other used a more academical approach introducing the concepts of Functors and Monads. None of those approaches really got to the point in my opinion. How do they actually work? None of the articles I read so far explained it to me clearly (I should have looked harder for better answers probably). To fully understand I had to do many tests with Playground till I eventually metabolized the concept.

Let’s begin saying that both this functions operate on objects that are “containers” of other objects and that their purpose is to extract whatever is within the container, pass it to a function (or a closure) and create a new container with  whatever was returned by the function. The key difference between the two is that flatMap expect the returned object of the function to be a container in order to extract whats within and “pack” it again before returning it

To be clear here are the steps of a map :

  1. extract the object from the container
  2. pass the object to the function/closure
  3. get the value returned by the function/closure and put it in a new container
  4. return the new container with the object returned by the function/closure

an here the steps of a flatMap:

  1. extract the object from the container
  2. pass the object to the function/closure
  3. get the value returned by the function/closure and extract whatever it is contained within
  4. return a new container with the object just extracted from the container returned by the function/closure

To better unfold the mysteries of map and flatMap let’s revisit what just said with a practical example in Swift. In swift by default you can invoke map and flatMap on arrays and optionals, in the example that I’m gonna do I’ll use optional (don’t worry I’ll show you how to deal with the arrays later on):

In the code above I declared two functions which get two integers as input and divide the first by the second. divideNumber will try to perform the division regardless it is possible or not (if the “by” integer is 0 an error will occur) and return an Int whereas safeDivideNumber return an optional Int because it checks that the divisor is not 0 before performing the division and in case it is it returns “.None” (which is the same as writing nil).

Here I just declared two optional integers on which I’m gonna invoke map and flatMap. Now let’s see what happens if I invoke map on optionalNum:

As we can see in the first case the result is an optional containing the result of the division because map :

  1. extract 64 from its optional container
  2. pass the extracted 64 to divideNumber along with the divisor 2
  3. divideNumber divide 64 by 2 producing the value 32 and return it
  4. map gets the returned value 32 and pack it into another optional
  5. map returns the just created optional containing 32

In the second case the result is an optional containing an optional Int… this is because map:

  1. extract 64 from its optional container
  2. pass the extracted 64 to safeDivideNumber along with the divisor 2
  3. safeDivideNumber divide 64 by 2 producing the value 32 and return it packed into an optional
  4. map gets the returned value Optional(32) and pack it into another optional
  5. map returns the just created optional containing Optional(32)

So in this second case the final result is Optional(Optional(32))… an optionalception! Fortunately flatMap is here to help us avoiding this ugly optionalception…. in fact:

As you can see in this case the result is just a simple Optional(32) even though we called the safeDivideNumber function which returns an optional… this is because flatMap:

  1. extract 64 from its optional container
  2. pass the extracted 64 to safeDivideNumber along with the divisor 2
  3. safeDivideNumber divide 64 by 2 producing the value 32 and return it packed into an optional
  4. map gets the returned value Optional(32) and extract the value 32 from the optional
  5. map creates a new optional with the value just extracted and returns it

If you are asking yourselves what would happen if flatMap is used with a function or a closure which don’t return an optional the answer is that flatMap will simply skip the “unpacking” phase of the value returned by the function:

If you are wondering what would happen if map or flatMap were invoked on an optional which is nil the answer is that it will return nil

To conclude the part involving optionals let’s underline that flatMap invoked on an optional is only capable of unwrapping other optionals:

At a certain point above I told you that not only Optionals implement map and flatMap but Arrays as wellthis is not technically true in fact is actually the protocol SequenceType the one implementing these methods (and as you know Array conforms to it).

As you can see map and flatMap, when invoked on a SequenceType, behave a little bit differently than on Optionals: when called on an Otional the returned value is another Optional whereas when called on a SequenceType the returned value is an Array (and not an instance of the same type on which the method was invoked).

But how are these methods working when when invoked on SequenceType? The behaviour of map is quite simple: it takes each element of the SequenceType, pass it to the function/closure and then store the returned value in a new array that will be returned as result.

Here it is a practical example where we invoke the map method on an array (which conforms to SequenceType) of integers passing the function divideNumber as input:

As you can see the result is an array where the elements are the result of the division by 2 of the elements of arrayOfInt. But what would have happened if instead of passing divideNumber we had passed safeDivideNumber? Very simple… the result would have been an array of Int? (optional integers) where every optional would have contained the result of the division by two of arrayOfInt‘s the elements.

Whereas map‘s behaviour is pretty straightforward flatMap‘s one is a little bit trickier: when receiving the value returned by the function/closure instead of directly storing it into the array that will be returned it checks if it’s an optional or a SequenceType and if it is the case it “extracts” the values contained (just one ore non in case of optionals, many in case of SequenceType) and put each of them into the array that will be returned.

Let’s do a quick practical example using the same arrayOfInt as before but instead of using divideNum we are going to use safeDivideNum which returns an Int? (optional int):

As you can se all the optional returned by safeDivideNumber have been unwrapped before being added to the returned array.

But what if some of the value returned by the function/closure are nil? Well… they’ll be simply ignored and not put inside the returned array. For instance… you have an array of integers and you invoke flatMap on it with a function that accepts in input a number and if is a multiple of 2 return it whereas if it is not return nil the output would be the subarray of multiples of 2.

As stated previously flatMap is capable of unwrapping not only optionals but SequenceType as well which means that invoking it on an array of arrays will return an array containing all the elements within the arrays:

Since map and flatMap are declared as part of the protocol SequenceType they can be invoked on any type conforming to this protocol such as, for instance, Set and Dictionaries. Sets behave pretty much like arrays whereas dictionaries are a little bit weirder in fact each pair key-value is treated as a tuple containing both and is passed as input to the function/closure on which to map it.

Here some examples with Sets:

It is important to remember that the unwrapping phase done by flatMap is done on the returned value of the function/closure given as argument… NOT on the value passed to it. In the example above I used “{$0}” as closure which means “simple return the element received in input as it is“, this way I can unwrap directly the elements of the array or set on which flatMap is invoked. Of course, since the extraction is done on the value returned by the function/closure, I couldn’t pass divideNumber or safeDivideNumber as input to flatMap because these function accept in input an Int whereas in case of a Set of Sets or an Array of Sets flatMap would pass a Set to the function (and not an Int).

Fore some example with dictionaries I’m introducing 2 new dividing functions that are pretty much like divideNumber and safeDivideNumber but accept in input a tuple (String, Int) instead of an Int:

As in the case of arrays and sets since map and flatMap are working on SequenceType you could as well use flatMap to extract dictionaries within arrays or even within other dictionaries (not within Sets because dictionaries don’t conform to Hashable):

I hope that now everything about map and flatMap is clearer for you after reading this post… and remember that if your type is an optional or conforms to SequenceType you can leverage them to made your code cleaner and more “functional“!