Skip to content

Instantly share code, notes, and snippets.

@rkrzewski
Last active February 10, 2017 12:26
Show Gist options
  • 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 abstract Projection<L> leftProj();
protected abstract Projection<R> 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());
}
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> {
private final Projection<LL> leftProj;
private final Projection<RR> rightProj;
public Left(LL value) {
this.leftProj = new ValueProjection<LL>(value);
this.rightProj = new EmptyProjection<RR>();
}
@Override
public Projection<LL> leftProj() {
return leftProj;
}
@Override
public Projection<RR> rightProj() {
return rightProj;
}
}
private static class Right<LL, RR> extends Either<LL, RR> {
private final Projection<LL> leftProj;
private final Projection<RR> rightProj;
public Right(RR value) {
this.leftProj = new EmptyProjection<LL>();
this.rightProj = new ValueProjection<RR>(value);
}
@Override
public Projection<LL> leftProj() {
return leftProj;
}
@Override
public Projection<RR> rightProj() {
return rightProj;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment