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

com.jnape.palatable.lambda.adt.Try 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.Fn0;
import com.jnape.palatable.lambda.functions.Fn1;
import com.jnape.palatable.lambda.functions.recursion.RecursiveResult;
import com.jnape.palatable.lambda.functions.builtin.fn1.Downcast;
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.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.adt.Maybe.nothing;
import static com.jnape.palatable.lambda.adt.Unit.UNIT;
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.fn1.Upcast.upcast;
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.internal.Runtime.throwChecked;

/**
 * A {@link Monad} of the evaluation outcome of an expression that might throw. Try/catch/finally semantics map to
 * trying/catching/ensuring, respectively.
 *
 * @param  the possibly successful expression result
 * @see Either
 */
public abstract class Try implements
        MonadError>,
        MonadRec>,
        Traversable>,
        CoProduct2> {

    private Try() {
    }

    /**
     * Catch any instance of throwableType and map it to a success value.
     *
     * @param            the {@link Throwable} (sub)type
     * @param throwableType the {@link Throwable} (sub)type to be caught
     * @param recoveryFn    the function mapping the {@link Throwable} to the result
     * @return a new {@link Try} instance around either the original successful result or the mapped result
     */
    public final  Try catching(Class throwableType,
                                                       Fn1 recoveryFn) {
        return catching(throwableType::isInstance, t -> recoveryFn.apply(Downcast.downcast(t)));
    }

    /**
     * Catch any thrown T satisfying predicate and map it to a success value.
     *
     * @param predicate  the predicate
     * @param recoveryFn the function mapping the {@link Throwable} to the result
     * @return a new {@link Try} instance around either the original successful result or the mapped result
     */
    public final Try catching(Fn1 predicate,
                                 Fn1 recoveryFn) {
        return match(t -> predicate.apply(t) ? success(recoveryFn.apply(t)) : failure(t), Try::success);
    }

    /**
     * Run the provided runnable regardless of whether this is a success or a failure (the {@link Try} analog to
     * finally.
     * 

* If the runnable runs successfully, the result is preserved as is. If the runnable itself throws, and the result * was a success, the result becomes a failure over the newly-thrown {@link Throwable}. If the result was a failure * over some {@link Throwable} t1, and the runnable throws a new {@link Throwable} t2, the * result is a failure over t1 with t2 added to t1 as a suppressed exception. * * @param sideEffect the runnable block of code to execute * @return the same {@link Try} instance if runnable completes successfully; otherwise, a {@link Try} conforming to * rules above */ public final Try ensuring(SideEffect sideEffect) { return this.>match(t -> trying(sideEffect) .>fmap(constantly(failure(t))) .recover(t2 -> { t.addSuppressed(t2); return failure(t); }), a -> trying(sideEffect).fmap(constantly(a))); } /** * If this is a success, return the wrapped value. Otherwise, apply the {@link Throwable} to fn and * return the result. * * @param fn the function mapping the potential {@link Throwable} T to A * @return a success value */ public final A recover(Fn1 fn) { return match(fn, id()); } /** * If this is a failure, return the wrapped value. Otherwise, apply the success value to fn and return * the result. * * @param fn the function mapping the potential A to T * @return a failure value */ public final Throwable forfeit(Fn1 fn) { return match(id(), fn); } /** * If this is a success value, return it. Otherwise, rethrow the captured failure. * * @param a declarable exception type used for catching checked exceptions * @return possibly the success value * @throws T anything that the call site may want to explicitly catch or indicate could be thrown */ public final A orThrow() throws T { try { return orThrow(id()); } catch (Throwable t) { throw throwChecked(t); } } /** * If this is a success value, return it. Otherwise, transform the captured failure with fn and throw * the result. * * @param fn the {@link Throwable} transformation * @param the type of the thrown {@link Throwable} * @return possibly the success value * @throws T the transformation output */ public abstract A orThrow(Fn1 fn) throws T; /** * If this is a success, wrap the value in a {@link Maybe#just} and return it. Otherwise, return {@link * Maybe#nothing()}. * * @return {@link Maybe} the success value */ public final Maybe toMaybe() { return match(__ -> nothing(), Maybe::just); } /** * If this is a success, wrap the value in a {@link Either#right} and return it. Otherwise, return the {@link * Throwable} in an {@link Either#left}. * * @return {@link Either} the success value or the {@link Throwable} */ public final Either toEither() { return toEither(id()); } /** * If this is a success, wrap the value in a {@link Either#right} and return it. Otherwise, apply the mapping * function to the failure {@link Throwable}, re-wrap it in an {@link Either#left}, and return it. * * @param the {@link Either} left parameter type * @param fn the mapping function * @return {@link Either} the success value or the mapped left value */ public final Either toEither(Fn1 fn) { return match(fn.fmap(Either::left), Either::right); } /** * {@inheritDoc} */ @Override public Try throwError(Throwable throwable) { return failure(throwable); } /** * {@inheritDoc} */ @Override public Try catchError(Fn1>> recoveryFn) { return match(t -> recoveryFn.apply(t).coerce(), Try::success); } /** * {@inheritDoc} */ @Override public Try fmap(Fn1 fn) { return MonadError.super.fmap(fn).coerce(); } /** * {@inheritDoc} */ @Override public Try flatMap(Fn1>> f) { return match(Try::failure, a -> f.apply(a).coerce()); } /** * {@inheritDoc} */ @Override public Try pure(B b) { return success(b); } /** * {@inheritDoc} */ @Override public Try zip(Applicative, Try> appFn) { return MonadError.super.zip(appFn).coerce(); } /** * {@inheritDoc} */ @Override public Lazy> lazyZip(Lazy, Try>> lazyAppFn) { return match(f -> lazy(failure(f)), s -> lazyAppFn.fmap(tryF -> tryF.fmap(f -> f.apply(s)).coerce())); } /** * {@inheritDoc} */ @Override public Try discardL(Applicative> appB) { return MonadError.super.discardL(appB).coerce(); } /** * {@inheritDoc} */ @Override public Try discardR(Applicative> appB) { return MonadError.super.discardR(appB).coerce(); } /** * {@inheritDoc} */ @Override public Try trampolineM(Fn1, Try>> fn) { return flatMap(trampoline(a -> fn.apply(a).>>coerce().match( t -> terminate(failure(t)), aOrB -> aOrB.fmap(Try::success) ))); } /** * {@inheritDoc} */ @Override @SuppressWarnings("unchecked") public , TravB extends Traversable>, AppTrav extends Applicative> AppTrav traverse(Fn1> fn, Fn1 pure) { return match(t -> pure.apply((TravB) failure(t)), a -> fn.apply(a).fmap(Try::success).fmap(Applicative::coerce).coerce()); } /** * Static factory method for creating a success value. * * @param a the wrapped value * @param the success parameter type * @return a success value of a */ public static Try success(A a) { return new Success<>(a); } /** * Static factory method for creating a failure value. * * @param t the {@link Throwable} * @param the success parameter type * @return a failure value of t */ public static Try failure(Throwable t) { return new Failure<>(t); } /** * Execute supplier, returning a success A or a failure of the thrown {@link Throwable}. * * @param supplier the supplier * @param the possible success type * @return a new {@link Try} around either a successful A result or the thrown {@link Throwable} */ public static Try trying(Fn0 supplier) { try { return success(supplier.apply()); } catch (Throwable t) { return failure(t); } } /** * Execute runnable, returning a success {@link Unit} or a failure of the thrown {@link Throwable}. * * @param sideEffect the runnable * @return a new {@link Try} around either a successful {@link Unit} result or the thrown {@link Throwable} */ public static Try trying(SideEffect sideEffect) { return trying(() -> { IO.io(sideEffect).unsafePerformIO(); return UNIT; }); } /** * Given a {@link Fn0}<{@link AutoCloseable}> aSupplier and an {@link Fn1} * fn, apply fn to the result of aSupplier, ensuring that the result has its * {@link AutoCloseable#close() close} method invoked, regardless of the outcome. *

* If the resource creation process throws, the function body throws, or the * {@link AutoCloseable#close() close method} throws, the result is a failure. If both the function body and the * {@link AutoCloseable#close() close method} throw, the result is a failure over the function body * {@link Throwable} with the {@link AutoCloseable#close() close method} {@link Throwable} added as a * {@link Throwable#addSuppressed(Throwable) suppressed} {@link Throwable}. If only the * {@link AutoCloseable#close() close method} throws, the result is a failure over that {@link Throwable}. *

* Note that withResources calls can be nested, in which case all of the above specified exception * handling applies, where closing the previously created resource is considered part of the body of the next * withResources calls, and {@link Throwable Throwables} are considered suppressed in the same manner. * Additionally, {@link AutoCloseable#close() close methods} are invoked in the inverse order of resource creation. *

* This is {@link Try}'s equivalent of * * try-with-resources, introduced in Java 7. * * @param fn0 the resource supplier * @param fn the function body * @param the resource type * @param the function return type * @return a {@link Try} representing the result of the function's application to the resource */ @SuppressWarnings("try") public static Try withResources( Fn0 fn0, Fn1> fn) { return trying(() -> { try (A resource = fn0.apply()) { return fn.apply(resource).fmap(upcast()); } }).flatMap(id()); } /** * Convenience overload of {@link Try#withResources(Fn0, Fn1) withResources} that cascades dependent resource * creation via nested calls. * * @param fn0 the first resource supplier * @param bFn the dependent resource function * @param fn the function body * @param the first resource type * @param the second resource type * @param the function return type * @return a {@link Try} representing the result of the function's application to the dependent resource */ public static Try withResources( Fn0 fn0, Fn1 bFn, Fn1> fn) { return withResources(fn0, a -> withResources(() -> bFn.apply(a), fn::apply)); } /** * Convenience overload of {@link Try#withResources(Fn0, Fn1, Fn1) withResources} that * cascades * two dependent resource creations via nested calls. * * @param fn0 the first resource supplier * @param bFn the second resource function * @param cFn the final resource function * @param fn the function body * @param the first resource type * @param the second resource type * @param the final resource type * @param the function return type * @return a {@link Try} representing the result of the function's application to the final dependent resource */ public static Try withResources( Fn0 fn0, Fn1 bFn, Fn1 cFn, Fn1> fn) { return withResources(fn0, bFn, b -> withResources(() -> cFn.apply(b), fn::apply)); } /** * The canonical {@link Pure} instance for {@link Try}. * * @return the {@link Pure} instance */ public static Pure> pureTry() { return Try::success; } private static final class Failure extends Try { private final Throwable t; private Failure(Throwable t) { this.t = t; } @Override public A orThrow(Fn1 fn) throws T { throw fn.apply(t); } @Override public R match(Fn1 aFn, Fn1 bFn) { return aFn.apply(t); } @Override public boolean equals(Object other) { return other instanceof Failure && Objects.equals(t, ((Failure) other).t); } @Override public int hashCode() { return Objects.hash(t); } @Override public String toString() { return "Failure{" + "t=" + t + '}'; } } private static final class Success extends Try { private final A a; private Success(A a) { this.a = a; } @Override public A orThrow(Fn1 fn) { return a; } @Override public R match(Fn1 aFn, Fn1 bFn) { return bFn.apply(a); } @Override public boolean equals(Object other) { return other instanceof Success && Objects.equals(a, ((Success) other).a); } @Override public int hashCode() { return Objects.hash(a); } @Override public String toString() { return "Success{" + "a=" + a + '}'; } } }