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.
- A method can wind up in an endless loop or a deadlock and never return anything.
- 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.
- If the return type is a class, the method can return
null
instead of an instance of that class. - 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.
- A method can start a new thread and cause the virtual machine to perform computations after the method returns.
- 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’.
- Use
for
-loops instead ofwhile
-loops, or better yet, iterate through a collection by passing a closure to a library function. - Don’t use exceptions for business logic.
- Avoid using
null
. - Avoid global variables and make objects immutable.
- Don’t write asynchronous code if you don’t have to. Use a futures monad when you must.
- 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.