com.jnape.palatable.lambda.adt.Try 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.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 super S, ? extends A> 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 super Throwable, ? extends Boolean> predicate,
Fn1 super Throwable, ? extends A> 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 super Throwable, ? extends A> 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 super A, ? extends Throwable> 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 super Throwable, ? extends T> 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 super Throwable, ? extends L> 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 super Throwable, ? extends Monad>> recoveryFn) {
return match(t -> recoveryFn.apply(t).coerce(), Try::success);
}
/**
* {@inheritDoc}
*/
@Override
public Try fmap(Fn1 super A, ? extends B> fn) {
return MonadError.super.fmap(fn).coerce();
}
/**
* {@inheritDoc}
*/
@Override
public Try flatMap(Fn1 super A, ? extends Monad>> 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 extends Applicative, 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 super A, ? extends MonadRec, 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 super A, ? extends Applicative> fn,
Fn1 super TravB, ? extends AppTrav> 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 extends A> 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 extends A> fn0,
Fn1 super A, ? extends Try extends B>> 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 extends A> fn0,
Fn1 super A, ? extends B> bFn,
Fn1 super B, ? extends Try extends C>> 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 extends A> fn0,
Fn1 super A, ? extends B> bFn,
Fn1 super B, ? extends C> cFn,
Fn1 super C, ? extends Try extends D>> 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 super Throwable, ? extends T> fn) throws T {
throw fn.apply(t);
}
@Override
public R match(Fn1 super Throwable, ? extends R> aFn, Fn1 super A, ? extends R> 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 super Throwable, ? extends T> fn) {
return a;
}
@Override
public R match(Fn1 super Throwable, ? extends R> aFn, Fn1 super A, ? extends R> 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 +
'}';
}
}
}