package com.loopperfect.buckaroo; import com.google.common.base.Preconditions; import java.util.Objects; import java.util.Optional; import java.util.function.Function; public final class Either<L, R> { private enum LeftOrRight { LEFT, RIGHT } private final LeftOrRight which; // l or r will be null depending on the value of which private final L l; private final R r; private Either(final LeftOrRight which, final L l, final R r) { this.which = which; this.l = l; this.r = r; } public Optional<L> left() { return join(Optional::of, x -> Optional.empty()); } public Optional<R> right() { return join(x -> Optional.empty(), Optional::of); } public <T, U> Either<T, U> map(final Function<L, T> f, final Function<R, U> g) { Preconditions.checkNotNull(f); Preconditions.checkNotNull(g); return which == LeftOrRight.LEFT ? Either.left(f.apply(l)) : Either.right(g.apply(r)); } public <T, U> Either<T, U> flatMap(final Function<L, Either<T, U>> f, final Function<R, Either<T, U>> g) { Preconditions.checkNotNull(f); Preconditions.checkNotNull(g); return which == LeftOrRight.LEFT ? f.apply(l) : g.apply(r); } public <T> Either<T, R> leftProjection(final Function<L, T> f) { Preconditions.checkNotNull(f); return which == LeftOrRight.LEFT ? Either.left(f.apply(l)) : Either.right(r); } public <T> Either<L, T> rightProjection(final Function<R, T> f) { Preconditions.checkNotNull(f); return which == LeftOrRight.LEFT ? Either.left(l) : Either.right(f.apply(r)); } public <T> T join(final Function<L, T> f, final Function<R, T> g) { Preconditions.checkNotNull(f); Preconditions.checkNotNull(g); return which == LeftOrRight.LEFT ? f.apply(l) : g.apply(r); } public Optional<R> toOptional() { return which == LeftOrRight.LEFT ? Optional.empty() : Optional.of(r); } @Override public int hashCode() { return which == LeftOrRight.LEFT ? Objects.hash(l) : Objects.hash(r); } @Override public boolean equals(final Object obj) { if (this == obj) { return true; } if (obj == null || !(obj instanceof Either)) { return false; } final Either<L, R> other = (Either<L, R>) obj; return Objects.equals(which, other.which) && (which == LeftOrRight.LEFT ? Objects.equals(l, other.l) : Objects.equals(r, other.r)); } @Override public String toString() { return which == LeftOrRight.LEFT ? "[Left " + l + "]" : "[Right " + r + "]"; } public static <L, R> Either<L, R> left(final L x) { Preconditions.checkNotNull(x); return new Either<>(LeftOrRight.LEFT, x, null); } public static <L, R> Either<L, R> right(final R x) { Preconditions.checkNotNull(x); return new Either<>(LeftOrRight.RIGHT, null, x); } public static <L extends Throwable, R> R orThrow(final Either<L, R> either) throws L { Preconditions.checkNotNull(either); return either.right().orElseThrow(() -> either.left().get()); } public static <T> T join(final Either<? extends T, ? extends T> either) { Preconditions.checkNotNull(either); return either.which == LeftOrRight.LEFT ? either.l : either.r; } public static <L, R, T> T join(final Either<L, R> either, final Function<L, ? extends T> f, final Function<R, ? extends T> g) { Preconditions.checkNotNull(either); Preconditions.checkNotNull(f); Preconditions.checkNotNull(g); return either.which == LeftOrRight.LEFT ? f.apply(either.l) : g.apply(either.r); } }