com.jnape.palatable.lambda.adt.Either Maven / Gradle / Ivy
Show all versions of lambda Show documentation
package com.jnape.palatable.lambda.adt;
import com.jnape.palatable.lambda.adt.coproduct.CoProduct2;
import com.jnape.palatable.lambda.functions.builtin.fn2.Peek;
import com.jnape.palatable.lambda.functions.builtin.fn2.Peek2;
import com.jnape.palatable.lambda.functions.specialized.checked.CheckedFn1;
import com.jnape.palatable.lambda.functions.specialized.checked.CheckedSupplier;
import com.jnape.palatable.lambda.functor.Applicative;
import com.jnape.palatable.lambda.functor.Bifunctor;
import com.jnape.palatable.lambda.monad.Monad;
import com.jnape.palatable.lambda.traversable.Traversable;
import java.util.Objects;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id;
import static com.jnape.palatable.lambda.functions.builtin.fn3.FoldLeft.foldLeft;
import static java.util.Arrays.asList;
/**
* The binary tagged union. General semantics tend to connote "success" values via the right value and "failure" values
* via the left values. Either
s are both Functor
s over their right value and
* Bifunctor
s over both values.
*
* @param The left parameter type
* @param The right parameter type
*/
public abstract class Either implements CoProduct2>, Monad>, Traversable>, Bifunctor {
private Either() {
}
/**
* Return the value wrapped by this Either
if it's a right value; otherwise, return defaultValue.
*
* @param defaultValue the value to return if this is a left
* @return the value wrapped by this Either if right; otherwise, defaultValue
*/
public final R or(R defaultValue) {
return recover(l -> defaultValue);
}
/**
* "Recover" from a left value by applying a recoveryFn to the wrapped value and returning it in the case of a left
* value; otherwise, return the wrapped right value.
*
* @param recoveryFn a function from L to R
* @return either the wrapped value (if right) or the result of the left value applied to recoveryFn
*/
public final R recover(Function super L, ? extends R> recoveryFn) {
return match(recoveryFn, id());
}
/**
* Inverse of recover. If this is a right value, apply the wrapped value to forfeitFn
and return it;
* otherwise, return the wrapped left value.
*
* @param forfeitFn a function from R to L
* @return either the wrapped value (if left) or the result of the right value applied to forfeitFn
*/
public final L forfeit(Function super R, ? extends L> forfeitFn) {
return match(id(), forfeitFn);
}
/**
* Return the wrapped value if this is a right; otherwise, map the wrapped left value to a T
and throw
* it.
*
* @param throwableFn a function from L to T
* @param the left parameter type (the throwable type)
* @return the wrapped value if this is a right
* @throws T the result of applying the wrapped left value to throwableFn, if this is a left
*/
public final R orThrow(Function super L, ? extends T> throwableFn) throws T {
return match((CheckedFn1) l -> {
throw throwableFn.apply(l);
}, id());
}
/**
* If this is a right value, apply pred
to it. If the result is true
, return the same
* value; otherwise, return the result of leftSupplier
wrapped as a left value.
*
* If this is a left value, return it.
*
* @param pred the predicate to apply to a right value
* @param leftSupplier the supplier of a left value if pred fails
* @return this if a left value or a right value that pred matches; otherwise, the result of leftSupplier wrapped in
* a left
*/
public final Either filter(Function super R, Boolean> pred, Supplier leftSupplier) {
return filter(pred, __ -> leftSupplier.get());
}
/**
* If this is a right value, apply pred
to it. If the result is true
, return the same
* value; otherwise, return the results of applying the right value to leftFn
wrapped as a left value.
*
* @param pred the predicate to apply to a right value
* @param leftFn the function from the right value to a left value if pred fails
* @return this is a left value or a right value that pred matches; otherwise, the result of leftFn applied to the
* right value, wrapped in a left
*/
public final Either filter(Function super R, Boolean> pred, Function super R, ? extends L> leftFn) {
return flatMap(r -> pred.apply(r) ? right(r) : left(leftFn.apply(r)));
}
/**
* If a right value, unwrap it and apply it to rightFn
, returning the resulting
* Either<L ,R>
. Otherwise, return the left value.
*
* Note that because this monadic form of flatMap
only supports mapping over a theoretical right value,
* the resulting Either
must be invariant on the same left value to flatten properly.
*
* @param rightFn the function to apply to a right value
* @param the new right parameter type
* @return the Either resulting from applying rightFn to this right value, or this left value if left
*/
@Override
public Either flatMap(Function super R, ? extends Monad>> rightFn) {
return flatMap(Either::left, rightFn.andThen(Applicative::coerce));
}
/**
* If a right value, apply rightFn
to the unwrapped right value and return the resulting
* Either
; otherwise, apply the unwrapped left value to leftFn and return the resulting
* Either
.
*
* @param leftFn the function to apply if a left value
* @param rightFn the function to apply if a right value
* @param the new left parameter type
* @param the new right parameter type
* @return the result of either rightFn or leftFn, depending on whether this is a right or a left
*/
public final Either flatMap(Function super L, ? extends Either> leftFn,
Function super R, ? extends Either> rightFn) {
return match(leftFn, rightFn);
}
@Override
public final Either invert() {
return flatMap(Either::right, Either::left);
}
/**
* Given two binary operators over L and R, merge multiple Either<L, R>
s into a single
* Either<L, R>
. Note that merge
biases towards left values; that is, if any left
* value exists, the result will be a left value, such that only unanimous right values result in an ultimate right
* value.
*
* @param leftFn the binary operator for L
* @param rightFn the binary operator for R
* @param others the other Eithers to merge into this one
* @return the merged Either
*/
@SafeVarargs
public final Either merge(BiFunction super L, ? super L, ? extends L> leftFn,
BiFunction super R, ? super R, ? extends R> rightFn,
Either... others) {
return foldLeft((x, y) -> x.match(l1 -> y.match(l2 -> left(leftFn.apply(l1, l2)), r -> left(l1)),
r1 -> y.match(Either::left, r2 -> right(rightFn.apply(r1, r2)))),
this,
asList(others));
}
/**
* Perform side-effects against a wrapped right value, returning back the Either
unaltered.
*
* @param rightConsumer the effecting consumer
* @return the Either, unaltered
*/
public Either peek(Consumer rightConsumer) {
return Peek.peek(rightConsumer, this);
}
/**
* Perform side-effects against a wrapped right or left value, returning back the Either
unaltered.
*
* @param leftConsumer the effecting consumer for left values
* @param rightConsumer the effecting consumer for right values
* @return the Either, unaltered
*/
public Either peek(Consumer leftConsumer, Consumer rightConsumer) {
return Peek2.peek2(leftConsumer, rightConsumer, this);
}
/**
* Given two mapping functions (one from an L
to a V
, one from an R
to a
* V
), unwrap the value stored in this Either
, apply the appropriate mapping function,
* and return the result.
*
* @param leftFn the left value mapping function
* @param rightFn the right value mapping function
* @param the result type
* @return the result of applying the appropriate mapping function to the wrapped value
*/
@Override
public abstract V match(Function super L, ? extends V> leftFn, Function super R, ? extends V> rightFn);
@Override
public final Either fmap(Function super R, ? extends R2> fn) {
return Monad.super.fmap(fn).coerce();
}
@Override
@SuppressWarnings("unchecked")
public final Either biMapL(Function super L, ? extends L2> fn) {
return (Either) Bifunctor.super.biMapL(fn);
}
@Override
@SuppressWarnings("unchecked")
public final Either biMapR(Function super R, ? extends R2> fn) {
return (Either) Bifunctor.super.biMapR(fn);
}
@Override
public final Either biMap(Function super L, ? extends L2> leftFn,
Function super R, ? extends R2> rightFn) {
return match(l -> left(leftFn.apply(l)), r -> right(rightFn.apply(r)));
}
@Override
public Either pure(R2 r2) {
return right(r2);
}
@Override
public Either zip(Applicative, Either> appFn) {
return appFn.>>coerce().flatMap(this::biMapR);
}
@Override
public Either discardL(Applicative> appB) {
return Monad.super.discardL(appB).coerce();
}
@Override
public Either discardR(Applicative> appB) {
return Monad.super.discardR(appB).coerce();
}
@Override
public Applicative, App> traverse(
Function super R, ? extends Applicative> fn,
Function super Traversable>, ? extends Applicative extends Traversable>, App>> pure) {
return match(l -> pure.apply(left(l)).fmap(x -> (Either) x),
r -> fn.apply(r).fmap(Either::right));
}
/**
* In the left case, returns a {@link Maybe#nothing()}; otherwise, returns {@link Maybe#maybe} around the right
* value.
*
* @return Maybe the right value
*/
public Maybe toMaybe() {
return projectB();
}
/**
* Convert a {@link Maybe}<R> into an Either<L, R>
, supplying the left value from
* leftFn
in the case of {@link Maybe#nothing()}.
*
* @param maybe the maybe
* @param leftFn the supplier to use for left values
* @param the left parameter type
* @param the right parameter type
* @return a right value of the contained maybe value, or a left value of leftFn's result
*/
public static Either fromMaybe(Maybe maybe, Supplier leftFn) {
return maybe.>fmap(Either::right)
.orElseGet(() -> left(leftFn.get()));
}
/**
* Attempt to execute the {@link CheckedSupplier}, returning its result in a right value. If the supplier throws an
* exception, apply leftFn to it, wrap it in a left value and return it.
*
* @param supplier the supplier of the right value
* @param leftFn a function mapping E to L
* @param the most contravariant exception that the supplier might throw
* @param the left parameter type
* @param the right parameter type
* @return the supplier result as a right value, or leftFn's mapping result as a left value
*/
@SuppressWarnings("unchecked")
public static Either trying(CheckedSupplier supplier,
Function super E, ? extends L> leftFn) {
try {
return right(supplier.get());
} catch (Exception e) {
return left(leftFn.apply((E) e));
}
}
/**
* Attempt to execute the {@link CheckedSupplier}, returning its result in a right value. If the supplier throws an
* exception, wrap it in a left value and return it.
*
* @param supplier the supplier of the right value
* @param the left parameter type (the most contravariant exception that supplier might throw)
* @param the right parameter type
* @return the supplier result as a right value, or a left value of the thrown exception
*/
public static Either trying(CheckedSupplier supplier) {
return trying(supplier, id());
}
/**
* Static factory method for creating a left value.
*
* @param l the wrapped value
* @param the left parameter type
* @param the right parameter type
* @return a left value of l
*/
public static Either left(L l) {
return new Left<>(l);
}
/**
* Static factory method for creating a right value.
*
* @param r the wrapped value
* @param the left parameter type
* @param the right parameter type
* @return a right value of r
*/
public static Either right(R r) {
return new Right<>(r);
}
private static final class Left extends Either {
private final L l;
private Left(L l) {
this.l = l;
}
@Override
public V match(Function super L, ? extends V> leftFn, Function super R, ? extends V> rightFn) {
return leftFn.apply(l);
}
@Override
public boolean equals(Object other) {
return other instanceof Left && Objects.equals(l, ((Left) other).l);
}
@Override
public int hashCode() {
return Objects.hash(l);
}
@Override
public String toString() {
return "Left{" +
"l=" + l +
'}';
}
}
private static final class Right extends Either {
private final R r;
private Right(R r) {
this.r = r;
}
@Override
public V match(Function super L, ? extends V> leftFn, Function super R, ? extends V> rightFn) {
return rightFn.apply(r);
}
@Override
public boolean equals(Object other) {
return other instanceof Right && Objects.equals(r, ((Right) other).r);
}
@Override
public int hashCode() {
return Objects.hash(r);
}
@Override
public String toString() {
return "Right{" +
"r=" + r +
'}';
}
}
}