Last active
August 29, 2015 14:08
-
-
Save danieldietrich/a21f4de7a42052bf30ad to your computer and use it in GitHub Desktop.
The most sophisticated generic Java type I ever wrote
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)); | |
}} |
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
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 argumentf
.More specifically the interface method
flatMap
needed to be overridden with
These snippets solve this problem using generics.