All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.jnape.palatable.lambda.adt.Either Maven / Gradle / Ivy

There is a newer version: 5.5.0
Show newest version
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. Eithers are both Functors over their right value and
 * Bifunctors 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 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 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 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 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 pred, Function 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>> 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> leftFn, Function> 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 leftFn, BiFunction 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 leftFn, Function rightFn); @Override public final Either fmap(Function fn) { return Monad.super.fmap(fn).coerce(); } @Override @SuppressWarnings("unchecked") public final Either biMapL(Function fn) { return (Either) Bifunctor.super.biMapL(fn); } @Override @SuppressWarnings("unchecked") public final Either biMapR(Function fn) { return (Either) Bifunctor.super.biMapR(fn); } @Override public final Either biMap(Function leftFn, Function 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> fn, Function>, ? extends Applicative>, 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 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 leftFn, Function 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 leftFn, Function 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 + '}'; } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy