Last active
December 16, 2015 04:28
-
-
Save mergeconflict/5377120 to your computer and use it in GitHub Desktop.
Java 8 fantasy land?
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.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