An intuition
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
@ToString @EqualsAndHashCode class Wrap<T> { private final T value; private Wrap(T value) { this.value = value; } public static <T> Wrap<T> of(T value) { return new Wrap<>(value); } }
This class is just going to wrap a value using factory method of
. There are no getters and setters, but present toString
, equals
and 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.
class Wrap<T> { // ... public <R> Wrap<R> map(Function<T, R> mapper) { return Wrap.of(mapper.apply(value)); } }
Wrap<Integer> a = Wrap.of(1); // Wrap(value=1) Wrap<Integer> b = a.map(i -> i + 9); // Wrap(value=10) Wrap<Integer> c = b.map(i -> i * 11); // Wrap(value=110) a.map(i -> i * 10).map(i -> i + 11); // Wrap(value=21)
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
Wrap<Integer> inc(Integer x) { return Wrap.of(x + 1); }
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.
Wrap<Integer> a = Wrap.of(1); // Wrap(value=1) a.map(this::inc); // Wrap(value=Wrap(value=2)) a.map(this::inc).map(this::inc); // !!! COMPILATION ERROR
First problem we face is that it wraps already wrapped result. And then we cannot continue applying it – inc
accepts only Integer
but not Wrap<Integer>
instance.
There should be some way to inc
value and not wrap it again.
class Wrap<T> { // ... public <R> Wrap<R> flatMap(Function<T, Wrap<R>> mapper) { return mapper.apply(value); } }
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
Wrap<Integer> a = Wrap.of(1); // Wrap(value=1) a.flatMap(this::inc); // Wrap(value=2) a.flatMap(this::inc).flatMap(this::inc); // Wrap(value=3)
Actually flatMap
is more generic then map
and we can implement map
using it
class Wrap<T> { // ... <R> Wrap<R> map(Function<T, R> mapper) { return flatMap(mapper.andThen(Wrap::of)); } }
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
interface Monad<T> { Monad<T> of(T value); <R> Monad<R> flatMap(Function<T, Monad<R>> mapper); }
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
Optional<Integer> a = ... Optional<Integer> b = ... return add(a, b);
Such function add
should return new Optional with the result of adding values
Optional<Integer> add(Optional<Integer> oa, Optional<Integer> ob) { return oa.flatMap(a -> ob.map(b -> a + b)); }
Lambda inside flatMap
has access to value of oa
and uses it to increment value of ob
similarly to previous inc
function.
Optional<Integer> a = Optional.of(13); Optional<Integer> b = Optional.of(42); add(a, b); // Optional[55] add(a, Optional.empty()); // Optional.empty add(Optional.empty(), b); // Optional.empty
What if we need to perform other operations? Lets create another method that additionally accepts an operation
<A, B, R> Optional<R> compute(BiFunction<A, B, R> operation, Optional<A> oa, Optional<B> ob) { return oa.flatMap(a -> ob.map(b -> operation.apply(a, b))); }
It is little bit to verbose but basically compute
applies operation
on values from optionals and returns optional result
Optional<Integer> a = Optional.of(13); Optional<Integer> b = Optional.of(42); BiFunction<Integer, Integer, Integer> plus = (x, y) -> x + y; BiFunction<Integer, Integer, Integer> times = (x, y) -> x * y; compute(plus, a, b); // Optional[55] compute(times, a, b); // Optional[546]
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[24] 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
stream.reduce((acc, elem) -> compute(times, acc, elem)); // Optional[Optional[24]]
It wraps result into optional, because stream can be empty and we didn’t provide any initial value.
Flattening
How can we deal with optional of optional without unwrapping it? We can flatten it with flatMap
Optional<Optional<Integer>> ooa = Optional.of(Optional.of(24)); Optional<Integer> oa = ooa.flatMap(o -> o); // Optional[24]
Function o -> o
is called identity and actually it is so useful that you can find it in standard library Function#identity
Wrap-up
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 ;)