On Monads 2: monads are everywhere

Like most modern scientific theories, the theory of monads is descriptive rather than prescriptive. When applied to programming, the theory is not supposed to tell you how to program nor how to design your libraries or programming languages. Instead the theory helps to describe how a computer composes side effects when it executes a program.

In Java the type signature of a method tells us that it returns a value of a certain type, but that is certainly not all a method can do.

  1. A method can wind up in an endless loop or a deadlock and never return anything.
  2. A method can throw an exception which will abort the computation of the method that is calling it, unless caught. Java also allow throwing errors, which shut down the virtual machine.
  3. If the return type is a class, the method can return null instead of an instance of that class.
  4. A method can change the state of the object it is defined on, the state of objects that are passed to it as arguments, or the state of global variables. This in turn may change the behavior of all methods dependent on those states.
  5. A method can start a new thread and cause the virtual machine to perform computations after the method returns.
  6. A method can communicate with the world outside of the virtual machine by performing IO operations.

For all of these side effects ordinary mathematical functions between sets don’t model Java property. Instead, we need a ‘Java monad’ to capture all outcomes of a method and to explain how side effects are combined when methods are composed into larger methods.

Resisting monads is futile. You don’t avoid the monads but a ubiquitous language for composing side effects. That means that your refusal to conform makes it harder for your customers to find and recognize solutions to their problems in other people’s code. I grant that this is a prescription, but note that it comes from outside the theory of monads itself.

There are other ways monads could help, however. The Java monad is pretty messy and this can make Java programs hard to understand for programmers and also for static analysis tools. For that reason, good programming style often comes down to ‘writing purer functions’.

  1. Use for-loops instead of while-loops, or better yet, iterate through a collection by passing a closure to a library function.
  2. Don’t use exceptions for business logic.
  3. Avoid using null.
  4. Avoid global variables and make objects immutable.
  5. Don’t write asynchronous code if you don’t have to. Use a futures monad when you must.
  6. Decouple parts of your program that have to perform IO.

These precautions would probably not be necessary if Java had been pure by default and only let programmers access side effects through monads, instead or expecting programmers to have the discipline to avoid them.

On Monads

Monads are like lions. It is easy to show what a lion is, but afterwards the students won’t understand lions or be able to work with them.

I will use a Java8-like syntax and terminology for code. A monad consists of a generic type M<T> and two functions–M<T> unit(T t) and M<U> bind(M<T> mt, Function<T,M<U>> f)–that satisfy the monad laws:

  • bind(unit(x),f) == f(x)
  • bind(x,unit) == x
  • bind(bind(x,f),g) == bind(x,y -> bind(f.apply(y),g))

That is all.

To understand monads you need to know why people use them. Programs are usually composed of smaller programs and monads let you redefine how composition works. This way, you can extend all datatypes with new operators, while preserving existing ones.

Take the a typical list monad for example. Instances of the generic type List<T> have methods for working with lists as part of the generic type itself. The function unit sends and object x to a one element list unit(x). That way it makes list operations available on the object. On the other hand, unit(x.anyMethod(y)) turns the result of any method defined on x into a list. Finally the function bind(l,f) applies the list valued function f to every element of the list l and concatenates the results in order into one big list. That way we can apply list valued functions to lists like we can apply ordinary functions to ordinary objects. Hence List<T> has access to both the methods of T and of List and also to every possible combination of them.

You can use the list monad for non deterministic algorithms, where functions can have several different return values. Many other kinds of functions and other methods of composition have their own monads. For example, a futures monad takes care of non blocking asynchronous computations while hiding the callback hell that usually goes with it.

Working with monads is a matter of remembering that you introduce a new form of composition on purpose and that you usually neither have to leave it behind, nor want to do so. Generally you want to keep using the bind and unit functions to keep everything happening “inside the monad” until “the end of the world”, which is the main method of your program. As always, practice makes perfect.