com.ocadotechnology.utils.Either Maven / Gradle / Ivy
/*
* Copyright © 2017-2023 Ocado (Ocava)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.ocadotechnology.utils;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.function.ToDoubleFunction;
import java.util.function.ToIntFunction;
import com.google.common.base.MoreObjects;
import com.google.common.base.Preconditions;
/** This class loosely follows the implementation of Either used within the Scala Standard Library:
* https://www.scala-lang.org/api/2.12.0/scala/util/Either.html
* Note: Either has the same memory overhead as Optional.
*
* The canonical use case is tracking errors: Either<Error, Result>
* which can then be composed (using {@link #map} or {@link #bimap} or {@link #flatMap})
* to process the result and/or error separately.
*/
public abstract class Either {
public static Either createLeft(C value) {
return new Left<>(value);
}
public static Either createRight(D value) {
return new Right<>(value);
}
/** Constructor from Optional: The Optional value goes rightward. */
public static Either createRight(Optional value, C ifNotPresent) {
return value.isPresent() ? Either.createRight(value.get()) : Either.createLeft(ifNotPresent);
}
/** Constructor from Optional: The Optional value goes rightward. */
public static Either createRight(Optional value, Supplier ifNotPresent) {
return value.isPresent() ? Either.createRight(value.get()) : Either.createLeft(ifNotPresent.get());
}
/**
* Returns true if this is a Left, false otherwise.
*/
public abstract boolean isLeft();
/**
* Returns true if this is a Right, false otherwise.
*/
public abstract boolean isRight();
/**
* Projects this either as a left.
* When using a left projection the manipulation functions will apply to the left side value.
*/
public LeftProjection left() {
return new LeftProjection<>(this);
}
/**
* Projects this either as a right.
* When using a right projection, the manipulation functions will apply to the right side value.
* The functions on a RightProjection act in the exact same way as functions on the Either class itself due to
* the right-side bias of the either data-structure.
*/
public RightProjection right() {
return new RightProjection<>(this);
}
/**
* @return The left value of the either when present.
* @throws IllegalStateException when the either is not left.
*/
public abstract A leftValue();
/**
* @return The right value of the either when present.
* @throws IllegalStateException when the either is not right.
*/
public abstract B rightValue();
/**
* Switches the types making the left type the right and vice-versa.
*/
public abstract Either swap();
/** Type coercion: cast a left to have a different (irrelevant) right type.
* It is an error to cast a right value, so this is safe.
*/
public abstract Either leftOnly();
/** Type coercion: cast a right to have a different (irrelevant) left type.
* It is an error to cast a left value, so this is safe.
*/
public abstract Either rightOnly();
/** Functor
* Map the right side value of type B to the new value with type C using the functor.
* The right side is mapped on an Either as the data-structure has a right side bias.
* If the either has a left value, this will do nothing other than change the type of the right side.
* This is equivalent to right().map(). To get the same function for the left value, use left().map()
* @param functor The function for converting value of type B to C
* @param The new right side value type
* @return A new either with the new type signature {@code Either}
*/
public Either map(Function functor) {
return right().map(functor);
}
/** Bifunctor
* Map both the left and right values to a new either with new types.
* This function will use whichever map function is appropriate for this either to map to a new either with
* new value types.
* @param leftMap The function used on a left valued either
* @param rightMap The function used on a right valued either
* @param The left type of the new either
* @param The right type of the new either
* @return An either of type {@code Either} constructed from the functions given
*/
public abstract Either bimap(Function leftMap, Function rightMap);
// Would be nice if these were all called 'reduce', but the (Java 8) compiler can't disambiguate well enough.
/**
* Reduce an either into a single value of type C
* @param leftReduce The function used to reduce a left either to C
* @param rightReduce The function used to reduce a right either to C
* @param The type of the new value
* @return The result of the reduction
*/
public abstract C reduce(Function leftReduce, Function rightReduce);
/**
* Reduce an either into a double
* @param leftReduce The function used to reduce a left either to double
* @param rightReduce The function used to reduce a right either to double
* @return The result of the reduction
*/
public abstract double reduceToDouble(ToDoubleFunction leftReduce, ToDoubleFunction rightReduce);
/**
* Reduce an either into a int
* @param leftReduce The function used to reduce a left either to int
* @param rightReduce The function used to reduce a right either to int
* @return The result of the reduction
*/
public abstract int reduceToInt(ToIntFunction leftReduce, ToIntFunction rightReduce);
/**
* Reduce an either into a boolean
* @param leftReduce The function used to reduce a left either to boolean
* @param rightReduce The function used to reduce a right either to boolean
* @return The result of the reduction
*/
public abstract boolean reduceToBoolean(Predicate leftReduce, Predicate rightReduce);
/**
* If a left, reduce to the right typed value using the reducer function. If right, get the right value.
* @param reduceLeft left value reducer
* @return right typed value
*/
public abstract B reduceLeft(Function reduceLeft);
/**
* If a right, reduce to a left typed value using the reducer function. If left, get the left value.
* @param reduceRight right value reducer
* @return right typed value
*/
public abstract A reduceRight(Function reduceRight);
/** Consumer: Calls either leftConsumer or rightConsumer (but not both). */
public abstract void accept(Consumer leftConsumer, Consumer rightConsumer);
/** Monad
* Returns an either which is returned as the result of applying the mapper function
* to the right hand value on this either.
* If the either has a left value, it will not do anything.
* This function operates identically to the way the flatMap methods on the
* Java standard library's Optional and Stream datatypes.
* @param mapper The function run on the right hand value to create a new Either
* @param The new right hand type
*/
public abstract Either flatMap(Function> mapper);
/**
* Returns an Optional of the right value if the either is right.
* Optional.empty() if the either is left.
* This is by convention on the Either datatype which has a right-side bias.
* A major use-case of the Either type is keeping successful value on the righthand side and
* failure cases on the left.
* Because of this, the righthand, successful case, gets used within the optional conversion.
*/
public abstract Optional toOptional();
/**
* Tests the given predicates against the value of the either.
* @param leftCheck The predicate that must be satisfied if the either has a left value
* @param rightCheck The predicate that must be satisfied if the either has a right value
* @return The result of the applied predicate.
*/
public abstract boolean testEither(Predicate leftCheck, Predicate rightCheck);
/** If 'this' is right-leaning, test the predicate against the right value.
* If true, return the same value as 'this', otherwise return a left(onNotAccepted).
* If 'this' is left-leaning, return the original value.
*/
public abstract Either filter(Predicate acceptor, A onNotAccepted);
/** Same as {@link #filter(Predicate, Object)} where the Supplier is only called if necessary. */
public abstract Either filter(Predicate acceptor, Supplier onNotAccepted);
private static final class Left extends Either {
private final A leftValue;
private Left(A a) {
Preconditions.checkArgument(a != null);
this.leftValue = a;
}
@Override
public boolean isLeft() {
return true;
}
@Override
public boolean isRight() {
return false;
}
@Override
public A leftValue() {
return leftValue;
}
@Override
public B rightValue() {
Preconditions.checkState(false);
return null;
}
@Override
public Either swap() {
return new Right<>(leftValue);
}
public Either leftOnly() {
return (Either)this;
}
public Either rightOnly() {
Preconditions.checkState(false);
return null;
}
@Override
public Optional toOptional() {
return Optional.empty();
}
@Override
public boolean testEither(Predicate leftCheck, Predicate rightCheck) {
return leftCheck.test(leftValue);
}
@Override
public void accept(Consumer first, Consumer second) {
first.accept(leftValue);
}
@Override
public Either bimap(Function leftMap, Function rightMap) {
return Either.createLeft(leftMap.apply(leftValue));
}
@Override
public Either flatMap(Function> g) {
return (Either)this;
}
@Override
public C reduce(Function leftReduce, Function rightReduce) {
return leftReduce.apply(leftValue);
}
@Override
public double reduceToDouble(ToDoubleFunction leftReduce, ToDoubleFunction rightReduce) {
return leftReduce.applyAsDouble(leftValue);
}
@Override
public int reduceToInt(ToIntFunction leftReduce, ToIntFunction rightReduce) {
return leftReduce.applyAsInt(leftValue);
}
@Override
public boolean reduceToBoolean(Predicate leftReduce, Predicate rightReduce) {
return leftReduce.test(leftValue);
}
@Override
public B reduceLeft(Function reduceLeft) {
return reduceLeft.apply(leftValue);
}
@Override
public A reduceRight(Function reduceRight) {
return leftValue;
}
@Override
public Either filter(Predicate acceptor, A onNotAccepted) {
return this;
}
@Override
public Either filter(Predicate acceptor, Supplier onNotAccepted) {
return this;
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this).add("left", leftValue).toString();
}
@Override
public boolean equals(Object other) {
return other instanceof Left && leftValue.equals(((Left, ?>)other).leftValue);
}
@Override
public int hashCode() {
return leftValue.hashCode();
}
}
private static final class Right extends Either {
private final B rightValue;
private Right(B b) {
Preconditions.checkArgument(b != null);
this.rightValue = b;
}
@Override
public boolean isLeft() {
return false;
}
@Override
public boolean isRight() {
return true;
}
@Override
public A leftValue() {
Preconditions.checkState(false);
return null;
}
@Override
public B rightValue() {
return rightValue;
}
@Override
public Either swap() {
return new Left<>(rightValue);
}
public Either leftOnly() {
Preconditions.checkState(false);
return null;
}
public Either rightOnly() {
return (Either)this;
}
@Override
public Optional toOptional() {
return Optional.of(rightValue);
}
@Override
public boolean testEither(Predicate leftCheck, Predicate rightCheck) {
return rightCheck.test(rightValue);
}
@Override
public void accept(Consumer first, Consumer second) {
second.accept(rightValue);
}
@Override
public Either bimap(Function leftMap, Function rightMap) {
return Either.createRight(rightMap.apply(rightValue));
}
@Override
public Either flatMap(Function> g) {
return g.apply(rightValue);
}
@Override
public C reduce(Function leftReduce, Function rightReduce) {
return rightReduce.apply(rightValue);
}
@Override
public double reduceToDouble(ToDoubleFunction leftReduce, ToDoubleFunction rightReduce) {
return rightReduce.applyAsDouble(rightValue);
}
@Override
public int reduceToInt(ToIntFunction leftReduce, ToIntFunction rightReduce) {
return rightReduce.applyAsInt(rightValue);
}
@Override
public boolean reduceToBoolean(Predicate leftReduce, Predicate rightReduce) {
return rightReduce.test(rightValue);
}
@Override
public B reduceLeft(Function reduceLeft) {
return rightValue;
}
@Override
public A reduceRight(Function reduceRight) {
return reduceRight.apply(rightValue);
}
@Override
public Either filter(Predicate acceptor, A onNotAccepted) {
return acceptor.test(rightValue) ? this : Either.createLeft(onNotAccepted);
}
@Override
public Either filter(Predicate acceptor, Supplier onNotAccepted) {
return acceptor.test(rightValue) ? this : Either.createLeft(onNotAccepted.get());
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this).add("right", rightValue).toString();
}
@Override
public boolean equals(Object other) {
return other instanceof Right && rightValue.equals(((Right, ?>)other).rightValue);
}
@Override
public int hashCode() {
return rightValue.hashCode();
}
}
public static final class LeftProjection {
private final Either either;
private LeftProjection(Either either) {
this.either = either;
}
/**
* If the parent either is left it will return an Optional of the left value. Empty otherwise
*/
public Optional toOptional() {
if (either.isLeft()) {
return Optional.of(either.leftValue());
} else {
return Optional.empty();
}
}
/** Functor
* Map the left side value of type A to the new value with type C using the functor.
* @param functor The function for converting left value of type A to C
* @param The new left side type
* @return A new either with the new type signature {@code Either}
*/
public Either map(Function functor) {
if (either.isLeft()) {
return new Left(functor.apply(either.leftValue()));
} else {
return (Either) either;
}
}
/** Monad
* Returns the either which is returned as the result of applying the mapper function to
* the left hand value on this either.
* If the either has a right value, it will not do anything.
* This function operates identically to the way the flatMap methods on
* the Java standard library's Optional and Stream datatypes.
* @param mapper The function run on the left hand value to create a new Either
* @param The new left hand type
*/
public Either flatMap(Function> mapper) {
if (either.isLeft()) {
return mapper.apply(either.leftValue());
} else {
return (Either) either;
}
}
/**
* Tests the given predicate against the left value
*/
public boolean test(Predicate acceptor) {
return either.testEither(acceptor, unused -> false);
}
/**
* Runs the consumer on the left value if it is present.
*/
public void ifPresent(Consumer f) {
either.accept(f, unused -> {});
}
/**
* If this is left-leaning, test the predicate against the left value, Otherwise create a right with
* the provided value.
*/
public Either filter(Predicate acceptor, B onNotAccepted) {
if (either.isLeft()) {
return acceptor.test(either.leftValue())
? either
: new Right(onNotAccepted);
} else {
return either;
}
}
/**
* If this is left-leaning, test the predicate against the left value, Otherwise create a right with
* the value returned from the supplier.
*/
public Either filter(Predicate acceptor, Supplier onNotAccepted) {
if (either.isLeft()) {
return acceptor.test(either.leftValue())
? either
: new Right(onNotAccepted.get());
} else {
return either;
}
}
}
public static final class RightProjection {
private final Either either;
private RightProjection(Either either) {
this.either = either;
}
/**
* If the parent either is right it will return an Optional of the right value. Empty otherwise
*/
public Optional toOptional() {
return either.toOptional();
}
/** Functor
* Map the right side value of type A to the new value with type C using the functor.
* @param functor The function for converting right value of type B to C
* @param The new right side type
* @return A new either with the new type signature {@code Either}
*/
public Either map(Function functor) {
if (either.isRight()) {
return new Right(functor.apply(either.rightValue()));
} else {
return (Either) either;
}
}
/** Monad
* Returns the either which is returned as the result of applying the mapper function to
* the right hand value on this either.
* If the either has a left value, it will not do anything.
* This function operates identically to the way the flatMap methods on
* the Java standard library's Optional and Stream datatypes.
* @param mapper The function run on the right hand value to create a new Either
* @param The new right hand type
*/
public Either flatMap(Function> mapper) {
if (either.isRight()) {
return mapper.apply(either.rightValue());
} else {
return (Either) either;
}
}
/**
* Tests the given predicate against the right value
*/
public boolean test(Predicate acceptor) {
return either.testEither(unused -> false, acceptor);
}
/**
* Runs the consumer on the right value if it is present.
*/
public void ifPresent(Consumer f) {
either.accept(unused -> {}, f);
}
/**
* If this is right-leaning, test the predicate against the right value, Otherwise create a left with
* the provided value.
*/
public Either filter(Predicate acceptor, A onNotAccepted) {
return either.filter(acceptor, onNotAccepted);
}
/**
* If this is right-leaning, test the predicate against the right value, Otherwise create a left with
* the value returned from the supplier.
*/
public Either filter(Predicate acceptor, Supplier onNotAccepted) {
return either.filter(acceptor, onNotAccepted);
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy