Skip to content

Instantly share code, notes, and snippets.

@david-bakin
Created November 11, 2015 05:03
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save david-bakin/35d55daeeaee1eb71cea to your computer and use it in GitHub Desktop.
Save david-bakin/35d55daeeaee1eb71cea to your computer and use it in GitHub Desktop.
Java Result class - union (sum) type of a success type and a failure type, with a "pattern matching" functional interface
package com.bakins_bits.types;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import com.google.common.base.Preconditions;
/**
* Hold a result that can either be a successful value, or an failure (error) value. Use instead of exceptions for a
* more straightforward style (IMO) but <i>don't forget to use the return value of any method returning a Result!</i>.
* If your success or failure needs no additional information beyond the simple fact of success or failure, use type
* Void or a wildcard ? (and the parameterless factories success() and failure()).
* <p>
* Inspired by
* <a href="http://enterprisecraftsmanship.com/2015/03/20/functional-c-handling-failures-input-errors/">Functional C#:
* Handling failures, input errors</a> and the code
* <a href="https://gist.github.com/vkhorikov/7852c7606f27c52bc288">here</a>.
* <p>
* (I wish C-family languages, including the O-S variants like C# and Java, had an ability to force a compiler error on
* ignoring the result from a function/method (unless it was deliberately thrown away with a cast to void). Perhaps as
* an optional keyword on the function/method declaration.)
*/
public abstract class Result<S, F>
{
private Result() {
}
/**
* Return a success result.
*/
public static <S, F> Result<S, F> success(S s) {
return new SResult<S, F>(s);
}
/**
* Return a success result with no value.
*/
public static <S, F> Result<S, F> success() {
return new SResult<S, F>(null);
}
/**
* Return an error result.
*/
public static <S, F> Result<S, F> fail(F f) {
return new FResult<S, F>(f);
}
/**
* Return an error result with no value.
*/
public static <S, F> Result<S, F> fail() {
return new FResult<S, F>(null);
}
protected abstract Object getValue();
/**
* Return true iff this is a success result.
*/
public boolean isSuccess() {
return !isFailure();
}
/**
* Return true iff this is a failure result.
*/
public boolean isFailure() {
return !isSuccess();
}
/**
* Given a bunch of results, return the first fail, or if none, return a generic success (with void value).
*/
@SafeVarargs
public static <S, F> Result<S, F> combine(Result<S, F>... results) {
for (Result<S, F> r : results) {
Preconditions.checkNotNull(r, "no element of params results can be null");
if (r.isFailure())
return r;
}
return success();
}
// TODO: A version of combine that takes a combiner Function<F[], FF> to combine the failure values.
/**
* Continuation for successful values, given a 0-param lambda that returns a new Result. Passes failure through.
*/
public <SS> Result<SS, F> onSuccess(Supplier<Result<SS, F>> func) {
Preconditions.checkNotNull(func, "func must not be null");
if (isSuccess())
return func.get();
else {
@SuppressWarnings("unchecked")
F f = (F) getValue();
return fail(f);
}
}
/**
* Continuation for successful values, given a 1-param lambda that returns void. Passes failure through.
*/
public Result<S, F> onSuccess(Consumer<S> func) {
Preconditions.checkNotNull(func, "func must not be null");
if (isSuccess()) {
@SuppressWarnings("unchecked")
S s = (S) getValue();
func.accept(s);
}
return this;
}
/**
* Continuation for successful values, given a function that transforms a success value. Passes failure through.
*/
public <SS> Result<SS, F> onSuccess(Function<S, SS> func) {
Preconditions.checkNotNull(func, "func must not be null");
if (isSuccess()) {
@SuppressWarnings("unchecked")
S s = (S) getValue();
SS ss = func.apply(s);
return success(ss);
} else {
@SuppressWarnings("unchecked")
F f = (F) getValue();
return fail(f);
}
}
/**
* Continuation for failure values, given a 0-param lambda that returns a new Result. Passes success through.
*/
public <FF> Result<S, FF> onFailure(Supplier<Result<S, FF>> func) {
Preconditions.checkNotNull(func, "func must not be null");
if (isFailure())
return func.get();
else {
@SuppressWarnings("unchecked")
S s = (S) getValue();
return success(s);
}
}
/**
* Continuation for failure values, given a 1-param lambda that returns void. Passes success through.
*/
public Result<S, F> onFailure(Consumer<F> func) {
Preconditions.checkNotNull(func, "func must not be null");
if (isFailure()) {
@SuppressWarnings("unchecked")
F f = (F) getValue();
func.accept(f);
return this;
}
return this;
}
/**
* Continuation for failure values, given a function that transforms an error value. Passes success through.
*/
public <FF> Result<S, FF> onFailure(Function<F, FF> func) {
Preconditions.checkNotNull(func, "func must not be null");
if (isFailure()) {
@SuppressWarnings("unchecked")
F f = (F) getValue();
FF ff = func.apply(f);
return fail(ff);
} else {
@SuppressWarnings("unchecked")
S s = (S) getValue();
return success(s);
}
}
/**
* Continuation for Result values (success or failure), given a 1-param lambda that returns void. Passes the
* original Result through.
*/
public Result<S, F> onBoth(Consumer<Result<S, F>> func) {
Preconditions.checkNotNull(func, "func must not be null");
func.accept(this);
return this;
}
/**
* Continuation for Result values (success or failure), given a function that transforms this Result into another
* Result.
*/
public <SS, FF> Result<SS, FF> onBoth(Function<Result<S, F>, Result<SS, FF>> func) {
Preconditions.checkNotNull(func, "func must not be null");
return func.apply(this);
}
@Override
public boolean equals(Object obj) {
if (null == obj)
return false;
if (this == obj)
return true;
if (this.getClass() != obj.getClass())
return false;
@SuppressWarnings("rawtypes")
Result rhs = (Result) obj;
return new EqualsBuilder().append(this.getValue(), rhs.getValue()).isEquals();
}
@Override
public int hashCode() {
return new HashCodeBuilder(1069, 9601 /* emrips */).append(getValue()).toHashCode();
}
private static class SResult<S, F> extends Result<S, F>
{
private final S s;
private SResult(S s) {
this.s = s;
}
@Override
protected S getValue() {
return s;
}
@Override
public boolean isSuccess() {
return true;
}
@Override
public String toString() {
return "S:" + String.valueOf(s);
}
}
private static class FResult<S, F> extends Result<S, F>
{
private final F f;
private FResult(F f) {
this.f = f;
}
@Override
protected F getValue() {
return f;
}
@Override
public boolean isFailure() {
return true;
}
@Override
public String toString() {
return "F:" + String.valueOf(f);
}
}
}
package com.bakins_bits.types;
import static org.assertj.core.api.Assertions.*;
import org.testng.annotations.Test;
/**
* Test class Result<S,F>
*/
public class TestResult
{
// This little dance with field b and methods setB and resetB is to get around the "final" capture of Java lambdas.
public boolean b;
public void resetB() {
b = false;
}
public void setB() {
b = true;
}
@Test(groups = { "unit" })
public void success_is_success() {
// ARRANGE & ACT
Result<Integer, String> sut = Result.success(5);
// ASSERT
resetB();
assertThat(sut.isSuccess()).isTrue();
assertThat(sut.isFailure()).isFalse();
sut.onSuccess(i -> {
setB();
assertThat(i).isEqualTo(5);
}).onFailure(﹏ -> {
fail("called OnFailure");
});
assertThat(b).isTrue();
}
@Test(groups = { "unit" })
public void failure_is_failure() {
// ARRANGE & ACT
Result<Integer, String> sut = Result.fail("fubar");
// ASSERT
resetB();
assertThat(sut.isSuccess()).isFalse();
assertThat(sut.isFailure()).isTrue();
sut.onFailure(s -> {
setB();
assertThat(s).isEqualTo("fubar");
}).onSuccess(﹏ -> {
fail("called OnSuccess");
});
assertThat(b).isTrue();
}
@Test(groups = { "unit" })
public void success_type_can_be_Void() {
// ARRANGE & ACT
Result<Void, String> sut = Result.success();
// ASSERT
assertThat(sut.isSuccess()).isTrue();
assertThat(sut.isFailure()).isFalse();
}
@Test(groups = { "unit" })
public void success_type_can_be_unbounded_wildcard() {
// ARRANGE & ACT
Result<?, String> sut = Result.success();
// ASSERT
assertThat(sut.isSuccess()).isTrue();
assertThat(sut.isFailure()).isFalse();
}
@Test(groups = { "unit" })
public void combine_with_no_fails_returns_success() {
// ARRANGE
Result<Integer, String> sut1 = Result.success(5);
Result<Integer, String> sut2 = Result.success(15);
Result<Integer, String> sut3 = Result.success(20);
Result<Integer, String> sut4 = Result.success(25);
Result<Integer, String> sut5 = Result.success(30);
// ACT
Result<Integer, String> actual = Result.combine(sut1, sut2, sut3, sut4, sut5);
// ASSERT
assertThat(actual).isEqualTo(Result.success());
}
@Test(groups = { "unit" })
public void combine_returns_first_fail() {
// ARRANGE
Result<Integer, String> sut1 = Result.success(5);
Result<Integer, String> sut2 = Result.success(15);
Result<Integer, String> sut3 = Result.fail("foo");
Result<Integer, String> sut4 = Result.success(25);
Result<Integer, String> sut5 = Result.fail("bar");
// ACT
Result<Integer, String> actual = Result.combine(sut1, sut2, sut3, sut4, sut5);
// ASSERT
assertThat(actual).isEqualTo(sut3);
}
@Test(groups = { "unit" })
public void onSuccess_taking_supplier_works() {
// ARRANGE
Result<Integer, String> sutS = Result.success(15);
Result<Integer, String> sutF = Result.fail("foo");
// ACT
resetB();
Result<String, String> actualS = sutS.onSuccess(() -> {
setB();
return Result.<String, String> success("15");
}).onFailure(﹏ -> {
fail("onFailure called, sutS");
});
Result<String, String> actualF = sutF.onSuccess(() -> {
fail("onSuccess called, sutF");
return Result.success("x");
});
// ASSERT
assertThat(b).isTrue();
assertThat(actualS.isSuccess()).isTrue();
assertThat(actualS.getValue()).isEqualTo("15");
assertThat(actualF.isFailure()).isTrue();
assertThat(actualF.getValue()).isEqualTo("foo");
}
@Test(groups = { "unit" })
public void onFailure_taking_supplier_works() {
// ARRANGE
Result<Integer, String> sutS = Result.success(15);
Result<Integer, String> sutF = Result.fail("foo");
// ACT
resetB();
Result<Integer, Integer> actualF = sutF.onFailure(() -> {
setB();
return Result.<Integer, Integer> fail(15);
}).onSuccess(﹏ -> {
fail("onSuccess called, sutF");
});
Result<Integer, Integer> actualS = sutS.onFailure(() -> {
fail("onFailure called, sutS");
return Result.fail(10);
});
// ASSERT
assertThat(b).isTrue();
assertThat(actualF.isFailure()).isTrue();
assertThat(actualF.getValue()).isEqualTo(15);
assertThat(actualS.isSuccess()).isTrue();
assertThat(actualS.getValue()).isEqualTo(15);
}
@Test(groups = { "unit" })
public void onSuccess_taking_consumer_works() {
// ARRANGE
Result<Integer, String> sutS = Result.success(15);
Result<Integer, String> sutF = Result.fail("foo");
// ACT
resetB();
Result<Integer, String> actualS = sutS.onSuccess(s -> {
setB();
assertThat(s).isEqualTo(15);
}).onFailure(﹏ -> {
fail("onFailure called, sutS");
});
Result<Integer, String> actualF = sutF.onSuccess(() -> {
fail("onSuccess called, sutF");
return Result.success(20);
});
// ASSERT
assertThat(b).isTrue();
assertThat(actualS.isSuccess()).isTrue();
assertThat(actualS.getValue()).isEqualTo(15);
assertThat(actualF.isFailure()).isTrue();
assertThat(actualF.getValue()).isEqualTo("foo");
}
@Test(groups = { "unit" })
public void onFailure_taking_consumer_works() {
// ARRANGE
Result<Integer, String> sutS = Result.success(15);
Result<Integer, String> sutF = Result.fail("foo");
// ACT
resetB();
Result<Integer, String> actualF = sutF.onFailure(f -> {
setB();
assertThat(f).isEqualTo("foo");
}).onSuccess(﹏ -> {
fail("onSuccess called, sutF");
});
Result<Integer, String> actualS = sutS.onFailure(() -> {
fail("onFailure called, sutS");
return Result.success(25);
});
// ASSERT
assertThat(b).isTrue();
assertThat(actualF.isFailure()).isTrue();
assertThat(actualF.getValue()).isEqualTo("foo");
assertThat(actualS.isSuccess()).isTrue();
assertThat(actualS.getValue()).isEqualTo(15);
}
@Test(groups = { "unit" })
public void onSuccess_taking_transformer_works() {
// ARRANGE
Result<Integer, String> sutS = Result.success(15);
Result<Integer, String> sutF = Result.fail("foo");
// ACT
resetB();
Result<String, String> actualS = sutS.onSuccess(s -> {
setB();
assertThat(s).isEqualTo(15);
return "bar";
}).onFailure(﹏ -> {
fail("onFailure called, sutS");
});
Result<String, String> actualF = sutF.onSuccess(() -> {
fail("onSuccess called, sutF");
return Result.success("xray");
});
// ASSERT
assertThat(b).isTrue();
assertThat(actualS.isSuccess()).isTrue();
assertThat(actualS.getValue()).isEqualTo("bar");
assertThat(actualF.isFailure()).isTrue();
assertThat(actualF.getValue()).isEqualTo("foo");
}
@Test(groups = { "unit" })
public void onFailure_taking_transformer_works() {
// ARRANGE
Result<Integer, String> sutS = Result.success(15);
Result<Integer, String> sutF = Result.fail("foo");
// ACT
resetB();
Result<Integer, Integer> actualF = sutF.onFailure(f -> {
setB();
assertThat(f).isEqualTo("foo");
return 45;
}).onSuccess(﹏ -> {
fail("onSuccess called, sutF");
});
Result<Integer, Integer> actualS = sutS.onFailure(() -> {
fail("onFailure called, sutS");
return Result.success(25);
});
// ASSERT
assertThat(b).isTrue();
assertThat(actualF.isFailure()).isTrue();
assertThat(actualF.getValue()).isEqualTo(45);
assertThat(actualS.isSuccess()).isTrue();
assertThat(actualS.getValue()).isEqualTo(15);
}
@Test(groups = { "unit" })
public void onBoth_taking_consumer_works() {
// ARRANGE
Result<Integer, String> sutS = Result.success(15);
Result<Integer, String> sutF = Result.fail("foo");
// ACT & ASSERT
resetB();
Result<Integer, String> actualS = sutS.onBoth(r -> {
setB();
assertThat(r).isEqualTo(sutS);
});
assertThat(b).isTrue();
assertThat(actualS).isEqualTo(sutS);
resetB();
Result<Integer, String> actualF = sutF.onBoth(r -> {
setB();
assertThat(r).isEqualTo(sutF);
});
assertThat(b).isTrue();
assertThat(actualF).isEqualTo(sutF);
}
@Test(groups = { "unit" })
public void onBoth_taking_transformer_works() {
// ARRANGE
Result<Integer, String> sutS = Result.success(15);
Result<Integer, String> sutF = Result.fail("foo");
// ACT & ASSERT
resetB();
Result<String, Integer> actualS = sutS.onBoth(r -> {
setB();
assertThat(r).isEqualTo(sutS);
return Result.fail(40);
});
assertThat(b).isTrue();
assertThat(actualS.isFailure()).isTrue();
assertThat(actualS.getValue()).isEqualTo(40);
resetB();
Result<String, Integer> actualF = sutF.onBoth(r -> {
setB();
assertThat(r).isEqualTo(sutF);
return Result.success("xray");
});
assertThat(b).isTrue();
assertThat(actualF.isSuccess()).isTrue();
assertThat(actualF.getValue()).isEqualTo("xray");
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment