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.choice.Choice3;
import com.jnape.palatable.lambda.adt.coproduct.CoProduct2;
import com.jnape.palatable.lambda.functions.Fn0;
import com.jnape.palatable.lambda.functions.Fn1;
import com.jnape.palatable.lambda.functions.Fn2;
import com.jnape.palatable.lambda.functions.recursion.RecursiveResult;
import com.jnape.palatable.lambda.functions.specialized.Pure;
import com.jnape.palatable.lambda.functions.specialized.SideEffect;
import com.jnape.palatable.lambda.functor.Applicative;
import com.jnape.palatable.lambda.functor.Bifunctor;
import com.jnape.palatable.lambda.functor.Functor;
import com.jnape.palatable.lambda.functor.builtin.Lazy;
import com.jnape.palatable.lambda.io.IO;
import com.jnape.palatable.lambda.monad.Monad;
import com.jnape.palatable.lambda.monad.MonadError;
import com.jnape.palatable.lambda.monad.MonadRec;
import com.jnape.palatable.lambda.traversable.Traversable;

import java.util.Objects;

import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly;
import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id;
import static com.jnape.palatable.lambda.functions.builtin.fn3.FoldLeft.foldLeft;
import static com.jnape.palatable.lambda.functions.recursion.RecursiveResult.terminate;
import static com.jnape.palatable.lambda.functions.recursion.Trampoline.trampoline;
import static com.jnape.palatable.lambda.functor.builtin.Lazy.lazy;
import static com.jnape.palatable.lambda.io.IO.io;
import static java.util.Arrays.asList;

/**
 * The binary tagged union, implemented as a specialized {@link CoProduct2}. General semantics tend to connote "success"
 * values via the right value and "failure" values via the left values. {@link Either}s are both {@link Monad}s and
 * {@link Traversable}s over their right value and are {@link Bifunctor}s over both values.
 *
 * @param  The left parameter type
 * @param  The right parameter type
 */
public abstract class Either implements
        CoProduct2>,
        MonadError>,
        MonadRec>,
        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(Fn1 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(Fn1 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          the left parameter type (the throwable type)
     * @param throwableFn a function from L to T
     * @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(Fn1 throwableFn) throws T {
        return match(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 leftFn0 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(Fn1 pred, Fn0 leftFn0) { return filter(pred, __ -> leftFn0.apply()); } /** * 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(Fn1 pred, Fn1 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 @SuppressWarnings("RedundantTypeArguments") public Either flatMap(Fn1>> rightFn) { return match(Either::left, rightFn.fmap(Monad>::coerce)); } /** * {@inheritDoc} */ @Override public Either trampolineM(Fn1, Either>> fn) { return match(Either::left, trampoline(a -> fn.apply(a).>>coerce() .match(l -> terminate(left(l)), aOrB -> aOrB.fmap(Either::right)))); } @Override public final Either invert() { return match(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 @SuppressWarnings("varargs") public final Either merge(Fn2 leftFn, Fn2 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 effect the effecting consumer * @return the Either, unaltered * @deprecated in favor of {@link Either#match(Fn1, Fn1) matching} into an {@link IO} and explicitly running it */ @Deprecated public Either peek(Fn1> effect) { return match(l -> io(Either.left(l)), r -> effect.apply(r).fmap(constantly(this))) .unsafePerformIO(); } /** * Perform side-effects against a wrapped right or left value, returning back the Either unaltered. * * @param leftEffect the effecting consumer for left values * @param rightEffect the effecting consumer for right values * @return the Either, unaltered * @deprecated in favor of {@link Either#match(Fn1, Fn1) matching} into an {@link IO} and explicitly running it */ @Deprecated public Either peek(Fn1> leftEffect, Fn1> rightEffect) { return match(leftEffect, rightEffect).fmap(constantly(this)).unsafePerformIO(); } /** * 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 the result type * @param leftFn the left value mapping function * @param rightFn the right value mapping function * @return the result of applying the appropriate mapping function to the wrapped value */ @Override public abstract V match(Fn1 leftFn, Fn1 rightFn); /** * {@inheritDoc} */ @Override public Choice3 diverge() { return match(Choice3::a, Choice3::b); } /** * {@inheritDoc} */ @Override public final Either fmap(Fn1 fn) { return MonadError.super.fmap(fn).coerce(); } /** * {@inheritDoc} */ @Override public final Either biMapL(Fn1 fn) { return (Either) Bifunctor.super.biMapL(fn); } /** * {@inheritDoc} */ @Override public final Either biMapR(Fn1 fn) { return (Either) Bifunctor.super.biMapR(fn); } /** * {@inheritDoc} */ @Override public final Either biMap(Fn1 leftFn, Fn1 rightFn) { return match(l -> left(leftFn.apply(l)), r -> right(rightFn.apply(r))); } /** * {@inheritDoc} */ @Override public final Either pure(R2 r2) { return right(r2); } /** * {@inheritDoc} */ @Override public final Either zip(Applicative, Either> appFn) { return MonadError.super.zip(appFn).coerce(); } /** * {@inheritDoc} */ @Override public Lazy> lazyZip( Lazy, Either>> lazyAppFn) { return match(l -> lazy(left(l)), r -> lazyAppFn.fmap(eitherLF -> eitherLF.fmap(f -> f.apply(r)).coerce())); } /** * {@inheritDoc} */ @Override public final Either discardL(Applicative> appB) { return MonadError.super.discardL(appB).coerce(); } /** * {@inheritDoc} */ @Override public final Either discardR(Applicative> appB) { return MonadError.super.discardR(appB).coerce(); } /** * {@inheritDoc} */ @Override public Either throwError(L l) { return left(l); } /** * {@inheritDoc} */ @Override @SuppressWarnings("RedundantTypeArguments") public Either catchError(Fn1>> recoveryFn) { return match(recoveryFn.fmap(Monad>::coerce), Either::right); } /** * {@inheritDoc} */ @Override public final , TravB extends Traversable>, AppTrav extends Applicative> AppTrav traverse(Fn1> fn, Fn1 pure) { return match(l -> pure.apply(Either.left(l).coerce()), r -> fn.apply(r).>fmap(Either::right).fmap(Functor::coerce)) .coerce(); } /** * In the left case, returns a {@link Maybe#nothing()}; otherwise, returns {@link Maybe#maybe} around the right * value. * * @return Maybe the right value */ public final 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 the left parameter type * @param the right parameter type * @param maybe the maybe * @param leftFn0 the supplier to use for left values * @return a right value of the contained maybe value, or a left value of leftFn's result */ public static Either fromMaybe(Maybe maybe, Fn0 leftFn0) { return maybe.>fmap(Either::right) .orElseGet(() -> left(leftFn0.apply())); } /** * Attempt to execute the {@link Fn0}, 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 the left parameter type * @param the right parameter type * @param fn0 the supplier of the right value * @param leftFn a function mapping E to L * @return the supplier result as a right value, or leftFn's mapping result as a left value */ public static Either trying(Fn0 fn0, Fn1 leftFn) { return Try.trying(fn0).toEither(leftFn); } /** * Attempt to execute the {@link Fn0}, returning its result in a right value. If the supplier throws an * exception, wrap it in a left value and return it. * * @param fn0 the supplier of the right value * @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(Fn0 fn0) { return trying(fn0, id()); } /** * Attempt to execute the {@link SideEffect}, returning {@link Unit} in a right value. If the runnable throws * an exception, apply leftFn to it, wrap it in a left value, and return it. * * @param the left parameter type * @param sideEffect the runnable * @param leftFn a function mapping E to L * @return {@link Unit} as a right value, or leftFn's mapping result as a left value */ public static Either trying(SideEffect sideEffect, Fn1 leftFn) { return Try.trying(sideEffect).toEither(leftFn); } /** * Attempt to execute the {@link SideEffect}, returning {@link Unit} in a right value. If the runnable throws * exception, wrap it in a left value and return it. * * @param sideEffect the runnable * @return {@link Unit} as a right value, or a left value of the thrown exception */ public static Either trying(SideEffect sideEffect) { return trying(sideEffect, 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); } /** * The canonical {@link Pure} instance for {@link Either}. * * @param the left type * @return the {@link Pure} instance */ public static Pure> pureEither() { return Either::right; } private static final class Left extends Either { private final L l; private Left(L l) { this.l = l; } @Override public V match(Fn1 leftFn, Fn1 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(Fn1 leftFn, Fn1 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 - 2024 Weber Informatics LLC | Privacy Policy