Option/Maybe, Either, and Future Monads in JavaScript, Python, Ruby, Swift, and Scala

This monad tutorial gives a brief explanation of monads and shows how to implement the most useful ones in five different programming languages—if you’re looking for monads in JavaScript, monads in Python, monads in Ruby, monads in Swift, and/or monads in Scala, or to compare any implementations, you’re reading the right article!

Using these monads you will get rid of a series of bugs like null-pointer exceptions, unhandled exceptions, and race conditions.

This is what I cover below:

  • An introduction into category theory
  • The definition of a monad
  • Implementations of the Option (“Maybe”) monad, Either monad, and Future monad, plus a sample program leveraging them, in JavaScript, Python, Ruby, Swift, and Scala

Let’s get started! Our first stop is category theory, which is the basis for monads.

Introduction to Category Theory

Category theory is a mathematical field that was actively developed in the middle of the 20th century. Now it is the basis of many functional programming concepts including the monad. Let’s take a quick look into some category theory concepts, tuned for software development terminology.

So there are three core concepts that define a category:

  1. Type is just as we see it in statically typed languages. Examples: Int, String, Dog, Cat, etc.
  2. Functions connect two types. Therefore, they can be represented as an arrow from one type to another type, or to themselves. Function $f$ from type $T$ to type $U$ can be denoted as $f: T \to U$. You can think of it as a programming language function that takes an argument of type $T$ and returns a value of type $U$.
  3. Composition is an operation, denoted by the $\cdot$ operator, that builds new functions from existing ones. In a category, it’s always guaranteed for any functions $f: T \to U$ and $g: U \to V$ there exists a unique function $h: T \to V$. This function is denoted as $f \cdot g$. The operation effectively maps a pair of functions to another function. In programming languages, this operation is, of course, always possible. For instance, if you have a function that returns a length of a string —$strlen: String \to Int$—and a function that tells if the number is even —$even: Int \to Boolean$—then you can make a function $even{\_}strlen: String \to Boolean$ which tells if the length of the String is even. In this case $even{\_}strlen = even \cdot strlen$. Composition implies two features:
    1. Associativity: $f \cdot g \cdot h = (f \cdot g) \cdot h = f \cdot (g \cdot h)$
    2. The existence of an identity function: $\forall T: \exists f: T \to T$, or in plain English, for every type $T$ there exists a function that maps $T$ to itself.

So let’s take a look at a simple category.

A simple category involving String, Int, and Double, and some functions among them.

Side note: We’re assuming that Int, String and all other types here are guaranteed to be non-null, i.e., the null value does not exist.

Side note 2: This is actually only part of a category, but that is all we want for our discussion, because it has all the essential parts we need and the diagram is less cluttered this way. The real category would also have all composed functions like $roundToString: Double \to String = intToString \cdot round$, to satisfy the composition clause of categories.

You might notice that functions in this category are super simple. In fact it’s almost impossible to have a bug in these functions. There are no nulls, no exceptions, just the arithmetic and working with memory. So the only bad thing that can happen is processor or memory failure—in which case you need to crash the program anyway—but that happens very seldomly.

Wouldn’t it be nice if all our code just worked at this level of stability? Absolutely! But what about I/O, for example? We definitely can’t live without it. Here is where monad solutions come to the rescue: They isolate all unstable operations into super-small and very well-audited pieces of code—then you can use stable calculations in your whole app!

Enter Monads

Let’s call unstable behavior like I/O a side effect. Now we want to be able to work with all our previously defined functions like length and types like String in a stable way in the presence of this side effect.

So let’s start with an empty category $M[A]$ and make it into a category that will have values with one particular type of side effect and also values with no side effects. Let’s assume we’ve defined this category and it’s empty. Right now there’s nothing useful we can do with it, so to make it useful, we’ll follow these three steps:

  1. Fill it with values of the types from category $A$, like String, Int, Double, etc. (green boxes in the diagram below)
  2. Once we have these values, we still cannot do anything meaningful with them, so we need a way to take each function $f: T \to U$ from $A$ and create a function $g: M[T] \to M[U]$ (blue arrows in the diagram below). Once we have these functions, we can do everything with the values in category $M[A]$ that we were able to do in category $A$.
  3. Now that we have a brand new $M[A]$ category, a new class of functions emerge with signature $h: T \to M[U]$ (red arrows in the diagram below). They emerge as a result of promoting values in step one as part of our codebase, i.e., we write them as needed; these are the main things that will differentiate working with $M[A]$ versus working with $A$. The final step will be to make these functions work well on types in $M[A]$ as well, i.e., being able to derive function $m: M[T] \to M[U]$ from $h: T \to M[U]$

Creating a new category: Categories A and M[A], plus a red arrow from A's Double to M[A]'s Int, labelled "roundAsync". M[A] reuses every value and function of A at this point.

So let’s start off by defining two ways of promoting values of $A$ types to values of $M[A]$ types: one function with no side effects and one with side effects.

  1. The first is called $pure$ and is defined for each value of a stable category: $pure: T \to M[T]$. The resulting $M[T]$ values won’t have any side effects, hence this function is called $pure$. E.g., for an I/O monad, $pure$ will return some value immediately with no possibility of failure.
  2. The second is called $constructor$ and, unlike $pure$, returns $M[T]$ with some side effects. An example of such a $constructor$ for an async I/O monad could be a function that fetches some data from the web and returns it as a String. The value returned by $constructor$ will have type $M[String]$ in this case.

Now that we have two ways of promoting values into $M[A]$, it’s up to you as a programmer to choose which function to use, depending on your program goals. Let’s consider an example here: You want to fetch an HTML page like https://www.toptal.com/javascript/option-maybe-either-future-monads-js and for this you make a function $fetch$. Since anything could go wrong while fetching it—think network failures, etc.—you’ll use $M[String]$ as the return type of this function. So it will look something like $fetch: String \to M[String]$ and somewhere in the function body there we will use $constructor$ for $M$.

Now let’s assume we make a mock function for testing: $fetchMock: String \to M[String]$. It still has the same signature, but this time we just inject the resulting HTML page inside the body of $fetchMock$ without doing any unstable network operations. So in this case we just use $pure$ in the implementation of $fetchMock$.

As the next step, we need a function that safely promotes any arbitrary function $f$ from the category $A$ to $M[A]$ (blue arrows in a diagram). This function is called $map: (T \to U) \to (M[T] \to M[U])$.

Now we have a category (which can have side effects if we use $constructor$), which also has all functions from the stable category, meaning they are stable in $M[A]$ as well. You might notice that we explicitly introduced another class of functions like $f: T \to M[U]$. E.g., $pure$ and $constructor$ are examples of such functions for $U = T$, but there obviously could be more, like if we were to use $pure$ and then $map$. So, generally, we need a way to deal with arbitrary functions in the form $f: T \to M[U]$.

If we want to make a new function based on $f$ that could be applied to $M[T]$, we could try to use $map$. But that will bring us to function $g: M[T] \to M[M[U]]$, which is no good since we don’t want to have one more category $M[M[A]]$. To deal with this problem, we introduce one last function: $flatMap: (T \to M[U]) \to (M[T] \to M[U])$.

But why would we want to do that? Let’s assume we’re after step 2, i.e., we have $pure$, $constructor$, and $map$. Say we want to grab an HTML page from toptal.com, then scan all the URLs there as well as fetch them. I’d make a function $fetch: String \to M[String]$ that fetches just one URL and returns an HTML page.

Then I’d apply this function to a URL and get a page from toptal.com, which is $x: M[String]$. Now, I do some transformation on $x$ and finally arrive at some URL $u: M[String]$. I want to apply function $fetch$ to it, but I can’t, because it takes type $String$, not $M[String]$. That’s why we need $flatMap$ to convert $fetch: String \to M[String]$ to $m_fetch: M[String] \to M[String]$.

Now that we have completed all three steps, we can actually compose any value transformations we need. For instance, if you have value $x$ of type $M[T]$ and $f: T \to U$, you can use $map$ to apply $f$ to value $x$ and get value $y$ of type $M[U]$. That way any transformation of values can be done in a 100 percent bug-free way, as long as the $pure$, $constructor$, $map$ and $flatMap$ implementations are bug-free.

So instead of dealing with some nasty effects each time you encounter them in your codebase, you just need to make sure that only these four functions are implemented correctly. At the end of the program, you will get just one $M[X]$ where you can safely unwrap the value $X$ and handle all error cases.

This is what a monad is :  a thing that implements $pure$, $map$, and $flatMap$. (Actually $map$ can be derived from $pure$ and $flatMap$, but it’s very useful and widespread function, so I didn’t omit it from the definition.)

The Option Monad, a.k.a. the Maybe Monad

Okay, let’s dive into the practical implementation and usage of monads. The first really helpful monad is the Option monad. If you’re coming from classic programming languages, you’ve probably encountered many crashes because of the infamous null pointer error. Tony Hoare, the inventor of null, calls this invention “The Billion Dollar Mistake”:

This has led to innumerable errors, vulnerabilities, and system crashes, which have probably caused a billion dollars of pain and damage in the last forty years.

So let’s try to improve on that. The Option monad either holds some non-null value, or no value. Pretty similar to a null value, but having this monad, we can safely use our well-defined functions without being afraid of the null pointer exception. Let’s take a look at implementations in different languages:

We start off by implementing a Monad class that will be the base for all of our monad implementations. Having this class is very handy, because by implementing just two of its methods —pure and flatMap—for a specific monad, you will get many methods for free (we limit them to simply the map method in our examples, but generally there are many other useful methods, like sequence and traverse for working with arrays of Monads).

We can express map as composition of pure and flatMap. You can see from flatMap’s signature $flatMap: (T \to M[U]) \to (M[T] \to M[U])$ that it’s really close to $map: (T \to U) \to (M[T] \to M[U])$. The difference is the additional $M$ in the middle, but we can use the pure function to convert $U$ into $M[U]$. That way we express map in terms of flatMap and pure.

