As a Java(script) programmer, you already know Lists and Optionals, and have used map and flatMap functions just fine without knowing the underlying Monad concept. However, getting to know it will make your code easier to test with fewer bugs, and make you a competent programmer on nowadays essential, harder topics like asynchronous and reactive programming.

Introduction

Programming is all about composition to tackle large problems by combining small solutions, so every programmer should know Monads by now:
Monads (in programming) are all about composition of side-effects. They have already become mainstreamm, although without recognition, in stream processing and reactive/asynchronous programming with Futures, Promises, Reactive Extensions; they will soon become mainstream in AI for (Deep) Learning through Probabilistic Programming and Automatic Differentiation. However, Monads model side-effects without actual side-effects and can look complicated. So, let’s raise our awareness to composition and side-effects separately first before we focus on composition with side-effects combined.
More: Reactive Extensions Intro Page
More: Automatic Differentiation Intro Talk
More: Probabilistic Programming Monad Talk

Function Composition in Java(script)

You already know and use function composition day by day:

static Integer length(String s) { .. }  // pure

static Boolean isOdd(int n)     { .. }  // pure

static Boolean hasOddLength(String s) { // pure
  return isOdd(length(s))
  //     |composition|
}

This works perfect for pure functions, that is, those without side-effects. But how do you compose impure functions, for example, those that may fail? In traditional Java(script) you “compose” as usual but introduce exceptions:

static Integer parseInt(String s) { ..throws new RuntimeException().. }

static Boolean isValidId(int n)   { ..throws new RuntimeException().. }

static Boolean isValidUserId(String s) {
  return isValidId(parseInt(s)) // also throws exceptions
  //     |---composition---|
}

This works but is dangerous, because functions with runtime exceptions don’t really compose; they lie: When using isValidUserId nothing tells you to check for the exception, which may be thrown deep and far way from your current code. If you forget that check, your program crashes unexpectedly on some inputs.
See: Math. Function Composition Wiki

The No-Value Side-Effect With Optionals

To make runtime exceptions safer you may list all exceptions in the comments (unreliable) or switch to checked exceptions with lots of try-catch blocks (horrible). However, for the side-effect of potentially not-having a return value for some inputs there is a safer, more readable alternative: Optional.

static Optional<Integer> parseInt(String s) { .. } // no exceptions

static Optional<Boolean> isValidId(int n)   { .. } // no exceptions

static Optional<Boolean> isValidUserId(String s) { // no exceptions
  // composition with manual glue code:
  var intOpt = parseInt(s);
  if (intOpt.isPresent()) {
    return isValidId(intOpt.get());
  } else {
    return Optional.empty();
  }
}

Even if you don’t know that Optional is a Monad, you will surely always rewrite that last function to use flatMap:

static Optional<Boolean> isValidUserId(String s) { // no exceptions
  return parseInt(s).flatMap(isValidId)) 
  //     |--------composition----------|
}

This function cannot crash and you cannot unknowingly forget to check for the empty case, because the return type and compiler will warn you.
More: Checked vs Runtime Exceptions in Java
More: Java Optional Guide

info

In rough terms, a Monad is a container type with a flatMap function, just like Optional. This intuition suffices for now.

So, using Optional we have turned impure functions with real side-effects (exceptions/crashes) into “impure” functions, where “side-effect” is a matter of perspective: Technically a “impure” function like isValidUserId is still pure. It simply returns a value of type Optional<Boolean> instead of Boolean. However, we interpret Optional as the “side-effect” of potentially not having a return value. This is what “side-effects without actual side-effects” means.

info

An “impure” function is also often called a “Monadic function”, because it returns a Monad. Different Monads represent different “side-effects”.

Compose Side-Effects with Monads

To get a deeper understanding how Monads relate to composition of functions with side-effects, we look at previous examples but switch the notation from Java to Math. Java(script) is simply too verbose and inconsistent, because you can write functions as lambdas or static methods and use different composition syntax. So, having a function like

static Boolean hasOddLength(String s) { .. }  // pure

we forget its name and focus only on its signature to get its essence:

String -> Boolean

Compose Pure Functions

As before, we start with composing pure functions:

   String -> Integer                  Integer -> Boolean
// |-pure function-|                  |--pure function--|

The output type of the left function matches the input of the right, so these two can be composed with a normal function composition operator:

   String -> Integer    `andThen`     Integer -> Boolean 
// result:
// String -------------------------------------> Boolean

This operator is called andThen in Java, fmap in Haskell, or simply compose in some Javascript frameworks. The result is a new function that first applies the left function, then the right one; it goes directly from String to Boolean and is correct by construction, because there is no manual glue code that we can mess up.
See: Java’s andThen Function
More: Function Composition Notation in Math
More: Function Composition Notation in Haskell

Pure functions, as found in purely functional languages like Haskell, can be used in other, non-pure languages such as Java and are simply the best tool for programming: They compose nicely as we have seen, they always return the same result for the same input and they don’t have side-effects. That means they don’t crash, they don’t manipulate/change any variable or any state, especially not outside the function, which makes them easily testable and simpler to reason about than every other kind of function. Furthermore, non-local computations cannot affect local ones, because there are no changes/mutation at all.
More: Pure Function Wiki
More: What is a non-local change?

Remodel Functions With Side-Effects

To compose functions with side-effects, the first step is to turn a real side-effect into a new container type: In our examples Optional replaces an exception as the side-effect of potentially not-having a return value for some inputs. So, suppose the previous functions aren’t pure but throw runtime exceptions on some inputs:

   String -> Integer                  Integer -> Boolean
// |with hidden exception|           |with hidden exception|

We remove those exceptions and wrap/extend/embellish the return type instead:

   String -> Optional<Integer>                 Integer -> Optional<Boolean>
// |----"impure" function----|                 |-----"impure" function----|

These functions now have remodelled the no-value-side-effect to the type-level. However, we now face the problem that the return type of the left function doesn’t match the input type of the right one: Normal function composition doesn’t work anymore.

Compose Monadic Functions

The next step is where the Monad concept comes into play. Since Optional is a Monad it has a flatMap function: We can compose (only) the left Optional value with the right “impure” function:

             Optional<Integer>   `flatMap`     Integer -> Optional<Boolean>
//           |--- monad -----|                 |---"impure" function------|

But we want to compose the whole left function, not just its Optional!? So we extend that flatMap function to build the composition operator we actually want:

   String -> Optional<Integer>    `>=>`        Integer -> Optional<Boolean>
// |----"impure" function----|     fish        |-----"impure" function----|
// result:      
// String ----------------------------------------------> Optional<Boolean>

This “fish operator”, as it appears in Haskell, is a combination of a andThen call on the left function and flatMap on the left Optional as follows:

   (String -> Optional<Integer>).andThen(
                                 opt -> opt.flatMap(Integer -> Optional<Boolean>))
//  |----"impure" function----|                     |-----"impure" function----|
// result:      
// String ---------------------------------------------------> Optional<Boolean>

It composes two “impure” functions into one, similar to normal function composition. The result is a function that goes directly from String to Optional<Boolean> and is correct by construction, because again there is no glue code that we can mess up.
See: Java’s andThen Guide
See: Java Optional flatMap Guide

The main takeway is that Monads allow us to model side-effects in a pure way and to compose functions with those “side-effects” in a safe, crash-free, less-error prone and easy-to-test way.

We have only seen the Optional Monad, but there are many more Monad types, each representing a different side-effect. For example, there is the nowadays essential Future/Promise Monad type for asynchronous programming that represents the side-effect of not having a value yet but sometime in the future. Luckily, all Monads have in common that you can think of them as containers with a flatMap function: If you know how to use one, you can use all the others with less difficulty.
More: Bartosz Milewski’s Category Theory Monad Introduction Using Fish
More: What is a Functor?

Tags: functional programming software engineering monad haskell java javascript

Malte Neuss

Java Software Engineer by day, Haskell enthusiast by night.

Other Posts In Series

Just Enough FP For OO-Developers

The Best Container Interface: Functor. I Promise.

If you have used the Java Stream Api, the Browser’s Fetch Api or modified a list in Javascript, you’ve come across a Functor. It’s a beautiful, intensively analyzed concept from Category Theory and Functional Programming that you’ll begin to see everywhere.

Read More