Practical introduction to Functional Programming with JS
Open Source Your Knowledge, Become a Contributor
Technology knowledge has to be shared and made accessible for free. Join the movement.
Functors
Remember when we talked about map
? We only used map on arrays, but you can actually implement map
on other data structures, e.g. trees, streams, promises...
Any type that has a map function is a functor.
You should have understood the concept of map by now, but if you a real nitpicker, you will now ask "How do we define map?". One of the best definitions is given by haskell's functor laws; for those of you who are interested, I will translate them into JavaScript:
// identity
myFunctor.map( x => x) === myFunctor
// composition === chaining
myFunctor.map( x => f(g(x)) ) === myFunctor.map(g).map(f)
In the code above, I used
===
to keep it simple, but I should have checked for deep equality. If you don't know what this means, don't worry, you should be able to understand the meaning anyway. Nonetheless, if you want to learn more about it, this StackOverflow thread is a good place to start.
The advantage of using a functor is that the container is now abstracted away. E.g., in streams, we don't need to care about asynchronous data handling, we can just use a stream like an array.
Another cool feature of functors is that you can chain map
calls, because the map
function returns another functor.
So why don't we create our own functor?
Monads
Giving a definition of monad is somewhat tedious and requires a bit of theory, so we are first going to build an intuition for them through examples.
Array Monad
Let's write a function that duplicates every item in an array (e.g. [1,2,3]
--> [1,1,2,2,3,3]
).
Let's first try with map:
Running the code, you can see that the result is not quite what we wanted: [[1,1],[2,2],[3,3]]
. Wouldn't it be great if we had a function that automatically "flattened" the array we returned into [1,1,2,2,3,3]
? That's flatMap
!
The
flatMap
function was implemented by me, because it's not provided by javascript yet; we'll deal with the details later.
As you can see, flatMap's callback returns another monad and flatMap's job is handling the unpacking. If you are interested in the details of the implementation, here's my code:
Although the array's implementation of flatMap
is very interesting and useful, it's not the only one: we can, for example, use flatMap to maintain knowledge of previous states of a system. As always, let's start with an example.
Gambler Monad
Let's say we want to create a model for a gambler: the gambler plays multiple times at the roulette; sometimes he wins, sometimes he loses. But if he goes bankrupt, he gets kicked out of the casino and cannot recover. We want to simulate a number of games and see if at the end the gambler still has money or not.
We cannot simply add up wins and losses, because we would ignore intermediate states. The following is a wrong solution:
What we need is a model that stops computing the transactions after he went bankrupt, like the following:
This time, we used flatMap
to remember past states, but again the callback returned a new monad and flatMap
handled the unwrapping of the original monad content.
Wrapping up
Monads are a broad definition, but they all share one characteristic: they implement flatMap
, which in term is useful to maintain knowledge of the context in which a function is being executed.