Vorschlag für Either ohne Laziness und mit Optional-inspirierter Benamsung:
import java.util.NoSuchElementException;
import java.util.Optional;
import java.util.function.Function;
import java.util.function.Supplier;
public abstract class Either<A, B> {
Either() {
}
public static <A, B> Left<A, B> leftOf(A a) {
return new Left<>(a);
}
public static <A, B> Right<A, B> rightOf(B b) {
return new Right<>(b);
}
public abstract boolean isLeft();
public abstract boolean isRight();
public abstract A left() throws NoSuchElementException;
public abstract B right() throws NoSuchElementException;
public abstract A leftOrElse(A defaultValue);
public abstract B rightOrElse(B defaultValue);
public abstract A leftOrElseGet(Supplier<A> defaultSupplier);
public abstract B rightOrElseGet(Supplier<B> defaultSupplier);
public abstract Optional<A> leftOpt();
public abstract Optional<B> rightOpt();
public abstract <A1> Either<A1, B> mapLeft(Function<? super A, ? extends A1> fn);
public abstract <B1> Either<A, B1> mapRight(Function<? super B, ? extends B1> fn);
public abstract <A1, B1> Either<A1, B1> bimap(Function<? super A, ? extends A1> fnA, Function<? super B, ? extends B1> fnB);
public abstract <C> C either(Function<? super A, ? extends C> fnA, Function<? super B, ? extends C> fnB);
public abstract Either<B, A> swap();
}
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Function;
import java.util.function.Supplier;
public final class Left<A, B> extends Either<A, B> {
private final A a;
public Left(A a) {
Objects.requireNonNull(a);
this.a = a;
}
@Override
public boolean isLeft() {
return true;
}
@Override
public boolean isRight() {
return false;
}
@Override
public A left() {
return a;
}
@Override
public B right() throws NoSuchElementException {
throw new NoSuchElementException("right() call on Left");
}
@Override
public A leftOrElse(A defaultValue) {
return left();
}
@Override
public B rightOrElse(B defaultValue) {
return defaultValue;
}
@Override
public A leftOrElseGet(Supplier<A> defaultSupplier){
return left();
}
@Override
public B rightOrElseGet(Supplier<B> defaultSupplier) {
return defaultSupplier.get();
}
@Override
public Optional<A> leftOpt() {
return Optional.of(left());
}
@Override
public Optional<B> rightOpt() {
return Optional.empty();
}
@Override
public <A1> Either<A1, B> mapLeft(Function<? super A, ? extends A1> fn) {
return leftOf(fn.apply(left()));
}
@Override
public <B1> Either<A, B1> mapRight(Function<? super B, ? extends B1> fn) {
return leftOf(left());
}
@Override
public <A1, B1> Either<A1, B1> bimap(Function<? super A, ? extends A1> fnA, Function<? super B, ? extends B1> fnB) {
return leftOf(fnA.apply(left()));
}
@Override
public <C> C either(Function<? super A, ? extends C> fnA, Function<? super B, ? extends C> fnB) {
return fnA.apply(left());
}
@Override
public Either<B, A> swap() {
return rightOf(left());
}
@Override
public String toString() {
return String.format("Left(%s)", left());
}
@Override
public int hashCode() {
return 29 * left().hashCode();
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj instanceof Left) {
Left<?,?> that = (Left) obj;
return this.left().equals(that.left());
}
return false;
}
}
Right
natürlich analog.
Ich finde die Implementierung so recht vernünftig und vor allem ohne große Überraschungen. leftOpt
und rightOpt
ist ein Kompromiss, weil ich bei Listen dann auch gerne headOpt
und tailOpt
hätte.
Ob wir aus Performancegründen bei mapLeft
und mapRight
casten statt neu zu wrappen, können wir uns noch überlegen.