It is a paraphrase to Java of “The Trivial Monad” by Dan Piponi with applications for Java developers.
Monad is like a …
There is no good analogy to explain what is monad to new person (monad tutorial fallacy). The best way to get it right is just to do some coding. So lets explore simple design pattern - one-way wrapper. Such wrapper can wrap value but can’t unwrap it back.
In Java it can look like simple immutable object
This class is just going to wrap a value using factory method
of. There are no getters and setters, but present
hashCode for convenience (see lombok).
To do something with value we need a method that will apply mapping function and then return new wrapped value. It is important to keep value wrapped so it never escapes.
OK, now we can do something with this, but usually things are more interesting. After starting using wrapped values here and there we eventually create method like this
inc gets a number and then returns a wrapped result. It is very useful business logic and we want to apply it even to wrapped values.
First problem we face is that it wraps already wrapped result. And then we cannot continue applying it -
inc accepts only
Integer but not
There should be some way to
inc value and not wrap it again.
flatMap is still safe - it doesn’t give user a plain value but provides it to the mapper.
Now we can apply
inc several times in chains
flatMap is more generic then
map and we can implement
map using it
andThen composes function with another, passing result of first as argument to the second Function#andThen
Now we have all tools to work with our wrapper type.
of wraps a value and
flatMap gives a way to modify it without need to unwrap anything. And we can chain multiple transformations without worry how to unwrap layers of results.
Basically, this is a monad - type that provides APIs to enclose some value and modify it without exiting enclosed context
Real world problems
In Java there are several monadic types and even more with growing number of libraries. I will use Optional next, but these examples can be similarly applied to others.
Operations on Optionals
Assume we need to add two optional values. And we don’t know how to unwrap them, I mean don’t know what to do with empty values. All what we want is only add values together and leave that decision for later
add should return new Optional with the result of adding values
flatMap has access to value of
oa and uses it to increment value of
ob similarly to previous
What if we need to perform other operations? Lets create another method that additionally accepts an operation
It is little bit to verbose but basically
operation on values from optionals and returns optional result
Streams of Optionals
So far so good. But with Java 8 we usually deal with a lot of streams. It is a common case when during pipeline we end up with stream of optional values. Now as we know how to perform operations on optionals lets find a product of all optional values in stream
Optional<Integer> one = Optional.of(1); Stream<Optional<Integer>> stream = Stream.of(1, 2, 3, 4).map(Optional::of); stream.reduce(one, (acc, elem) -> compute(times, acc, elem)); // Optional stream = Stream.of(Optional.of(10), Optional.empty()); stream.reduce(one, (acc, elem) -> compute(times, acc, elem)); // Optional.empty
We provide initial value
one and then compute product of accumulator and each value in stream.
APIs are not always so friendly. Lets look at the reduce but without initial value
It wraps result into optional, because stream can be empty and we didn’t provide any initial value.
How can we deal with optional of optional without unwrapping it? We can flatten it with
o -> o is called identity and actually it is so useful that you can find it in standard library Function#identity
Sometimes it is better to operate on monads and leave decision how to unwrap them for later. Consider this next time when you will try to get value from Optional, CompletableFuture or some other monadic type. I hope you’ve learned here one or two methods how to simplify your design using operations on Monads.
Code is available on Gist - feel free to play with it.
Have a nice hack ;)