Skip to content

Instantly share code, notes, and snippets.

@ayago
Created July 26, 2019 04:40
Show Gist options
  • Save ayago/ca26fcf94c74b847fc3cb34709f1b14f to your computer and use it in GitHub Desktop.
Save ayago/ca26fcf94c74b847fc3cb34709f1b14f to your computer and use it in GitHub Desktop.
Handling exception side effect in Java
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