Evojam

View Original

Practical Intro to Monads in JavaScript

If you've heard about Monads but never had time to learn them, here's a simple explanation. Not a theoretical nonsense. It's a simple, practical tutorial for JavaScript developers showing how some monads can be used. It's for engineers, not scientists.

In theory there is no difference between theory and practice. In practice there is.

Yogu Berra

All examples are based on monet.js - a tool bag that assists Functional Programming by providing a rich set of Monads and other useful functions. I'll use arrow functions introduced in ES6. They are much more readable than regular functions when used as single operation "callbacks".
Also, in some examples, I will add TypeScript type definitions to enhance overall readability.


Monad

Monad is a box containing some value. People say it has something to do with a freaky category theory, but we can ignore that fact. Our box is not just a wrapper. It has tools to bind computations to the value. It can serve multiple different purposes — asynchronicity, fail-fast, error accumulation, lazy evaluation, etc… but these will be covered in future articles.

Monads are defined by 3 axioms. One of the things that axioms tell us is that there has to be some function that binds calculations to the value contained in a monad box. It's called bind (or flatMap) and has to meet 3 requirements:

  • bind takes one parameter — a function (let's call it callback)

  • callback takes current value and returns monad containing result of calculation

  • bind returns a monad returned by callback

See this content in the original post

And TypeScript definition:

See this content in the original post

Let's forget about theory now and jump into practical examples:

Identity

Axioms tell us that there should be some function that creates a monad. Consider we have Identity(value) function that does the job:

See this content in the original post

But why should I ever complicate my life so much if all I want is to add two integers?

Right. Previous example is not a real life one at all. But we can try to .bind() several operations to some value.

And we may use another common (in monad world) method —.map(). When mapping, you don't have to return a monad from callback — the returned value is wrapped under the hood in a same monad:

See this content in the original post

So let's do some real computation:

See this content in the original post

A .map() in Identity works exactly like in an Array:

See this content in the original post

For the purpose of this example, calculations above are rather simple but they show idea of how to compose operations in descriptive, readable way. Consider other possibilities:

See this content in the original post

or in more functional style:

See this content in the original post

And now think about real-life, complex compositions of calculations…

Promise A/A+

Common JavaScript Promises are quite similar to monads. They are boxes containing values (or rejections). Compare this portion of code with the initial Identity example:

See this content in the original post

It is a simple example. Actually Promise A/A+ is not as typesafe as Monads are. Method .then() is so elastic that it does a job of several different methods of a monad (bind/flatMap, map, orElse, cata and few more).

But, for sure, there exist monadic implementations of JavaScript Promise!

Maybe

There are several monads in the wild. Probably the most common is Maybe (Option). It can be Some(value) or Nothing. Here’s an example that fits previous ones:

See this content in the original post

It becomes useful when we have methods/functions returning some value or null/undefined. For example getCurrentUser():

See this content in the original post

It can return a User entity or… null. See this:

See this content in the original post

We can fix this with some if/else bloat:

See this content in the original post

…or we can pack our values in a Maybe box:

See this content in the original post

We are safe. No Uncaught TypeError: Cannot read property 'id' of null exceptions. And we do not have to add the evil if/else complexity to the code.

Maybe has a few base methods:

  • flatMap which is core of any monad

  • map lets us get Maybe from Maybe

  • filter lets us change Some into None if a condition is not met

  • orSome - get monad value or just another value

  • orElse - get monad (if is Some) or else new passed monad

  • cata - whooo lot of magic…

Let's .filter() and .map() like an Array

If you treat a Maybe as a single element (Some) or empty (None) array - map and filter will work the same:

See this content in the original post

…and for Maybe:

See this content in the original post

Let's create a new getter (similar to getId() a few lines above):

See this content in the original post

The difference from Array's map is that a monad mapping should fail if callback returns no value. So if user's name is null or undefined, the above example will be broken. We can easily fix this with filtering:

See this content in the original post

"But that becomes so complex…" you would say. And you are right. Basic monad method is flatMap (aka unit) and it can handle this situation.

So let's .flatMap()

See this content in the original post

…but that is still a bit unattractive. And that's why my favorite JavaScript implementation (monet.js) provides additional way to create a Maybe monad:

See this content in the original post

With this JavaScript oriented super power we can:

See this content in the original post

We can easily imagine a situation in which empty user name could be filled with some default value, e.g. 'Guest'. We can use .orSome() for this.

Get value .orSome() other value

Let's get user name with fallback to 'Guest':

See this content in the original post

This one in other implementations is called .getOrElse() - get value or else another passed value.

Without monads this would look like:

See this content in the original post

Ugly, isn't it? Now what is the difference between .orSome() and .orElse() ?

Maybe tea .orElse() maybe coffee…

We have considered a default value for optional name field. We may also want to fill it with optional value from any other field in our source value, like nickname. Here's what can we do with the filthy imperative tools:

See this content in the original post

So returning to a safe ground:

See this content in the original post

Simple, isn't it?

Catastrophism vs. Catamorphism

In category theory, the concept of catamorphism (from Greek: κατά = downwards or according to; μορφή = form or shape) denotes the unique homomorphism from an initial algebra into some other algebra.

We can ignore that scientific bullshit I think. It's easy to remember that the most complex part of monet.js's Maybe monad is .cata() just like catastrophism. It can lead to catastrophe or cataclysm if used carelessly or without type safety in mind. Furthermore, it is also the best tool (along with .orSome() and .orElse()) to link our brand new functional code with an old and nasty third-party libraries ;)

It is like hybrid of .orSome() and .map() with 2 callbacks:

  • for case we have None — it takes none args and returns fallback value

  • for case we have Some(value) — it takes one arg (a value) and returns a new value

It returns the output of the callback that was called. Its signature in context of Maybe looks like this:

See this content in the original post

Consider another case - we have a Maybe (option of user name) and need to get greeting - different for named user and different for anonymous Guest:

See this content in the original post

Sophisticated scientific name for a simple operation.

Conclusion

This simple little monad can save you a lot of time and some code. Identity gives you ability to write complex computations as descriptive steps. Maybe brings protection from Cannot read property of null exceptions. And what is most important — it's only the first step to functional programming monadic world where you can find more similar tools like Either (fail-fast handling), Validation (error accumulation), Promise (async operations), immutable List, Lazy, Continuation, Free, Read, etc… And some of them will be covered here soon!

Remember, a monad is really nothing more than a chainable computation. It is simply a functional way to sequence things. That’s it.

A Gentle Intro to Monads … Maybe? Sean Voisen

Further reading: