This concepts are very used nowadays on functional programing, but because of the heavy mathematical background, sometimes it may be confusing to understand all the definitions.
In this post I’ll try to clear a little more the understatement of this subject, but not going too deep and using simple examples, helping you to create your own functors and monads. I wish you enjoy it!
Following the mathematical definition, and relating in the programing world in a very simple way, functors are a mapping using a function f(x) (or composite function f(g(x)) for instance) on a category A generating a category B, creating a new image, respecting the morphism. In other words, is any object we can map and apply a function generating another object instance of the same type and connections.
Let’s check an example:
[1, 2, 3].map(val => val * 2); //generates [2, 4, 6]
So, we can see that Array is a functor, because it respects the same type (results in other Array instance) and the connections too (have the same number of items).
Creating a Functor
With this in mind, let’s create a functor. I’ll take my previous post example of the Train class: (you may think I really like trains, right?)
The map method will return a image (another train, with the same number of connections) with the wagons content:
["gold", "gold", "people"]
But, if I try to do this:
Now we have a problem, because we need to reimplement the map method for all the children classes, as the Train map just return a new Train, causing it to break the rule of the morphism. How can I do to make the children classes return themselves instances without the need of overwriting the parent method?
Reflection using Symbol.species
Note: if you don’t know about Symbols, you can check my previous post, where I explain a little about it behaviour and what are the “well-known symbols” , which I focus about another one, the Symbol.iterator.
So, as the title says, we’re going to use the well-known symbol species for reflection, to make our current class create a new instance of itself while returning the map call:
This way, any child class instance that uses the Train.map are going to be a functor too, as they’re going to respect the morphism.
Monads are more complicated to explain, but I’ll try to make a parallel on what we know, and using a description given by Eric Elliott:
a => bwhich lets you compose functions of type
a => b
Functors map with context:
Functor(a) => Functor(b), which lets you compose functions
F(a) => F(b)
Flatten and map with context:
Monad(Monad(a)) => Monad(b), which lets you compose lifting functions
a => F(b)
So, the monad basically is a functor but with the special power to unwrap any value from its context using the flatMap. Arrays are monads as you can flat then:
[ "H", ["e"], ["l"], ["l"], "o"].flat();
// returns ["H", "e", "l", "l", "o"]
And flatMap then:
[1, , 3].flatMap(val => val * 2)
// returns [2, 4, 6]
You can see that if it’s a primitive value, the flat map just applies the context (array) to it, but when it finds a wrapped value, it lifts its context to the same level of the flatMap caller.
Creating a Monad
I’m going to use an example from my childhood, coming from the super sentai series, the Zords.
In this series, a Zord is a giant robot where the pilots were able to join multiple ones to create a MegaZord, which is basically another Zord.
For instance, the Megazord is the composition of all the rangers Zords, and the Mega Dragonzord is the composition of the Megazord + the green ranger’s Zord.
In our code, we want basically to accomplish something like this:
As a monad we’re going to be able to “extract” this values from the context using the flatMap, lifting one layer of Zords. So, the implementation should be:
With that, we can check the following behaviour:
.flatMap(rng => console.log(rng)); // logs whiteZord.of(Zord.of('white'))
.flatMap(rng => console.log(rng)); // logs white too// Flat mapping also make it return the inner wrapped Value
// Zord(Zord('white')).flatMap() -> Zord('white')
Nice, we accomplished what we wanted. Maybe you probably saw this same behaviour before, in Promises:
.then(res => console.log(res)); // logs helloPromise.resolve(Promise.resolve("hello"))
.then(res => console.log(res)); // logs hello too
“So, the promises are monads and the resolution method then( ) is a flatMap!”
Yes, you’re right! Promises encapsulate asynchronous values, which can produce (or not) values when flattened. That’s why you can return a value or promise inside the then( ) callback, which always lifts the values (when needed), unwrapping from any Promise context. And also, you can keep chaining calls of the Zord’s flatMap( ) or the Promise’s then( ), as they always return their respective wrappers.
Note: The Promises then( ) method acts as a magical unwrapper, lifting how many contexts possible to reach the wrapped value (basically a deepFlatMap). So differently from a common monad :
‘func’ will actually get the ‘hello’ value instead of the Promise<’hello’>.
So, going back to the Zords (Zordon is calling us!), if we look into the code, any class extending Zord isn’t going to respect the morphism, as we call from the static of( ) method a “new Zord( )” directly.
We need to use the same thing we’ve used before, the Symbol.species, and through reflexion, get the right constructor:
And now we can create a (not so boring) class child of Zord, while still being a monad and respecting the morphism:
You can see the result below:
After understanding a little more about functors and monads, you figure out that’s all about function composition and how to work in different contexts with it.
Well, that’s all! If you liked it, give it some claps 👏 and show your support. Critics and suggestions are always welcome, so don’t be afraid and use the comment section 👇, give your feedback 😃.
Note: This is part of the “Composing Software” series on learning functional programming and compositional software…
Inspired by Swift Functors, Applicatives, and Monads in Pictures where the original article Functors, Applicatives, And…
Monads — FunFunFunction video #21
Monads is a type of Functor, so you need to know what a Functor is in order for this episode to make sense. Thankfully…
Functors, Applicatives, And Monads In Pictures
updated: May 20, 2013 Here's a simple value: And we know how to apply a function to this value: Simple enough. Lets…
Special Thanks to
Yannick Spark for pointing me out corrections on the definition of the monad behaviour.