What Is a Monad? Basic Theory for a Java Developer
Are you a Java developer who wants to know the theory behind Monads? Here you will find a step-by-step tutorial that will help you understand them.
Join the DZone community and get the full member experience.
Join For FreeA monad is a concept originating from a part of mathematics called category theory, not a class or trait. In this article, I will try to explain its structure and inner workings. With the use of Optional from Java, I will try to describe all of this in a more understandable way. I will also implement a basic monad to better understand how they work and conclude with a short usage example to show the monad’s advantage over the non-monad approach.
The source code for this article is available in GitHub repository.
Why Learn How Monads Work?
First of all, it is always good to have a basic understanding of how things that we use work. If you are a Java developer, you probably use monads and may even not know it. It may surprise you but two of the best-known Java 8 features, namely Stream and Optional are monad implementations.
In addition, functional programming is becoming more and more popular nowadays, so it is possible that we will have more similar monadic structures. In such a case, knowing what a monad is and how it works may become even more valuable.
If you also want to learn something more about Optional history I can recommend you another one of my texts in which I am describing changes in Optional API since its introduction.
Let’s start with describing what a monad is – more or less accurately. In my opinion, the matter here is fairly straightforward.
Monad is just a monoid in the category of endofunctors
Based on a quote from "Categories for the Working Mathematician" by Saunders Mac Lane.
Back to being serious...
What Is Monad?
After reading the intro, you know that a monad is a concept from category theory. In the world of software, it can be implemented as a class or trait in any statically typed language with generics support. Moreover, we can view it as a wrapper that puts our value in some context and allows us to perform operations, specifically operations that return value wrapped in the same context, on the value. Also, we can chain the operations in such a manner that the output of an operation at any step is the input to the operation at the next step.
Examples of monads in modern-day programming languages:
- Stream (Java)
- Optional/Option (Java/Scala)
- Either (Scala)
- Try (Scala)
- IO Monad (Haskell)
Monad Laws
The last thing that needs mentioning while speaking of monads is their laws. If we want to consider our implementation a real monad, we must obey them. There are three laws: Left Identity, Right Identity, and Associativity. In my opinion, it can be somewhat hard to understand what they actually mean.
Now, with the help of Referential Monad, I will try to explain the above laws in a more detailed way.
But first a few assumptions:
- f is a function mapping from type T to type Optional<R>
Function<Integer, ReferentialMonad<String>> f = i -> ReferentialMonad.of(i.toString());
- g is a function mapping from type R to type Optional<U>
Function<String, ReferentialMonad<Long>> g = s -> ReferentialMonad.of(Long.valueOf(s));
Left Identity
If we create a new monad and bind it to the function, the result should be the same as applying the function to the value.
JavaReferentialMonad<String> leftIdentity = ReferentialMonad.of(x).flatMap(f); ReferentialMonad<String> mappedX = f.apply(x); leftIdentity.equals(mappedX);
Right Identity
The result of binding a unit function to a monad should be the same as the creation of a new monad.
JavaReferentialMonad<Integer> rightIdentity = ReferentialMonad.of(x).flatMap(ReferentialMonad::of); ReferentialMonad<Integer> wrappedX = ReferentialMonad.of(x); rightIdentity.equals(wrappedX);
Associativity
In the chain of function applications, it should not matter how functions are nested.
JavaReferentialMonad<Long> leftSide = ReferentialMonad.of(x).flatMap(f).flatMap(g); ReferentialMonad<Long> rightSide = ReferentialMonad.of(x).flatMap(v -> f.apply(v).flatMap(g)); leftSide.equals(rightSide);
If you are enjoying reading about Monads and want to learn similarly related concepts, I recommend you read my latest article about Functors.
Creation of Monad
Now, when we know the basics, we can focus on implementation.
The first thing we need is a parameterized type M<T>, which is a wrapper for our value of type T. Our type must implement two functions:
- of (unit) which is used to wrap our value and has the following signature M<T>(T).
- flatMap (bind) responsible for performing operations. Here we pass a function that operates on value in our context and returns it with another type already wrapped in context. This method should have the following signature M<U> (T -> M<U>).
To make it more understandable, I will use Optional one more time and show what the above structure looks like in this case.
Here, the first condition is met right away because Optional is a parameterized type. The role of the unit function is fulfilled by ofNullable and of methods. FlatMap plays the role of the bind function. Of course, in the case of Optional, type boundaries allow us to use more complex types than in the definition above.
Done With the Theory, Let’s Implement
package org.pasksoftware.monad.example;
import java.util.function.Function;
public final class WrapperMonad<A> {
private final A value;
private WrapperMonad(A value) {
this.value = value;
}
static <A> WrapperMonad<A> of(A value) {
return new WrapperMonad<>(value);
}
<B> WrapperMonad<B> flatMap(Function<A, WrapperMonad<B>> f) {
return f.apply(value);
}
// For sake of asserting in Example
boolean valueEquals(A x) {
return value.equals(x);
}
}
Et voila, monad implemented. Let’s describe in detail what exactly I have done here.
What Exactly Happened Here
The base of our implementation is the parameterized class with the immutable field named "value", which is responsible for storing our value. Then, we have a private constructor, which makes it impossible to create an object in any other way than through our wrapping method - of.
Next, we have two basic monad functions, namely of (equivalent of unit) and flatMap (equivalent of bind), which will guarantee that our implementation fulfills the required conditions in the form of monad laws.
With the functionalities described, it is time for a usage example. So here it is.
package org.pasksoftware.monad.example;
import java.util.function.Function;
public class Example {
public static void main(String[] args) {
int x = 2;
// Task: performing operation, returning wrapped value, over the value inside the container object.
// Non-Monad
Function<Integer, Wrapper<String>> toString = i -> new Wrapper<>(i.toString());
Function<String, Wrapper<Integer>> hashCode = str -> new Wrapper<>(str.hashCode());
Wrapper<Integer> wrapper = new Wrapper<>(x);
Wrapper<String> stringifyWrapper = toString.apply(wrapper.value);
// One liner - Wrapper<Integer> hashCodedWrapper = hashCode.apply(toString.apply(x).value);
Wrapper<Integer> hashCodedWrapper = hashCode.apply(stringifyWrapper.value);
// Monad
Function<Integer, WrapperMonad<String>> toStringM = i -> WrapperMonad.of(i.toString());
Function<String, WrapperMonad<Integer>> hashCodeM = str -> WrapperMonad.of(str.hashCode());
WrapperMonad<Integer> hashCodedWrapperMonadic = WrapperMonad.of(x)
.flatMap(toStringM)
.flatMap(hashCodeM);
assert hashCodedWrapperMonadic.valueEquals(hashCodedWrapper.value);
System.out.println("Values inside wrappers are equal");
}
}
In the code above, apart from seeing how monads work, we can also see a few pros of using them.
In the monadic part, all operations are combined into a single execution pipeline which makes the code more declarative, and easier to read and understand. Additionally, if we decided someday to add error-handling logic, it can be nicely encapsulated inside of and flatMap methods.
On the other hand, in the non-monadic part of the example, we have a different design with package-private field value, we need a way to access value from outside the wrapper, which breaks the encapsulation. As for now, the snippet is readable enough but you may notice that it is not extension-friendly. Adding any kind of exception handling may make it quite spaghetti.
Summing up
Monad is a very useful and powerful concept and probably many of us use it in our day-to-day work. I tried to provide a clear and descriptive explanation of its theoretical basis and logic behind it. I implemented a custom monad to show that it is not a complex structure. In the example above, I showed what the use of Monad may look like, what the potential pros of such an approach are, and how it can differ from normal methods call. Thank you for your time.
If you enjoyed reading about Monads and want to read more about other similar concepts I can recommend you my article about Functors.
Frequently Asked Questions About Monad
What Is Monad?
A Monad is a concept originating from a part of mathematics called category theory.
Why Should I Care About Monad?
If you are a Java developer, you probably use Monad every day but I can tell you have questions. For example, Stream and Optional are Monad implementations that are the most confusing objects. Additionally, functional programming is becoming more and more popular, so we may expect more structures like this.
What Are Monad Laws?
Every Monad implementation has to satisfy three laws: Left Identity, Right Identity, and Associativity.
What Do I Need to Implement Monad?
To implement Monad, you need a parameterized type M<T> and two methods unit and bind.
Opinions expressed by DZone contributors are their own.
Comments