Skip to content

Instantly share code, notes, and snippets.

@rkrzewski
Last active February 10, 2017 12:26
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rkrzewski/83a3165d46bb28b5ec7bfd4e1458d3cd to your computer and use it in GitHub Desktop.
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
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