Implementing JavaScript Functors and Monads

Mirror — Picture under the creative commons license (source: https://flic.kr/p/p4wFwX)

Introduction

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!

Functors

Definition

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

Definition

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:

Functions map: a => b which 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 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.

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:

Note that we use as default actor the identity function (x) => x

With that, we can check the following behaviour:

Zord.of('white')
.flatMap(rng => console.log(rng)); // logs white
Zord.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:

Promise.resolve("hello")
.then(res => console.log(res)); // logs hello
Promise.resolve(Promise.resolve("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 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:

Conclusion

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.


Dancing the Functional Jam!

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 😃.