The Monad Design Pattern in Java
Introduction
If you haven’t used monads before, you’ve almost certainly heard about them. Monads are widely used in purely functional programming languages but even non-functional programming languages make use of monads.
In this post, we will try to understand monads as a design pattern, firstly, by introducing them in the functional programming paradigm, and later exploring them in Java specific contexts.
What is a Monad?
The concept of a monad originally comes from a field of mathematics called Category Theory. It was initially introduced to theoretical computer science in the early 1990s to develop the categorical semantics of computations, in particular proving the equivalence of programs. Since then, applications of monads in programming language theory have become more sophisticated.
Monads are widely used in purely functional programming languages to integrate some flexibility provided by imperative programming languages. As a result, they have become so popular in the programming community that programmers define monads as design patterns. Some programming languages that are not necessarily functional programming languages make use of monads extensively. The most popular of these is Java, which even has built-in classes which can act as monads.
Monad use in functional programming
Monads can be used in functional programming to allow pure functions to “have side effects”. The first implementation was applied to Haskell in order to handle problems with I/O that were being caused by lazy evaluation. In Haskell all functions are pure and all values are evaluated lazily. However, lazy evaluation is not really desirable for I/O actions. Some mostly functional programming languages (like Lisp and SML) sacrifice functional purity in order to get successful I/O. By contrast, Haskell’s I/O system preserves functional purity with a feature called IO Monad, one of many uses of monads in functional programming.
One way of integrating a monad design pattern into your code is to wrap impure or slippery code using embellished types. Here, think of slippery code as something that can easily go wrong if its input is slightly unusual. Wrapping up chunks of code with an embellished type can be helpful for abstraction, but these chunks are not really useful on their own. To this end, we also need some tools to be able to put them together, and connect them with each another. The following example will be really helpful to understand a potential usage of such embellished type and monadic tools.
Let’s assume we have 2 methods.
public Integer divide(Integer a, Integer b) {
return a/b;
}public Integer addTen(Integer a) {
return a+10;
}
One of the main elements of functional programming is having composable methods, so one can feed the value of one method directly into another. Function composition comes from mathematics and is quite common in programming as well. The following diagram is a good comparison of composition in mathematics and programming.
Here, in our example, our 2 methods can be composed as well. In other words, we can feed the value of divide
into addTen
(e.g addTen(divide(8,2)) = addTen(4) = 14
). As one can guess, such composition can go wrong when the denominator or the second parameter of divide
is zero. What should we do now?
Let’s first make sure the methods are pure functions. It is obvious that method addTen
is already a pure function, while divide
is not. So, we need to modify divide
.
public Optional<Integer> divide(Integer a, Integer b) {
if (b == 0) {
return Optional.empty();
}
return Optional.of(a/b);
Or in a more elegant way:
public Optional<Integer> divide(Integer a, Integer b) {
return (b == 0) ? Optional.empty() : Optional.of(a/b);
}
At this point, we might ask why we are using Optional
s but not null
s. This is the million dollar question that this post tries to answer. Just to give a little spoiler, Optional
s are monads in hindsight, but we do not have to know that, since we still do not know what monads are. Sit tight, we are getting there.
Since one of our methods returns Optional
, we can change addTen
to return Optional
too. As a result, we would have:
public Optional<Integer> divide(Integer a, Integer b) {
return (b == 0) ? Optional.empty() : Optional.of(a/b);
}public Optional<Integer> addTen(Integer a) {
return Optional.of(a + 10);
}
As we can see, these methods are not directly composable anymore with our usual function composition operation. However, we can still do the following:
public Optional<Integer> divideAndAddTen(Integer a, Integer b) {
final Optional<Integer> divisionResult = divide(a, b);
if (divisionResult.isPresent()){
return addTen(divisionResult.get());
}
return Optional.empty();
}
This method will work perfectly, and will return the result we need. However, this is still not “the special function composition” we are looking for. This method does not use Monads.
What is a Monad anyway? We have been talking about it for so long, but have not yet given any proper definition for it. Well, there was a good reason why we have delayed it so long. Before knowing what a monad is, we needed to understand why we might need it. Now, we have a use-case for monads, so let’s go on to the less-rigorous definition:
Definition: Monad is a generic type constructor (i.e JavaType -> JavaType
) along with 2 mappings:
unit
mapping: That applies given a type constructor to a Java Type, or function.flattener
: Unwraps JavaType if a type constructor has been applied to it at least twice.
Okay, that was too abstract. Let’s give couple of examples to make more sense of this definition. Let’s say we have type constructor Optional:: JavaType -> JavaType
. Then our unit mapping would be :
unit:: T -> Optional<T>
unit:: (X -> Y) -> (Optional<X> -> Optional<Y>)
In Java Optionals, unit:: T -> Optional<T>
would be Optional.of()
. However, unit:: (X -> Y) -> (Optional<X> -> Optional<Y>)
is a little bit tricky. In that case, unit
takes a function as an input and returns its elevated form. For example, let’s say parseInt:: String -> Integer
is a method that takes a String
and returns it in Integer
representation. If we apply our unit
function to parseInt
that would be:
unit(parseInt) :: Optional<String> -> Optional<Integer>
We also need, flattener
, which is:
flattener:: Optional<Optional<T>> -> Optional<T>
In Java Optionals, that is similar to, but not exactly the same as flatMap
.
Here, Optional
is a monad by definition, and can be used within the design pattern of monads. There are many other generic type constructors in Java, such as List
, Stream
, Collection
etc, which can be used within the monad design pattern as well.
Now, let’s go back to our original example. Since, we already use Optional
s, let’s take it a step further and rewrite our divideAndAddTen
method in a monadic form. That would be:
Optional<Integer> divideAndAddTenWithMonad(Integer a, Integer b) {
return divide(a,b)
.flatMap(divisionResult -> addTen(divisionResult));
}
Again, as it is trivial from this snippet, flatMap
is not the same with flatten
function that comes with our monadic type constructor. In fact, flatten
is the last step in the execution.
Now this divideAndAddTenWithMonad
will return Optional<Integer>
result if its inputs are proper, otherwise it will return Optional.empty()
.
So, now we can confidently say that we have that “special function composition” we mentioned before. In the functional programming world, this is denoted as >=>
. Hence, divideAndAddTenWithMonad
is basically
addTen >=> divide
Conclusion
Monads give us the ability to use pure functions in our imperative programs, while dealing with all side effects. This already sounds exciting. How can pure functions and side effects be put together?
Basically, most of the side effects, including I/O, database read/write, System.in/System.out can be monadded. If you are curious how can you monad System.in/System.out operations, there is an amazing article by Vladimir Shiryayev titled Java IO Monad. Reality or Fiction.
In conclusion, monads can be used to push all of your program’s side effects to its edge and use pure functions as much as possible. This will in turn make your code less susceptible to the uncertainty of the real world we live in. :)
We’re recruiting
Find out about the exciting opportunities at THG here:
References
- Vladimir Shiryayev: Java IO Monad. Reality or Fiction.
- Cesar Tron-Lozai : What the ƒ is a Monad?
- Brian Beckman: Don’t fear the Monad
- Russ Olsen: Functional Programming in 40 Minutes • GOTO 2018