Created
July 26, 2019 04:40
-
-
Save ayago/ca26fcf94c74b847fc3cb34709f1b14f to your computer and use it in GitHub Desktop.
Handling exception side effect in Java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import java.util.List; | |
import java.util.function.Function; | |
import static java.util.stream.Collectors.collectingAndThen; | |
import static java.util.stream.Collectors.toList; | |
/** | |
* A Monad like construct (e.g. {@code Optional}) that handles | |
* whether the operation is successful or not | |
*/ | |
public abstract class Result<T> { | |
public static <S> Result<List<S>> merge(List<? extends Result<S>> results){ | |
return results.stream() | |
.filter(Result::isFailure) | |
.findFirst() | |
.map(t -> (Result<List<S>>) t) | |
.orElseGet(() -> results.stream() | |
.map(Result::get) | |
.collect(collectingAndThen(toList(), Result::success)) | |
); | |
} | |
public static Result<Unit> success() { | |
return new Success<>(Unit.unit()); | |
} | |
/** | |
* Factory method to create a successful result | |
* | |
* @param value contained value | |
* @param <U> contained value type | |
* @return a result instance of type U | |
*/ | |
public static <U> Result<U> success(U value) { | |
return new Success<>(value); | |
} | |
/** | |
* Factory method to create an erroneous result | |
* | |
* @param throwable cause of failure | |
* @param <U> target contained type | |
* @return a result instance of type U containing the failure cause | |
*/ | |
public static <U> Result<U> failure(Throwable throwable) { | |
return new Failure<>(throwable); | |
} | |
public static <U> Result<U> fallible(EffectfulSupplier<U> supplier) { | |
try { | |
U u = supplier.get(); | |
return new Success<>(u); | |
} catch (Throwable e){ | |
return new Failure<>(e); | |
} | |
} | |
/** | |
* Check if result is erroneous | |
* | |
* @return f result is erroneous | |
*/ | |
public abstract boolean isFailure(); | |
abstract T get(); | |
/** | |
* Handles the side effect of an operation when | |
* the execution resulted to an exception. Specifically | |
* when this result object is erroneous, the supplier is invoked | |
* to provide a default value when the result is erroneous | |
* | |
* @param other the supplier of alternative value | |
* @return a Result of same type with instance equal to the supplied value | |
*/ | |
public abstract T whenErrorThen(final Function<? super Throwable, T> other); | |
public abstract Result<T> whenError(Effect<Throwable> sideEffect); | |
public abstract Result<T> whenSuccess(Effect<T> sideEffect); | |
/** | |
* To comply with monad interface this flatMap | |
* is the implementation of bind method which | |
* allows chaining of functions that returns a Result of type U | |
* when this result is successful otherwise the provider | |
* transformation is not invoked | |
* | |
* @param mapper the transformation function | |
* @param <U> resulting contained type | |
* @return the resulting Result monad of type U | |
*/ | |
public abstract <U> Result<U> bind(Function<T, Result<U>> mapper); | |
/** | |
* To comply with functor interface this map | |
* is the implementation of map method which | |
* enforces that this type is a functor, meaning it is a | |
* container that can be mapped over | |
* | |
* @param mapper the transformation function | |
* @param <U> resulting contained type | |
* @return the resulting Result monad of type U | |
*/ | |
public abstract <U> Result<U> map(Function<T, U> mapper); | |
private static class Success<U> extends Result<U> { | |
private final U value; | |
private Success(U value) { | |
this.value = value; | |
} | |
@Override | |
public boolean isFailure() { | |
return false; | |
} | |
@Override | |
U get() { | |
return value; | |
} | |
@Override | |
public U whenErrorThen(Function<? super Throwable, U> other) { | |
return value; | |
} | |
@Override | |
public Result<U> whenError(Effect<Throwable> sideEffect) { | |
return this; | |
} | |
@Override | |
public Result<U> whenSuccess(Effect<U> sideEffect) { | |
sideEffect.execute(value); | |
return this; | |
} | |
@Override | |
public <U1> Result<U1> bind(Function<U, Result<U1>> mapper) { | |
return mapper.apply(value); | |
} | |
@Override | |
public <U1> Result<U1> map(Function<U, U1> mapper) { | |
return new Success<>(mapper.apply(value)); | |
} | |
} | |
private static class Failure<U> extends Result<U> { | |
private final Throwable error; | |
private Failure(Throwable error) { | |
this.error = error; | |
} | |
@Override | |
public boolean isFailure() { | |
return true; | |
} | |
@Override | |
U get() { | |
throw new RuntimeException("Are you nuts?"); | |
} | |
@Override | |
public U whenErrorThen(Function<? super Throwable, U> other) { | |
return other.apply(error); | |
} | |
@Override | |
public Result<U> whenError(Effect<Throwable> sideEffect) { | |
sideEffect.execute(error); | |
return this; | |
} | |
@Override | |
public Result<U> whenSuccess(Effect<U> sideEffect) { | |
return this; | |
} | |
@Override | |
public <U1> Result<U1> bind(Function<U, Result<U1>> mapper) { | |
return new Failure<>(error); | |
} | |
@Override | |
public <U1> Result<U1> map(Function<U, U1> mapper) { | |
return new Failure<>(error); | |
} | |
} | |
} | |
/** | |
* Used to handle side effects | |
*/ | |
interface Effect<T> { | |
/** | |
* Execute this effect | |
* @param t observed type | |
*/ | |
void execute(T t); | |
default Effect<T> andThen(Effect<T> effect) { | |
return b -> {execute(b); effect.execute(b);}; | |
} | |
} | |
@FunctionalInterface | |
interface EffectfulSupplier<T> { | |
T get() throws Exception; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment