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 functors too, because 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 (I’m a fan of this guy!):
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 it context using the flatMap. Arrays are monads as you can flat then by simply doing:
.concat.apply(, [ "H", ["e", "l"], ["l"], "o"]);
// results on ["H", "e", "l", "l", "o"]
[Note on 01/12/2017] The native flatMap and flatten are currently a proposal on stage 3.
You can see that if is a primitive value, the flat map just apply the context (array) to it, but when find a wrapped value it lifts its context to the same level of the flatmap caller.
Ok, enough talk, let’s build one!
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 white
.flatMap(rng => console.log(rng)); // logs white too
// Zord(Zord('white')).flatMap() -> Zord('white')
Nice, we accomplished what we wanted. But now you should probably saw this same behaviour before, in Promises:
.then(res => console.log(res)); // logs hello
.then(res => console.log(res)); // logs hello too
“So, the promise is a monad and the resolution method then( ) is a flatMap!”
Yes, you’re right! Promises encapsulates asynchronous values, which can produce (or not) values when flatten. That’s why you can return a value or promise inside the then( ) callback, which always lift the values (if 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 they respective wrappers.
Note: The Promises then( ) method acts as a magical unwrapper, lifting how many contexts possible to reach the wrapped value. So differently from a common monad “Promise.resolve(Promise.resolve(Promise.resolve(‘hello’)” then( ) method will actually get the ‘hello’ value instead of the Promise<’hello’>.
So, going back to the Zords (Zordon is calling us!), if we look in the code, any class extending Zord isn’t going to respect the morphism as we call directly the “new Zord” on the static of( ) method.
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…medium.com
Inspired by Swift Functors, Applicatives, and Monads in Pictures where the original article Functors, Applicatives, And…medium.com
Monads is a type of Functor, so you need to know what a Functor is in order for this episode to make sense. Thankfully…medium.com
updated: May 20, 2013 Here's a simple value: And we know how to apply a function to this value: Simple enough. Lets…adit.io
Special Thanks to
Yannick Spark for pointing me out corrections on the definition of the monad behaviour.