What Is a Monad? Basic Theory for a Java Developer
What Is a Monad? Basic Theory for a Java Developer
In this article, we dive into the basics behind Monads in Java and discuss an implementation in depth.
Join the DZone community and get the full member experience.Join For Free
As you can guess from the title, the main topic of this article will be monads. I will try to dive deep in their structure and inner workings. With some help of Java Optional, I will try to describe all of this in a more detailed way. At the end of this article, I will implement a log monad — a type of a writer monad —and then describe the role of each major code fragment and provide a simple example of its usage.
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 without even knowing about it. It may surprise you, but two of the most commonly known Java 8 features are monad implementations, namely Stream and Optional. In addition, functional programming becomes more and more popular nowadays so it is possible that we will have more similar monadic structures. Then, this knowledge will be even more very valuable.
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...
Monad is a concept, not a class or trait. Of course, it can be further implemented as a class or trait. It is possible in almost any statically typed language with generics support. Moreover, we can view it as a wrapper which puts our value in some context and allows us to perform operations on the value. In this context, 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).
The last thing that needs mentioning while speaking of monads are 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 Optional I will try to explain above laws in a more detailed way.
But first a few assumptions:
- F is a function with the signature:
(T -> Optional<U>) = Optional<U>.
- G is a function with the signature
(A -> Optional<B>) = Optional<B>.
FG = F.apply(value).flatMap(G)with the signature:
(T -> Optional<B>) = Optional<B>.
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:
Right identity: The result of binding a unit function to a monad should be the same as the creation of a new monad:
Associativity - in the chain of function applications, it should not matter how functions are nested:
Optional<B> leftSide= Optional.of(value).flatMap(F).flatMap(G)
Optional<B> rightSide= Optional.of(value).flatMap(F.apply(value)
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:
unitwhich is used to wrap our value and has a following signature
(T) = M<T>.
bindresponsible for performing operations. Here we pass a function which operates on value in our context and returns it with other type already wrapped in context. This method should have the following signature
(T -> M<U>) = M<U>.
To make it more understandable, I will use Optional one more time and show what the above structure looks like in its case.
Here, the first condition is met right away because Optional is a parameterized type. The role of the
unit function is fulfilled by
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
Et voila, monad implemented. Let’s describe in detail what exactly I have done here.
The base of our implementation is the parameterized class with the immutable field named "value", which is responsible for storing our value. The second field is "Logger", which will be responsible for the key effect of our monad. Then, we have a private constructor, which makes it impossible to create an object in any other way than through our wrapping method.
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 form of monad laws. The last two methods are responsible for our monad effect, i.e. they are responsible for logging the current value to standard output. One of them allows to pass the logging level, the other one uses “INFO” level.
With the functionalities described it is time for a usage example. So here it is.
In the code above, other than seeing how monads work, we can also see a few pros of using them.
First of all we encapsulate our logging side-effect within a monadic context. That provided an abstraction layer over our logic. Through this abstraction we were able to reduce the amount of boilerplate. Our code became more declarative, easier to read and understand. Finally, we combined all operations into a single pipeline.
On the other hand, in the non monadic part of example, we can see all the implementation details. The code is less descriptive and reusable. What is more, we exposed our side-effect that may cause some problems in the future and made our code less readable. If we ever decide to handle an error from a function call, we will need a lot of boilerplate.
Moreover, in my opinion, the monadic code is easier to read and understand than its non monadic counterpart.
Monad is a very useful concept. Probably many of us use it in our day to day life. In this article I tried to provide a clear and descriptive explanation of its theoretical basis and logic behind it. I implemented LogMonad to show that it is not rocket science and in fact it is fairly simple to implement one. In the example I showed what the use of monad may look like, what the potential pros of such an action are and how it can differ from normal methods call.
- Saunders Mac Lane's Categories for the Working Mathematician
Opinions expressed by DZone contributors are their own.