Last active
February 10, 2017 12:26
-
-
Save rkrzewski/83a3165d46bb28b5ec7bfd4e1458d3cd to your computer and use it in GitHub Desktop.
Typesafe implementation of disjunction type for Java 8, inspired by scala.utils.Either
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
package pl.caltha.commons; | |
import java.util.Collections; | |
import java.util.Iterator; | |
import java.util.LinkedList; | |
import java.util.List; | |
import java.util.NoSuchElementException; | |
import java.util.Optional; | |
import java.util.function.BiFunction; | |
import java.util.function.BinaryOperator; | |
import java.util.function.Consumer; | |
import java.util.function.Function; | |
import java.util.stream.Stream; | |
public abstract class Either<L, R> implements Iterable<R> { | |
protected final Projection<L> leftProj; | |
protected final Projection<R> rightProj; | |
protected Either(Projection<L> leftProj, Projection<R> rightProj) { | |
this.leftProj = leftProj; | |
this.rightProj = rightProj; | |
} | |
public boolean isLeft() { | |
return leftProj.isPresent(); | |
} | |
public boolean isRight() { | |
return rightProj.isPresent(); | |
} | |
public Optional<R> optional() { | |
return rightProj.optioanal(); | |
} | |
public Stream<R> stream() { | |
return rightProj.stream(); | |
} | |
public Iterator<R> iterator() { | |
return rightProj.iterator(); | |
} | |
public <U> Either<L, U> map(Function<R, U> f) { | |
if (isLeft()) { | |
return left(leftProj.get()); | |
} else { | |
return right(f.apply(rightProj.get())); | |
} | |
} | |
public <LL, RR> Either<LL, RR> bimap(Function<R, RR> fr, Function<L, LL> fl) { | |
if (isLeft()) { | |
return left(fl.apply(leftProj.get())); | |
} else { | |
return right(fr.apply(rightProj.get())); | |
} | |
} | |
public <U> Either<L, U> flatMap(Function<R, Either<L, U>> f) { | |
if (isLeft()) { | |
return left(leftProj.get()); | |
} else { | |
return f.apply(rightProj.get()); | |
} | |
} | |
public void accept(Consumer<L> ifLeft, Consumer<R> ifRight) { | |
if (isLeft()) { | |
ifLeft.accept(leftProj.get()); | |
} else { | |
ifRight.accept(rightProj.get()); | |
} | |
} | |
public Either<R, L> swap() { | |
if (isLeft()) { | |
return right(leftProj.get()); | |
} else { | |
return left(rightProj.get()); | |
} | |
} | |
public static <L, R> Either<L, R> left(L value) { | |
return new Left<L, R>(value); | |
} | |
public static <L, R> Either<L, R> right(R value) { | |
return new Right<L, R>(value); | |
} | |
public static <L, R> Either<L, Stream<R>> sequence(Stream<Either<L, R>> eithers) { | |
Either<L, List<R>> zero = right(new LinkedList<R>()); | |
BiFunction<Either<L, List<R>>, Either<L, R>, Either<L, List<R>>> accumulator = (e1, e2) -> { | |
if (e1.isLeft()) { | |
return e1; | |
} | |
if (e2.isLeft()) { | |
return left(e2.leftProj.get()); | |
} | |
List<R> combined = e1.rightProj.get(); | |
combined.add(e2.rightProj.get()); | |
return right(combined); | |
}; | |
BinaryOperator<Either<L, List<R>>> combiner = (e1, e2) -> { | |
if (e1.isLeft()) { | |
return e1; | |
} | |
if (e2.isLeft()) { | |
return e2; | |
} | |
List<R> combined = e1.rightProj.get(); | |
combined.addAll(e2.rightProj.get()); | |
return right(combined); | |
}; | |
return eithers.reduce(zero, accumulator, combiner).map(l -> l.stream()); | |
} | |
public static <L, R> Either<Stream<L>, Stream<R>> bisequence(Stream<Either<L, R>> eithers) { | |
Either<List<L>, List<R>> zero = right(new LinkedList<R>()); | |
BiFunction<Either<List<L>, List<R>>, Either<L, R>, Either<List<L>, List<R>>> accumulator = ( | |
e1, e2) -> { | |
if (e2.isLeft()) { | |
if (e1.isLeft()) { | |
List<L> combined = e1.leftProj.get(); | |
combined.add(e2.leftProj.get()); | |
return left(combined); | |
} else { | |
List<L> initial = new LinkedList<>(); | |
initial.add(e2.leftProj.get()); | |
return left(initial); | |
} | |
} else { | |
if (e1.isLeft()) { | |
return left(e1.leftProj.get()); | |
} else { | |
List<R> combined = e1.rightProj.get(); | |
combined.add(e2.rightProj.get()); | |
return right(combined); | |
} | |
} | |
}; | |
BinaryOperator<Either<List<L>, List<R>>> combiner = (e1, e2) -> { | |
if (e1.isLeft()) { | |
if (e2.isLeft()) { | |
List<L> combined = e1.leftProj.get(); | |
combined.addAll(e2.leftProj.get()); | |
return left(combined); | |
} else { | |
return e1; | |
} | |
} else { | |
if (e2.isLeft()) { | |
return e2; | |
} else { | |
List<R> combined = e1.rightProj.get(); | |
combined.addAll(e2.rightProj.get()); | |
return right(combined); | |
} | |
} | |
}; | |
return eithers.reduce(zero, accumulator, combiner).bimap(l -> l.stream(), r -> r.stream()); | |
} | |
protected interface Projection<T> extends Iterable<T> { | |
boolean isPresent(); | |
T get(); | |
Optional<T> optioanal(); | |
Stream<T> stream(); | |
Iterator<T> iterator(); | |
} | |
private static class EmptyProjection<T> implements Projection<T> { | |
@Override | |
public boolean isPresent() { | |
return false; | |
} | |
@Override | |
public T get() { | |
throw new NoSuchElementException("empty projection"); | |
} | |
@Override | |
public Optional<T> optioanal() { | |
return Optional.empty(); | |
} | |
@Override | |
public Stream<T> stream() { | |
return Stream.empty(); | |
} | |
@Override | |
public Iterator<T> iterator() { | |
return Collections.emptyIterator(); | |
} | |
} | |
private static class ValueProjection<T> implements Projection<T> { | |
private final T value; | |
public ValueProjection(T value) { | |
this.value = value; | |
} | |
@Override | |
public boolean isPresent() { | |
return true; | |
} | |
@Override | |
public T get() { | |
return value; | |
} | |
@Override | |
public Optional<T> optioanal() { | |
return Optional.of(value); | |
} | |
@Override | |
public Stream<T> stream() { | |
return Stream.of(value); | |
} | |
@Override | |
public Iterator<T> iterator() { | |
return Collections.singleton(value).iterator(); | |
} | |
} | |
private static class Left<LL, RR> extends Either<LL, RR> { | |
public Left(LL value) { | |
super(new ValueProjection<LL>(value), new EmptyProjection<RR>()); | |
} | |
} | |
private static class Right<LL, RR> extends Either<LL, RR> { | |
public Right(RR value) { | |
super(new EmptyProjection<LL>(), new ValueProjection<RR>(value)); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment