Let
A
,B
,C
be typesunit: A -> Monad<A>
a constructorf: A -> Monad<B>
,g: B -> Monad<C>
functionsa
be an object of typeA
m
be an object of typeMonad<A>
Then all instances of the Monad
interface should obey the three control laws:
- Left identity:
unit(a).flatMap(f) ≡ f a
- Right identity:
m.flatMap(unit) ≡ m
- Associativity:
m.flatMap(f).flatMap(g) ≡ m.flatMap(x -> f.apply(x).flatMap(g))
Monads in Javaslang
Strictly, given a Monad type Monad
, the signature of the flatMap
method should look like this in Java. For the sake of simplicity we omitted other interface methods like Functor.map
.
interface Monad<A> {
<B> Monad<B> flatMap(Function<? super A, ? extends Monad<? extends B>> f);
}
Example:
interface Try<A> extends Monad<A> {
<B> Try<B> flatMap(Function<? super A, ? extends Try<? extends B>> f);
}
Because of the lack of higher-kinded types we cannot define the Try
interface as shown above. It does not compile because we changed the function return type from ? extends Monad
to ? extends Try
. To circumvent this limitation we relaxed the Monad interface a bit:
interface Monad<A> extends Iterable<A> {
<B> Monad<B> flatMap(Function<? super A, ? extends Iterable<? extends B>> f);
}
We are now able to define Try
like this:
interface Try<A> extends Monad<A> {
<B> Try<B> flatMap(Function<? super A, ? extends Iterable<? extends B>> f);
}
The Monad implementations are still satisfying the (adapted) Monad laws.
Examples:
// = Some(0.1)
Option.of(10).flatMap(i -> Try.of(() -> 1.0 / i));
// = List(-1, 1)
List.ofAll(-2, 0, 2).flatMap(i -> Try.of(() -> 2 / i));
// = TreeSet(1, 2, 3, 4)
TreeSet.ofAll(3, 2, 1).flatMap(i -> List.ofAll(i, i + 1));
// = Failure(java.lang.ArithmeticException: / by zero)
Monad.lift((Integer a, Integer b) -> a / b)
.apply(Try.success(20), List.ofAll(0, 4, 1, 2, 3));
// = Success(5)
Monad.lift((Integer a, Integer b) -> a / b)
.apply(Try.success(20), List.ofAll(4, 1, 2, 3));
// = List(5, 20, 10, 6)
Monad.lift((Integer a, Integer b) -> b / a)
.apply(List.ofAll(0, 4, 1, 2, 3), Try.success(20));
Just one word about this example:
One might expect that the result should be something like a
Success(Seq(5, 20, 10, 6))
.But this is not what the
Try.flatMap
method does.Try
is a single-valued type,List
is a multi-valued type.Try.flatMap
takes just the first element of the list, if present.We can't use
Monad.lift
to express it, it is more like this:However, the
sequence
method is static, so this will not work. (Note:sequence
currently isn't implemented for all Javaslang types, but it will be there soon.)