Monads for Java developers: Part 2 — Two monads more

If you are new to monads, I recommend reading the first part of this series for a basic introduction to monads in Java: Monads for Java developers: Part 1 — The Optional Monad

abstract: In this second part I will reinforce the the concept of monad by showing what happens inside the bind (flatMap) method. Also I will present the Log class to show how we can have side-effects-free logging capabilities thanks to monadic composition.

Summary of Part 1

  • Monads refer to types that wrap values such as Optional and Stream in Java.
  • The monad provides a context to the value: Optional<String> provides an Optional context to the String it wraps.
  • A monad has to implement bind (flatMap) and unit. Also it has to comply with three laws: Left identity, Right identity and associativity.
  • Monads benefits are clearer when composing them because they help us forget about the context. Composition in this case refers to function composition[1] and not to OOP composition.

Let’s write a monad from scratch with the aim of showing the internals of the flatMap method.

The Result class

In Java, one way of dealing with failure scenarios in business logic is by throwing exceptions albeit it’s not the only way. Without the aim of discussing when we should use exceptions and when we should not, let’s try to write an alternative to them that will help us to highlight the properties of monads.

The Result class represents the outcome of an operation that might fail. It contains either a value that represents the positive outcome of such operation, or an error String. [2]

First, let’s see the way it can be used:

Here we have some methods that return a Result type. See in the body of the method adjustValue how we check a business condition and return error with a message or an ok result after executing the operation. Also calculateAverage does not return a Result type since it doesn’t have any business failure scenario.

So far, the implementation of the Result class would be rather simple:

Two factory methods, one private constructor and some accessors. Nothing more.

Now, what would happen if we want to operate with methods that returns a Result object? Let’s take the ones we defined before and use them inside a business operation:

See the full implementation with all the methods here:

What we do here is checking the Result of each method invocation to see if there is an error[3]. If not, we continue with the next step. Since calculateAverage does not return Result, then we just wrap the value with Result.ok (line 25).

This is fine, but we have seen this pattern before haven’t we? It is pretty similar to the Optional monad examples in the previous post. So how about we try to convert the Result type into a Monad. Remember, we need unit, flatMap and a bunch of rules[7].

Unit is already there: “Result.ok”. It will take any type and wrap it in the Result context producing a Result of that type: Result.ok(3) will produce a Result<Integer>.

Now let’s add flatMap to the Result class:

See the Result full implementation including flatMap here:

The flatMap method receives a Function, which we will call mapper, and returns a Result of type U. Mapper in turn, receives the Result parameter T and returns a different Result with parameter U. This allows us to call flatMap on a Result<Integer> and produce a Result<String> for example.

Look how we invoke the mapper function (line 11) only if the Result its not an error. In case the Result is an error, we just return a new error[4] with the same message (line 8).

After implementing flatMap, the last step to transform Result into a monad is to check that it complies with the Monads rules. Good news, it does! Here is the proof:

Great! now we have the Result monad. It’s very similar to the Optional monad, just that it does store an error value instead of empty. Now we should be able to rewrite the businessOperation method using monadic composition:

See the full implementation with all methods here:

As we did in the previous post when composing Optionals, we chained and nested flatMap calls to compose the monads and produce a Result. Since calculateAverage (line 12) needs two values, we had to nest the flatMap calls on lines 10 and 11. Also, remember that calculateAverage does not return a Result type, so we use the unit function (Result.ok) to wrap it into the Result context. Again, if any of the operations in the composition returns an error, the composition chain stops in there and the final result will be that error. Otherwise you will get an ok Result<Double>.

As you can see, we compose the Result monad the same way we compose the Optional monad. As said before: All monads compose the same way. This is one of its main benefits.

Unfortunately, Java does not provide syntax sugar for Monadic composition, but other languages such as Scala or Haskell, take advantage of this and provides nice generic utilities that apply to all monads [5].

Until now we have focused on the way monads are composed, but haven’t explored too much the way they abstract the context from us. Hopefully the Log type will show this more explicitly.

The Log class

The Log class is an implementation of the Writer monad. The idea with this class is to keep a trace of the changes that a value has suffered during a period of time.

Before diving into the implementation, let’s take a look at how you would use this class.

Here we have some methods that receive a value and return a Log object. Log.trace would simply create a Log object with a value and a trace associated to it.

The interesting thing happens on the run method: Because Log is a monad, we compose it the same way we have done it in the previous examples. Remember that it’s the same way for all monads.

If we run this method, the console will output:

Value: 3.5
Trace: [initial value: 5, run operation by adding 2: 7,
divided by 4: 1.75, Multiplied by two: 3.5]

The final Log contains a value equals to 3.5 and also a trace which is a concatenation of all the traces involved in the composition. Look also how the trace contains the partial result at that particular time.

Remember when I said that Monads help us composing them without needing to worry about the context? Well, in this case the context is the trace added to the value, and effectively, we didn’t do anything to aggregate the trace or to input the partial value to each one of them.

With this approach, we can add logs anywhere and the composed Log monad will aggregate them transparently. Lets change operation2 to execute two different things instead of one:

The run method hasn’t changed and yet the output includes the new logs of operation2:

Value: 6.0
Trace: [initial value: 5, 2a -> add 2: 7, 2b -> add 5: 12,
divided by 4: 3.0, Multiplied by two: 6.0]

Part of the implementation of the Log type is as follows:

See the full implementation here:

First, each Log contains a value and a list of String that represent the trace for the value. On line 6 we have the trace method that will create a new Log with a list of one trace. Also this is the place where the partial value is added to the trace.

The flatMap method (line 10) calls the mapper function to obtain the new Log (line 11) and then concatenates the current trace with the trace obtained from the mapper result. Finally a new Log is created with the value from the mapper and the aggregated trace.

Monads can wrap other monads as well. If we put a Result into a Log like this: Log<Result<Integer>> we will have a Log of a Result that yields an Integer, so that if it is ok, you will have the Integer value and trace of changes, but if the Result is error, then you will have an error Result and a trace. Effectively, a stack trace with a twist: It will show the partial value of the Result on each step.

To finalise

We can think of Monads as a design pattern that we can use to define the way parameterised types can compose. In Java, they are just another tool that has benefits and drawbacks. In other languages using monads is mandatory since it’s the only way to have side effects[6].

The classes we have explored are implementations of some well known monads: Optional is an implementation of the Maybe monad, Result is an Either monad and Log is a Writer monad. Although there are some famous monads, you can come up with your own one. Just be sure to implement the methods and follow the laws[7].

Monads also exists in non typed languages, such as Javascript. A Promise is a monad in which the bind method is called then. That is a perfect example of a monad that can not be unwrapped (get the value out). The only way to get the value out of a Promise is via the bind (then) function.

Thank you for reading.

ps: If you liked the article and believe that it might be interesting for someone else, please ❤.


[1] Function composition in Java refers to: given B method1(A a) and C method2(B b) then the composition is: c = method2(method1(a))

[2] In this case, String could be any type, including an Exception. I am using String for simplicity.

[3] A new error has to be created because we have to transform Result<Integer> to Result<Double>

[4] Same as [3], we need to transform Result<T> to Result<U>.

[5] In Haskell ( and in Scala ( monads have special notation.

[6] The side effects I refer in here are wanted side effects, such as writing to disk, or showing something in screen. In Haskell the only way to achieve this is via the IO monad.

[7] In this post, the author talks a bit about the benefits of monad laws: I plan to write another post that will try to explain the benefits in a more detailed way. If you are interested, leave a comment and I will let you know when the post is ready.

Recommended reads: The monad section of this book is just awesome. with the implementation of the Try monad, which is a better version of the Result class we implemented. An interesting approach to understand monads using Java.