Skip to content

Instantly share code, notes, and snippets.

@danieldietrich
Last active August 29, 2015 14:08
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save danieldietrich/a21f4de7a42052bf30ad to your computer and use it in GitHub Desktop.
Save danieldietrich/a21f4de7a42052bf30ad to your computer and use it in GitHub Desktop.
The most sophisticated generic Java type I ever wrote
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
/**
* The Monad interface.
*
* @param <M> The type of the monad, e.g. {@code class Foo<A> extends Monad<Foo<?>, A, Foo<A>>}.
* @param <A> Component type of {@code M}.
*/
public interface Monad<M extends Monad<?, ?>, A> {
// the constructor:
// <B> Monad<?, B> unit(B b);
// map(f) ~ unit(f.apply(a))
<B> Monad<?, B> map(Function<? super A, ? extends B> f);
// flatMap(f) ~ f.apply(a)
<B, MONAD extends Monad<M, B>> M flatMap(Function<? super A, MONAD> f);
}
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
public final class None<T> implements Option<T> {
private static final None<?> INSTANCE = new None<>();
private None() {
}
@Override
public <U> Option<U> map(Function<? super T, ? extends U> mapper) {
return None.instance();
}
@Override
public <U, OPTION extends Monad<Option<?>, U>> Option<U> flatMap(Function<? super T, OPTION> mapper) {
return None.instance();
}
public static <T> None<T> instance() {
@SuppressWarnings("unchecked")
final None<T> none = (None<T>) INSTANCE;
return none;
}
}
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
public interface Option<T> extends Monad<Option<?>, T> {
@Override
<U> Option<U> map(Function<? super T, ? extends U> mapper);
@Override
<U, OPTION extends Monad<Option<?>, U>> Option<U> flatMap(Function<? super T, OPTION> mapper);
static <T> Option<T> empty() {
return None.instance();
}
}
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
public final class Some<T> implements Option<T> {
private final T value;
public Some(T value) {
this.value = value;
}
@Override
public <U> Option<U> map(Function<? super T, ? extends U> mapper) {
return new Some<>(mapper.apply(value));
}
@SuppressWarnings("unchecked")
@Override
public <U, OPTION extends Monad<Option<?>, U>> Option<U> flatMap(Function<? super T, OPTION> mapper) {
return (Option<U>) mapper.apply(value);
}
}
class Tests {{
final Option<String> ok1 = Option.empty();
final Option<String> ok2 = new Some<>("");
final Option<String> ok3 = None.instance();
final Option<Integer> ok4 = ok1.flatMap(s -> new Some<Integer>(1));
final Function<String, Some<Integer>> f = s -> new Some<>(s.length());
final Option<? extends Number> ok5 = ok1.flatMap(f);
// failing, ok!
final Option<Number> fail1 = ok1.flatMap(f);
// failing, ok!
final Some<Integer> fail2 = ok1.flatMap(s -> new Some<Integer>(1));
}}
@danieldietrich
Copy link
Author

My intention was to define an interface for Monads. However, there was a problem with the flatMap method. The return type may be overwritten by subtypes in contrast to the result type of the function argument f.

More specifically the interface method flatMap

interface Monad<A> {
    <B> Monad<B> flatMap(Function<? super A, Monad<B>> f);
}

needed to be overridden with

interface Option<T> extends Monad<T> {
    <U> Option<U> flatMap(Function<? super T, Option<U>> f);
}

These snippets solve this problem using generics.

@danieldietrich
Copy link
Author

Summarizing the solution, the interface method flatMap

interface Monad<M extends Monad<?,?>, A> {
    <B, MONAD extends Monad<M, B>> M flatMap(Function<? super A, MONAD> f);
}

is overridden with

interface Option<T> extends Monad<Option<?>, T> {
    @Override
    <U, OPTION extends Monad<Option<?>, U>> Option<U> flatMap(Function<? super T, OPTION> f);
}

We specify the return type MONAD of the function f, which is passed to flatMap. Because MONAD itself is parameterized with a generic parameter, we need to specify the container type M(e.g. Option<?>) and the component type A of M (e.g. T in the case of Option). By contract MONAD is of generic type M (informally M<B>), which is the return type of flatMap.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment