Skip to content

Instantly share code, notes, and snippets.

@mergeconflict
Last active December 16, 2015 04:28
Show Gist options
  • Save mergeconflict/5377120 to your computer and use it in GitHub Desktop.
Save mergeconflict/5377120 to your computer and use it in GitHub Desktop.
Java 8 fantasy land?
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Supplier;
/*
* Java 8 fantasy land spec:
*
* A monad is a parameterized data type `M` with the following constraints
* - An instance `M<A>` must have a method: `public <B> M<B> bind(Function<A, M<B>> f)`
* - `M` must have a static method: `public static <A> M<A> point(A value)`
*
* The names of these two methods don't matter, just the types. An example follows:
*/
interface Option<A> {
<B> B fold(Function<A, B> some, Supplier<B> none);
static final class Some<A> implements Option<A> {
public final A value;
public Some(A value) {
this.value = value;
}
public <B> B fold(Function<A, B> some, Supplier<B> none) {
return some.apply(value);
}
public String toString() {
return String.format("Some(%s)", value);
}
}
static final class None<A> implements Option<A> {
public <B> B fold(Function<A, B> some, Supplier<B> none) {
return none.get();
}
public String toString() {
return "None";
}
}
default <B> Option<B> bind(Function<A, Option<B>> f) {
return fold(f, None::new);
}
static <A> Option<A> point(A value) {
return new Some<>(value);
}
}
/*
* The purpose of having the two methods defined above is to write code which
* abstracts over monads.
*/
class DoYouEven {
// note that `ma` is not the actual monad instance, it's (hopefully) the `bind`
// method closing over that instance, instantiated at type `B`.
public static <A, B, MA, MB> MB liftM1(
Function<Function<A, MB>, MB> ma,
Function<B, MB> point,
Function<A, B> f)
{
return ma.apply(a -> point.apply(f.apply(a)));
}
// same deal here. `ma` and `mb` provide evidence that if you pass them a function,
// they'll give you an MC back. sour about polymorphic expressions being crammed into
// monomorphic values.
public static <A, B, C, MA, MB, MC> MC liftM2(
Function<Function<A, MC>, MC> ma,
Function<Function<B, MC>, MC> mb,
Function<C, MC> point,
BiFunction<A, B, C> f)
{
return ma.apply(a -> liftM1(mb, point, b -> f.apply(a, b)));
}
}
public class FantasyLand {
// bind and point are easy to use directly
public static Option<Integer> directOptionAdd(Option<Integer> ma, Option<Integer> mb) {
return ma.bind(a -> mb.bind(b -> Option.point(a + b)));
}
// thanks to poly expressions this isn't even too awful, but notice the redundancy
// and the type inference fail.
public static Option<Integer> liftOptionAdd(Option<Integer> ma, Option<Integer> mb) {
return DoYouEven.liftM2(ma::bind, mb::bind, Option::point, (Integer a, Integer b) -> a + b);
}
public static void main(String[] args) {
Option<Integer>
one = Option.point(1),
two = Option.point(2),
none = new Option.None<>();
System.out.println(directOptionAdd(one, two));
System.out.println(directOptionAdd(one, none));
System.out.println(directOptionAdd(none, two));
System.out.println(liftOptionAdd(one, two));
System.out.println(liftOptionAdd(one, none));
System.out.println(liftOptionAdd(none, two));
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment