Last active
August 11, 2024 20:56
-
-
Save danieldietrich/a21f4de7a42052bf30ad to your computer and use it in GitHub Desktop.
The most sophisticated generic Java type I ever wrote
This file contains hidden or 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 hidden or 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 hidden or 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 hidden or 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 hidden or 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)); | |
}} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Summarizing the solution, the interface method
flatMap
is overridden with
We specify the return type
MONAD
of the functionf
, which is passed toflatMap
. BecauseMONAD
itself is parameterized with a generic parameter, we need to specify the container typeM
(e.g.Option<?>
) and the component typeA
ofM
(e.g.T
in the case ofOption
). By contractMONAD
is of generic typeM
(informallyM<B>
), which is the return type offlatMap
.