Monads and Java 8

Jul 10, 2015 22:37

This post was supposed to be written nearly a year ago, but whatever.

Java 8 is one of those things that were supposed to happen, and happened. It took pretty long for Java to get all those lambdas, monads, streams and other stuff, but Java made it.
Who cares about stealing an idea or two from competitors on the way, right?
Being a-little-bit-functional is kinda' The Way now and Java gave us two (!) monad-style classes: Optional and Stream.

You know what a monad is? No, forget about that "bind" shit scala/haskell guys try to sell you. A monad is simply a generic class with one type parameter and at least three operations: constructor from the parameter instance (aka return), mapping parameters over a function (aka map) and turning a generic> into a generic (aka flatten). Unlike bind/flatMap, these operations are natural and really come from category theory.

So, you know what's funny about java 8 monads? They don't have flatten. flatten just doesn't work with traditional OOP. In C#, it is implemented using extension methods, in scala - using implicits. Poor old Java has none of that shit. Let's illustrate by writing a simple Java Optional mockery.

public final class OptionalEx {
T data;
// return
public OptionalEx(T value) { data = value; }
public OptionalEx map(final Function f) {
return new OptionalEx(data == null? null : f.apply(data));
}
public < WAT??? > OptionalEx< WAT??? > flatten() {
WAT???
}
}

See the problem? The flatten method should only work for optionals that are built upon optionals, ignoring all the other possibilities. How to deal with this? First, we could make it a raw method (generics aren't real in Java anyways) and use instanceof and exceptions to handle the situation. This is bad. Really bad. There's got to be a static way. Let's learn some lessons from Scala.

public OptionalEx flatten(final TypeEq> whatever) {
return (data == null)? new OptionalEx(null) : (OptionalEx)data;
}

This is how we would solve the problem with implicits. But Java has no implicits. Who cares, let's finish this. The only problem is to make sure that TypeEq are not to be created unless A = B.

public abstract class TypeEq {
private TypeEq(){}
static TypeEq get() {
return new TypeEq(){};
}
}
public final class Explicits {
public static TypeEq typeEq() {
return TypeEq.get();
}
}

Let's try to use this code.

OptionalEx> foo = new OptionalEx<>(new OptionalEx(2));
OptionalExbar = foo.flatten(>typeEq());

Now we have a statically correct (you cannot write code that does the wrong thing), but ugly solution. Turns out that with diamond typing you can omit the type completely:

OptionalEx bar = foo.flatten(typeEq());

And it still statically checks.
In our projects, we use this approach for many, many things. You are not even bound by type equality, you can even ask for TypeEq to check for subtyping relations. You can even get rid of that explicit cast by adding the right method to TypeEq (I leave it to you).

Good huntin'.

java 8