This works well for Scala, because it has an advanced type system. It also works well for JS, Python, and Ruby, because they are dynamically typed. Unfortunately, it doesn’t work for Swift, because it’s statically typed and doesn’t have advanced type features like higher-kinded types, so for Swift we’ll have to implement map for each monad.

Also note that the Option monad is already a de facto standard for languages like Swift and Scala, so we use slightly different names for our monad implementations.

Now that we have a base Monad class, let’s get to our Option monad implementations. As previously mentioned, the basic idea is that Option either holds some value (called Some) or or doesn’t hold any value at all (None).

The pure method simply promotes a value to Some, while the flatMap method checks the current value of the Option — if it’s None then it returns None, and if it’s Some with an underlying value ,  it extracts the underlying value, applies f() to it and returns a result.

Note that just using these two functions and map, it’s impossible to get into a null pointer exception—ever. (The problem could potentially arise in our implementation of the flatMap method, but that’s just a couple of lines in our code that we check once. After that, we just use our Option monad implementation throughout our code in thousands of places and don’t have to fear the null pointer exception at all.)

The Either Monad

Let’s dive into the second monad :  Either. This is basically the same as the Option monad, but with Some called Right and None called Left. But this time, Left is also allowed to have an underlying value.

We need that because it’s very convenient to express throwing an exception. If an exception occurred, then the value of the Either will be Left(Exception). The flatMap function doesn’t progress if the value is Left, which repeats the semantics of throwing exceptions: If an exception happened, we stop further execution.

Also note that it’s easy to catch exceptions: All you have to do is map Left to Right. (Although, we don’t do it in our examples, for brevity.)

The Future Monad

Let’s explore the last monad that we need: the  Future monad. The Future monad is basically a container for a value which is either available now or will be available in the near future. You can make chains of Futures with map and flatMap that will wait for the Future value to be resolved before executing the next piece of code that depends on the value being resolved first. This is very similar to concept of Promises in JS.

Our design goal now is to bridge the existing async APIs in different languages to one consistent base. It turns out that the easiest design approach is using callbacks in $constructor$.

While the callback design introduced the callback hell problem in JavaScript and other languages, it will not be a problem for us, since we use monads. In fact, the Promise object—the basis for JavaScript’s solution to callback hell—is a monad itself!

What about the constructor of the Future monad? Is has this signature:

constructor :: ((Either err a -> void) -> void) -> Future (Either err a)

Let’s split it into pieces. First, let’s define:

type Callback = Either err a -> void

So Callback is a function which takes either an error or a resolved value as an argument, and returns nothing. Now our signature looks like this:

constructor :: (Callback -> void) -> Future (Either err a)

So we need to supply it a function that returns nothing and triggers a callback as soon as async computation is resolved to either an error or some value. Looks easy enough to make a bridge for any language.

As for the design of the Future monad itself, let’s look at its internal structure. The key idea is to have a cache variable that holds a value if the Future monad is resolved, or holds nothing otherwise. You can subscribe to the Future with a callback which will be immediately triggered if value is resolved, or if not, will put the callback into the subscribers list.

Once the Future is resolved, each callback in this list will be triggered exactly once with the resolved value in a separate thread (or as the next function to be executed in the event loop, in the case of JS.) Note that it’s crucial to carefully use synchronization primitives, otherwise race conditions are possible.

The basic flow is: You start the async computation supplied as the constructor argument, and point its callback to our internal callback method. In the meantime, you can subscribe to the Future monad and put your callbacks into the queue. Once the computation is done, the internal callback method calls all callbacks in the queue. If you’re familiar with Reactive Extensions (RxJS, RxSwift, etc.), they use a very similar approach to async handling.

The public API of the Future monad consists of pure, map, and flatMap, just like in the previous monads. We’ll also need a couple of handy methods:

  1.  async, which takes a synchronous blocking function and executes it on a separate thread, and
  2. traverse, which takes an array of values and function that maps a value to a Future, and returns a Future of an array of resolved values

Let’s see how that plays out:

Now, notice how the public API of Future doesn’t contain any low-level details like threads, semaphores, or any of that stuff. All you need is basically to supply something with a callback, and that’s it!

Composing a Program from Monads

Okay, so let’s try to use our monads to make an actual program. Say we have a file with a list of URLs and we want to fetch each of these URLs in parallel. Then, we want to cut the responses to 200 bytes each for brevity and print out the result.

We start off by converting existing language APIs to monadic interfaces (see the functions readFile and fetch). Now that we have that,  we can just compose them to get the final result as one chain. Note that the chain itself is super safe, as all the gory details are contained in monads.

There you have it—monad solutions in practice. You can find a repo containing all the code from this article on GitHub.

For this simple monad-based program, it might look like overkill to use all the code that we wrote before. But that’s just the initial setup, and it will stay constant in its size. Imagine that from now on, using monads, you can write a lot of async code, not worrying about threads, race conditions, semaphores, exceptions, or null pointers! Awesome!

A monad is an abstract interface that defines "pure" and "flatMap" functions. Pure lets you convert an ordinary value to monadic value. FlatMap allows functions with ordinary arguments to be applied to monadic arguments.