Created
January 26, 2019 23:46
-
-
Save mnicky/b443a3ff792b1a0524195d72f2c036ee to your computer and use it in GitHub Desktop.
Result monad for Java (WIP)
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 lombok.AccessLevel; | |
import lombok.RequiredArgsConstructor; | |
import lombok.ToString; | |
import java.util.NoSuchElementException; | |
import java.util.Optional; | |
import java.util.function.Consumer; | |
import java.util.function.Function; | |
import java.util.function.Predicate; | |
import java.util.function.Supplier; | |
@RequiredArgsConstructor(access = AccessLevel.PRIVATE) | |
@ToString | |
public class Result<SUCC, FAIL> { | |
private final SUCC success; | |
private final FAIL failure; | |
//TODO: test whether failureSuppliesr shouldn't rather be functions of all arguments | |
/* === static factories === */ | |
public static <SUCC, FAIL> Result<SUCC, FAIL> success(SUCC success) { | |
return new Result<>(success, null); | |
} | |
public static <SUCC, FAIL> Result<SUCC, FAIL> failure(FAIL failure) { | |
return new Result<>(null, failure); | |
} | |
public static <SUCC, FAIL> Result<SUCC, FAIL> failure(Supplier<? extends FAIL> failure) { | |
return new Result<>(null, failure.get()); | |
} | |
public static <SUCC, FAIL> Result<SUCC, FAIL> ofNullable(SUCC nullable, FAIL failure) { | |
return nullable == null ? failure(failure) : success(nullable); | |
} | |
public static <SUCC, FAIL> Result<SUCC, FAIL> ofNullable(SUCC nullable, Supplier<? extends FAIL> failureSupplier) { | |
return nullable == null ? failure(failureSupplier.get()) : success(nullable); | |
} | |
//TODO: is it correct to return exact FAIL type?s | |
public static <SUCC> Result<SUCC, NullPointerException> ofNullable(SUCC nullable, String failureMessage) { | |
return nullable == null ? failure(new NullPointerException(failureMessage)) : success(nullable); | |
} | |
/* === getters === */ | |
public SUCC getSuccess() { | |
if (succeeded()) return success; | |
else throw new NoSuchElementException("Cannot get success of a failed result"); | |
} | |
public FAIL getFailure() { | |
if (failed()) return failure; | |
else throw new NoSuchElementException("Cannot get failure of a succeeded result"); | |
} | |
/* === methods === */ | |
public boolean succeeded() { | |
return success != null; | |
} | |
public boolean failed() { | |
return failure != null; | |
} | |
public void ifSucceeded(Consumer<? super SUCC> consumer) { | |
if (succeeded()) consumer.accept(getSuccess()); | |
} | |
public void ifFailed(Consumer<? super FAIL> consumer) { | |
if (failed()) consumer.accept(getFailure()); | |
} | |
//TODO: This method doesn't allow to change FAIL type. Is this correct? | |
public Result<SUCC, FAIL> filter(Predicate<? super SUCC> predicate, FAIL failure) { | |
if (succeeded()) return predicate.test(getSuccess()) ? this : failure(failure); | |
else return failure(getFailure()); | |
} | |
//TODO: This method doesn't allow to change FAIL type. Is this correct? | |
public Result<SUCC, FAIL> filter(Predicate<? super SUCC> predicate, Supplier<? extends FAIL> failureSupplier) { | |
if (succeeded()) return predicate.test(getSuccess()) ? this :failure(failureSupplier.get()); | |
else return failure(getFailure()); | |
} | |
//TODO: this method reuses failure from the previous call - makes it sense? | |
public <SUCC2> Result<SUCC2, FAIL> and(Function<? super SUCC, Result<SUCC2, FAIL>> mapper) { | |
if (succeeded()) return mapper.apply(getSuccess()); | |
else return failure(getFailure()); | |
} | |
//TODO: this method reuses failure from the previous call - makes it sense? | |
public <SUCC2> Result<SUCC2, FAIL> andResultOf(Function<? super SUCC, ? extends SUCC2> mapper) { | |
if (succeeded()) return success(mapper.apply(getSuccess())); | |
else return failure(getFailure()); | |
} | |
//TODO: this method reuses failure from the previous call - makes it sense? | |
public <SUCC2> Result<SUCC2, FAIL> andResultOfNullable(Function<? super SUCC, ? extends SUCC2> mapper) { | |
if (succeeded()) return ofNullable(mapper.apply(getSuccess()), getFailure()); | |
else return failure(getFailure()); | |
} | |
//TODO: this method reuses failure from the previous call - makes it sense? | |
public <SUCC2> Result<SUCC2, FAIL> andResultOfNullable(Function<? super SUCC, ? extends SUCC2> mapper, | |
Supplier<? extends FAIL> failureSupplier) | |
{ | |
if (succeeded()) return ofNullable(mapper.apply(getSuccess()), failureSupplier.get()); | |
else return failure(getFailure()); | |
} | |
//TODO: this method may change the type of FAIL. Is this correct? | |
public <SUCC2> Result<SUCC2, FAIL> andResultOfNullable(Function<? super SUCC, ? extends SUCC2> mapper, | |
String failureMessage) | |
{ | |
if (succeeded()) return ofNullable(mapper.apply(getSuccess()), failureMessage); | |
else return failure(getFailure()); | |
} | |
public <FAIL2> Result<SUCC, FAIL2> mapFailure(Function<? super FAIL, ? extends FAIL2> failureMapper) { | |
if (succeeded()) return success(getSuccess()); | |
else return failure(failureMapper.apply(getFailure())); | |
} | |
public <SUCC2, FAIL2> Result<SUCC2, FAIL2> mapBoth(Function<? super SUCC, ? extends SUCC2> successMapper, | |
Function<? super FAIL, ? extends FAIL2> failureMapper) | |
{ | |
if (succeeded()) return success(successMapper.apply(getSuccess())); | |
else return failure(failureMapper.apply(getFailure())); | |
} | |
public Result<SUCC, FAIL> or(SUCC other) { | |
if (succeeded()) return this; | |
else return Result.success(other); | |
} | |
public Result<SUCC, FAIL> or(Supplier<? extends SUCC> other) { | |
if (succeeded()) return this; | |
else return Result.success(other.get()); | |
} | |
public <E extends Throwable> Result<SUCC, FAIL> orThrow(Supplier<? extends E> exceptionSupplier) throws E { | |
if (succeeded()) return this; | |
else throw exceptionSupplier.get(); | |
} | |
public Result<SUCC, FAIL> orThrow(String exceptionMessage) throws RuntimeException { | |
if (succeeded()) return this; | |
else throw new RuntimeException(exceptionMessage); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment