Introduction
Note: This is not a great explanation in hindsight and mostly preserved so I can return to it for reference as notes on exploring and understanding Map vs. FlatMap FP patterns.
I recently encountered a “lightweight” implementation of Monads and Options implemented in Python. The point of this post is not to deep dive into those two, but rather to “zoom out” and try to explain (to myself, as well) the difference between two types of functional programming methods frequently associated with them: Map and FlatMap. These patterns are tied with the use of Options (which I will briefly introduce). I found this site helpful but wanted to write an even higher level version that is not tied to existing APIs in languages with stronger support for function programming patterns. So, in this post, I will draw attempt to illustrate the difference in the pattern between Map and FlatMap (and why you would use one versus the other) without diving into the details of specific APIs in such languages.
Map explanation
First, let’s consider an array of integers.
Let’s consider each item in the list. Each item can be an “Option.” A key aspect of the option that is leveraged by these mapping operations is the fact that an option has a state. That is, it can either be something or nothing (2 states). If it is something, it has a value. If it is not something (nil
), then it’s value is None
, effectively. We can represent an Option in this example post as an array of length 0 or 1. If the array has a value in it, it is “something.” Otherwise, it is an empty array and has nothing, no value in it (a None
).
In the above example, all values were cast as a list. Now let’s handle when there are None
values present:
We can now define a map operation as one that applies a function on a given option and returns another option in response. That is, even if the option is an empty array (is nothing) it will return an option in response that is also nothing (an empty array).
Here’s what a simple map method could be represented as:
We can see the behavior described above when we apply the new method for mapping to some example options:
Now, going back to the original list of options from earlier, we can reconsider that list itself as an option. In doing so, we can apply a method designed to handle this new option that has a value which is a list of options in a manner akin to the following (which, as an example, adds 100 to all values that are something):
FlatMap explanation
FlatMap differs from Map in a key way. Instead of the Map operation returning an Option automatically, it instead requires that the function passed to it return an Option type result itself. That is, while Map returns an Option, FlatMap returns a value (or None
value) for each option, regardless of whether it is a “something” or a “nothing” and requires that the input method applied return an Option type response. We can now write a FlatMap example operation that demonstrates how this method differs from Map.
Just as before, we can watch this play out with single examples of options that have a value (are something) and do not (are nothing).
Now, the power of a FlatMap becomes more apparent when more types of Options are introduced. Right now, the pattern will look similar to a Map operation as the requirement to cast as an Option type has now just moved into the parameterized lambda.
In a more functional pattern, the Option itself would have various subclasses and you would want to potentially recast the option type. In this example, we just have one Option type represented by the bracketed integer value, but in a more complete implementation, this FlatMap would allow you to control what type of Option is returned such that Option type A would not just return another Option type A but could be converted in a FlatMap operation to return an alternative Option, say Option type B.
Conclusion
I hope this simple example helps explain the difference between Map and FlatMap. In some instances, controlling Option type on outputs can be desired whereas, in other instances, it might be more useful to rely on the mapped output’s preceding, default Option type.