From 47193e64808cebb610d82555308124eff5961232 Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Sat, 23 Jun 2018 18:14:19 +0100 Subject: [PATCH 01/11] Add Result.map() --- src/main/java/net/kemitix/mon/result/Err.java | 5 ++++ .../java/net/kemitix/mon/result/Result.java | 12 +++++++- .../java/net/kemitix/mon/result/Success.java | 5 ++++ src/test/java/net/kemitix/mon/ResultTest.java | 29 +++++++++++++++++++ 4 files changed, 50 insertions(+), 1 deletion(-) diff --git a/src/main/java/net/kemitix/mon/result/Err.java b/src/main/java/net/kemitix/mon/result/Err.java index e454ade..a94318d 100644 --- a/src/main/java/net/kemitix/mon/result/Err.java +++ b/src/main/java/net/kemitix/mon/result/Err.java @@ -51,6 +51,11 @@ class Err implements Result { return Result.error(error); } + @Override + public Result map(final Function f) { + return Result.error(error); + } + @Override public void match(final Consumer onSuccess, final Consumer onError) { onError.accept(error); diff --git a/src/main/java/net/kemitix/mon/result/Result.java b/src/main/java/net/kemitix/mon/result/Result.java index 30f446b..3bf2a81 100644 --- a/src/main/java/net/kemitix/mon/result/Result.java +++ b/src/main/java/net/kemitix/mon/result/Result.java @@ -73,12 +73,22 @@ public interface Result { * Returns a new Result consisting of the result of applying the function to the contents of the Result. * * @param f the mapping function the produces a Result - * @param the type of the result of the mapping function + * @param the type of the value withing the Result of the mapping function * * @return a Result */ Result flatMap(Function> f); + /** + * Applies the functions to the value of a successful result, while doing nothing with an error. + * + * @param f the mapping function to produce the new value + * @param the type of the result of the mapping function + * + * @return a Result + */ + Result map(Function f); + /** * Matches the Result, either success or error, and supplies the appropriate Consumer with the value or error. * diff --git a/src/main/java/net/kemitix/mon/result/Success.java b/src/main/java/net/kemitix/mon/result/Success.java index 413d7c5..414c487 100644 --- a/src/main/java/net/kemitix/mon/result/Success.java +++ b/src/main/java/net/kemitix/mon/result/Success.java @@ -51,6 +51,11 @@ class Success implements Result { return f.apply(value); } + @Override + public Result map(final Function f) { + return Result.ok(f.apply(value)); + } + @Override public void match(final Consumer onSuccess, final Consumer onError) { onSuccess.accept(value); diff --git a/src/test/java/net/kemitix/mon/ResultTest.java b/src/test/java/net/kemitix/mon/ResultTest.java index 82eeebc..4de77e3 100644 --- a/src/test/java/net/kemitix/mon/ResultTest.java +++ b/src/test/java/net/kemitix/mon/ResultTest.java @@ -105,6 +105,35 @@ public class ResultTest implements WithAssertions { assertThat(flatMap.isError()).isTrue(); } + @Test + public void success_whenMap_isSuccess() { + //given + final Result okResult = Result.ok(1); + //when + final Result result = okResult.map(value -> String.valueOf(value)); + //then + assertThat(result.isOkay()).isTrue(); + result.match( + success -> assertThat(success).isEqualTo("1"), + error -> fail("not an error") + ); + } + + @Test + public void error_whenMap_isError() { + //given + final RuntimeException exception = new RuntimeException(); + final Result errorResult = Result.error(exception); + //when + final Result result = errorResult.map(value -> String.valueOf(value)); + //then + assertThat(result.isError()).isTrue(); + result.match( + success -> fail("not an success"), + error -> assertThat(error).isSameAs(exception) + ); + } + @Test public void useCase_whenOkay_thenReturnSuccess() { //given From 62a61213cc664a6704dd38bd6dfa2bd1ea97f774 Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Sat, 23 Jun 2018 18:22:05 +0100 Subject: [PATCH 02/11] Added Result.maybe() --- src/main/java/net/kemitix/mon/result/Err.java | 7 +++ .../java/net/kemitix/mon/result/Result.java | 10 +++ .../java/net/kemitix/mon/result/Success.java | 10 +++ src/test/java/net/kemitix/mon/ResultTest.java | 61 +++++++++++++++++++ 4 files changed, 88 insertions(+) diff --git a/src/main/java/net/kemitix/mon/result/Err.java b/src/main/java/net/kemitix/mon/result/Err.java index a94318d..76431ac 100644 --- a/src/main/java/net/kemitix/mon/result/Err.java +++ b/src/main/java/net/kemitix/mon/result/Err.java @@ -22,9 +22,11 @@ package net.kemitix.mon.result; import lombok.RequiredArgsConstructor; +import net.kemitix.mon.maybe.Maybe; import java.util.function.Consumer; import java.util.function.Function; +import java.util.function.Predicate; /** * An Error Result. @@ -60,4 +62,9 @@ class Err implements Result { public void match(final Consumer onSuccess, final Consumer onError) { onError.accept(error); } + + @Override + public Result> maybe(final Predicate predicate) { + return Result.error(error); + } } diff --git a/src/main/java/net/kemitix/mon/result/Result.java b/src/main/java/net/kemitix/mon/result/Result.java index 3bf2a81..37775a9 100644 --- a/src/main/java/net/kemitix/mon/result/Result.java +++ b/src/main/java/net/kemitix/mon/result/Result.java @@ -21,8 +21,11 @@ package net.kemitix.mon.result; +import net.kemitix.mon.maybe.Maybe; + import java.util.function.Consumer; import java.util.function.Function; +import java.util.function.Predicate; /** * An Either type for holding a result or an error (exception). @@ -97,4 +100,11 @@ public interface Result { */ void match(Consumer onSuccess, Consumer onError); + /** + * Wraps the value within the Result in a Maybe, either a Just if the predicate is true, or Nothing. + * + * @param predicate the test to decide + * @return a Result containing a Maybe that may or may not contain a value + */ + Result> maybe(Predicate predicate); } diff --git a/src/main/java/net/kemitix/mon/result/Success.java b/src/main/java/net/kemitix/mon/result/Success.java index 414c487..80dbea5 100644 --- a/src/main/java/net/kemitix/mon/result/Success.java +++ b/src/main/java/net/kemitix/mon/result/Success.java @@ -22,9 +22,11 @@ package net.kemitix.mon.result; import lombok.RequiredArgsConstructor; +import net.kemitix.mon.maybe.Maybe; import java.util.function.Consumer; import java.util.function.Function; +import java.util.function.Predicate; /** * A Successful Result. @@ -60,4 +62,12 @@ class Success implements Result { public void match(final Consumer onSuccess, final Consumer onError) { onSuccess.accept(value); } + + @Override + public Result> maybe(final Predicate predicate) { + if (predicate.test(value)) { + return Result.ok(Maybe.just(value)); + } + return Result.ok(Maybe.nothing()); + } } diff --git a/src/test/java/net/kemitix/mon/ResultTest.java b/src/test/java/net/kemitix/mon/ResultTest.java index 4de77e3..1bbb9e8 100644 --- a/src/test/java/net/kemitix/mon/ResultTest.java +++ b/src/test/java/net/kemitix/mon/ResultTest.java @@ -1,10 +1,13 @@ package net.kemitix.mon; import lombok.RequiredArgsConstructor; +import net.kemitix.mon.maybe.Maybe; import net.kemitix.mon.result.Result; import org.assertj.core.api.WithAssertions; import org.junit.Test; +import java.util.function.Predicate; + public class ResultTest implements WithAssertions { @Test @@ -134,6 +137,64 @@ public class ResultTest implements WithAssertions { ); } + @Test + public void successMaybe_whenPasses_isSuccessJust() { + //given + final Result okResult = Result.ok(1); + //when + final Result> maybeResult = okResult.maybe(value -> value >= 0); + //then + assertThat(maybeResult.isOkay()).isTrue(); + maybeResult.match( + success -> assertThat(success.toOptional()).contains(1), + error -> fail("not an error") + ); + } + + @Test + public void successMaybe_whenFails_isSuccessNothing() { + //given + final Result okResult = Result.ok(1); + //when + final Result> maybeResult = okResult.maybe(value -> value >= 4); + //then + assertThat(maybeResult.isOkay()).isTrue(); + maybeResult.match( + success -> assertThat(success.toOptional()).isEmpty(), + error -> fail("not an error") + ); + } + + @Test + public void errorMaybe_whenPasses_isError() { + //given + final RuntimeException exception = new RuntimeException(); + final Result errorResult = Result.error(exception); + //when + final Result> maybeResult = errorResult.maybe(value -> value >= 0); + //then + assertThat(maybeResult.isError()).isTrue(); + maybeResult.match( + success -> fail("not a success"), + error -> assertThat(error).isSameAs(exception) + ); + } + + @Test + public void errorMaybe_whenFails_isError() { + //given + final RuntimeException exception = new RuntimeException(); + final Result errorResult = Result.error(exception); + //when + final Result> maybeResult = errorResult.maybe(value -> value >= 4); + //then + assertThat(maybeResult.isError()).isTrue(); + maybeResult.match( + success -> fail("not a success"), + error -> assertThat(error).isSameAs(exception) + ); + } + @Test public void useCase_whenOkay_thenReturnSuccess() { //given From 05aa6fb3237661a2a2dc794a1312d3ec866e5251 Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Sat, 23 Jun 2018 22:29:47 +0100 Subject: [PATCH 03/11] Add Result.fromMaybe(Maybe, Supplier) --- src/main/java/net/kemitix/mon/maybe/Just.java | 3 +- .../java/net/kemitix/mon/maybe/Maybe.java | 54 +++++++++---------- .../java/net/kemitix/mon/maybe/Nothing.java | 5 +- .../java/net/kemitix/mon/result/Result.java | 36 ++++++++----- src/test/java/net/kemitix/mon/ResultTest.java | 49 +++++++++++++---- 5 files changed, 91 insertions(+), 56 deletions(-) diff --git a/src/main/java/net/kemitix/mon/maybe/Just.java b/src/main/java/net/kemitix/mon/maybe/Just.java index 0e480f0..449e523 100644 --- a/src/main/java/net/kemitix/mon/maybe/Just.java +++ b/src/main/java/net/kemitix/mon/maybe/Just.java @@ -36,7 +36,6 @@ import java.util.stream.Stream; * A Maybe where a value is present. * * @param the type of the content - * * @author Paul Campbell (pcampbell@kemitix.net) */ @RequiredArgsConstructor(access = AccessLevel.PROTECTED) @@ -45,7 +44,7 @@ final class Just implements Maybe { private final T value; @Override - public Maybe map(final Function f) { + public Maybe map(final Function f) { return new Just<>(f.apply(value)); } diff --git a/src/main/java/net/kemitix/mon/maybe/Maybe.java b/src/main/java/net/kemitix/mon/maybe/Maybe.java index c26ed13..51b4584 100644 --- a/src/main/java/net/kemitix/mon/maybe/Maybe.java +++ b/src/main/java/net/kemitix/mon/maybe/Maybe.java @@ -25,46 +25,21 @@ import lombok.NonNull; import net.kemitix.mon.Functor; import java.util.Optional; +import java.util.function.Function; /** * A value that may or may not be present. * * @param the type of the content of the Just - * * @author Paul Campbell (pcampbell@kemitix.net) */ public interface Maybe extends Functor>, MaybeStream, MaybeOptional { - /** - * Create a Maybe for the value that is present. - * - * @param value the value, not null - * @param the type of the value - * - * @return a Maybe of the value - */ - static Maybe just(@NonNull final T value) { - return new Just<>(value); - } - - /** - * Create a Maybe for a lack of a value. - * - * @param the type of the missing value - * - * @return an empty Maybe - */ - @SuppressWarnings("unchecked") - static Maybe nothing() { - return (Maybe) Nothing.INSTANCE; - } - /** * Create a Maybe for the value that may or may not be present. * * @param value the value, may be null * @param the type of the value - * * @return a Maybe, either a Just, or Nothing if value is null */ static Maybe maybe(final T value) { @@ -74,12 +49,33 @@ public interface Maybe extends Functor>, MaybeStream, MaybeOpt return just(value); } + /** + * Create a Maybe for a lack of a value. + * + * @param the type of the missing value + * @return an empty Maybe + */ + @SuppressWarnings("unchecked") + static Maybe nothing() { + return (Maybe) Nothing.INSTANCE; + } + + /** + * Create a Maybe for the value that is present. + * + * @param value the value, not null + * @param the type of the value + * @return a Maybe of the value + */ + static Maybe just(@NonNull final T value) { + return new Just<>(value); + } + /** * Create a Maybe from an {@link Optional}. * * @param optional the Optional * @param the type of the Optional - * * @return a Maybe * @deprecated need to find a better way of converting an Optional to a Maybe, but * without having to pass the Optional as a parameter @@ -88,7 +84,9 @@ public interface Maybe extends Functor>, MaybeStream, MaybeOpt @Deprecated static Maybe fromOptional(final Optional optional) { return optional.map(Maybe::maybe) - .orElse(nothing()); + .orElse(nothing()); } + @Override + Maybe map(Function f); } diff --git a/src/main/java/net/kemitix/mon/maybe/Nothing.java b/src/main/java/net/kemitix/mon/maybe/Nothing.java index 1ea6eb4..47448b0 100644 --- a/src/main/java/net/kemitix/mon/maybe/Nothing.java +++ b/src/main/java/net/kemitix/mon/maybe/Nothing.java @@ -32,7 +32,6 @@ import java.util.stream.Stream; * A Maybe where no value is present. * * @param the type of the missing content - * * @author Paul Campbell (pcampbell@kemitix.net) */ final class Nothing implements Maybe { @@ -40,8 +39,8 @@ final class Nothing implements Maybe { static final Maybe INSTANCE = new Nothing<>(); @Override - public Maybe map(final Function f) { - return this; + public Maybe map(final Function f) { + return (Maybe) INSTANCE; } @Override diff --git a/src/main/java/net/kemitix/mon/result/Result.java b/src/main/java/net/kemitix/mon/result/Result.java index 37775a9..e2d8c3d 100644 --- a/src/main/java/net/kemitix/mon/result/Result.java +++ b/src/main/java/net/kemitix/mon/result/Result.java @@ -26,38 +26,51 @@ import net.kemitix.mon.maybe.Maybe; import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Predicate; +import java.util.function.Supplier; /** * An Either type for holding a result or an error (exception). * * @param the type of the result when a success - * * @author Paul Campbell (pcampbell@kemitix.net) */ public interface Result { /** - * Create a Result for a success. + * Creates a Result from the Maybe, where the Result will be an error if the Maybe is Nothing. * - * @param value the value - * @param the type of the value - * @return a successful Result + * @param maybe the Maybe the might contain the value of the Result + * @param error the error that will be the Result if maybe is Nothing + * @param the type of the Maybe and the Result + * @return a Result containing the value of the Maybe when it is a Just, or the error when it is Nothing */ - static Result ok(final T value) { - return new Success<>(value); + static Result fromMaybe(final Maybe maybe, final Supplier error) { + return maybe.map(Result::ok) + .orElseGet(() -> Result.error(error.get())); } /** * Create a Result for an error. * * @param error the error (exception) - * @param the type had the result been a success + * @param the type had the result been a success * @return an error Result */ static Result error(final Throwable error) { return new Err<>(error); } + /** + * Create a Result for a success. + * + * @param value the value + * @param the type of the value + * @return a successful Result + */ + static Result ok(final T value) { + return new Success<>(value); + } + /** * Checks of the Result is an error. * @@ -77,7 +90,6 @@ public interface Result { * * @param f the mapping function the produces a Result * @param the type of the value withing the Result of the mapping function - * * @return a Result */ Result flatMap(Function> f); @@ -85,9 +97,8 @@ public interface Result { /** * Applies the functions to the value of a successful result, while doing nothing with an error. * - * @param f the mapping function to produce the new value + * @param f the mapping function to produce the new value * @param the type of the result of the mapping function - * * @return a Result */ Result map(Function f); @@ -96,7 +107,7 @@ public interface Result { * Matches the Result, either success or error, and supplies the appropriate Consumer with the value or error. * * @param onSuccess the Consumer to pass the value of a successful Result to - * @param onError the Consumer to pass the error from an error Result to + * @param onError the Consumer to pass the error from an error Result to */ void match(Consumer onSuccess, Consumer onError); @@ -107,4 +118,5 @@ public interface Result { * @return a Result containing a Maybe that may or may not contain a value */ Result> maybe(Predicate predicate); + } diff --git a/src/test/java/net/kemitix/mon/ResultTest.java b/src/test/java/net/kemitix/mon/ResultTest.java index 1bbb9e8..ef79954 100644 --- a/src/test/java/net/kemitix/mon/ResultTest.java +++ b/src/test/java/net/kemitix/mon/ResultTest.java @@ -6,8 +6,6 @@ import net.kemitix.mon.result.Result; import org.assertj.core.api.WithAssertions; import org.junit.Test; -import java.util.function.Predicate; - public class ResultTest implements WithAssertions { @Test @@ -195,6 +193,35 @@ public class ResultTest implements WithAssertions { ); } + @Test + public void justMaybe_isSuccess() { + //given + final Maybe just = Maybe.just(1); + //when + final Result result = Result.fromMaybe(just, () -> new RuntimeException()); + //then + assertThat(result.isOkay()).isTrue(); + result.match( + success -> assertThat(success).isEqualTo(1), + error -> fail("not an error") + ); + } + + @Test + public void nothingMaybe_isError() { + //given + final Maybe nothing = Maybe.nothing(); + final RuntimeException exception = new RuntimeException(); + //when + final Result result = Result.fromMaybe(nothing, () -> exception); + //then + assertThat(result.isError()).isTrue(); + result.match( + success -> fail("not a success"), + error -> assertThat(error).isSameAs(exception) + ); + } + @Test public void useCase_whenOkay_thenReturnSuccess() { //given @@ -265,20 +292,20 @@ public class ResultTest implements WithAssertions { calculateAverage(adjustedIntFromFile1, intFromFile2)))); } - private Result calculateAverage(final Integer val1, final Integer val2) { - return Result.ok((double) (val1 + val2) / 2); - } - - private Result adjustValue(Integer value) { - return Result.ok(value + 2); - } - - private Result readIntFromFile(String fileName) { + private Result readIntFromFile(final String fileName) { if (okay) { return Result.ok(fileName.length()); } return Result.error(new RuntimeException(fileName)); } + private Result adjustValue(final Integer value) { + return Result.ok(value + 2); + } + + private Result calculateAverage(final Integer val1, final Integer val2) { + return Result.ok((double) (val1 + val2) / 2); + } + } } \ No newline at end of file From e9184f88bf5440ee81f9da79509b9511b9eef53a Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Sun, 24 Jun 2018 08:16:24 +0100 Subject: [PATCH 04/11] Add Result.orElseThrow() --- src/main/java/net/kemitix/mon/result/Err.java | 5 +++++ .../java/net/kemitix/mon/result/Result.java | 14 ++++++++++--- .../java/net/kemitix/mon/result/Success.java | 5 +++++ src/test/java/net/kemitix/mon/ResultTest.java | 20 +++++++++++++++++++ 4 files changed, 41 insertions(+), 3 deletions(-) diff --git a/src/main/java/net/kemitix/mon/result/Err.java b/src/main/java/net/kemitix/mon/result/Err.java index 76431ac..b8666a4 100644 --- a/src/main/java/net/kemitix/mon/result/Err.java +++ b/src/main/java/net/kemitix/mon/result/Err.java @@ -67,4 +67,9 @@ class Err implements Result { public Result> maybe(final Predicate predicate) { return Result.error(error); } + + @Override + public T orElseThrow() throws Throwable { + throw error; + } } diff --git a/src/main/java/net/kemitix/mon/result/Result.java b/src/main/java/net/kemitix/mon/result/Result.java index e2d8c3d..018db6a 100644 --- a/src/main/java/net/kemitix/mon/result/Result.java +++ b/src/main/java/net/kemitix/mon/result/Result.java @@ -29,7 +29,7 @@ import java.util.function.Predicate; import java.util.function.Supplier; /** - * An Either type for holding a result or an error (exception). + * An Either type for holding a result or an error (Throwable). * * @param the type of the result when a success * @author Paul Campbell (pcampbell@kemitix.net) @@ -44,7 +44,7 @@ public interface Result { * @param the type of the Maybe and the Result * @return a Result containing the value of the Maybe when it is a Just, or the error when it is Nothing */ - static Result fromMaybe(final Maybe maybe, final Supplier error) { + static Result fromMaybe(final Maybe maybe, final Supplier error) { return maybe.map(Result::ok) .orElseGet(() -> Result.error(error.get())); } @@ -52,7 +52,7 @@ public interface Result { /** * Create a Result for an error. * - * @param error the error (exception) + * @param error the error (Throwable) * @param the type had the result been a success * @return an error Result */ @@ -119,4 +119,12 @@ public interface Result { */ Result> maybe(Predicate predicate); + /** + * Extracts the successful value from the result, or throws the error Throwable. + * + * @return the value if a success + * @throws Throwable the result is an error + */ + @SuppressWarnings("illegalthrows") + T orElseThrow() throws Throwable; } diff --git a/src/main/java/net/kemitix/mon/result/Success.java b/src/main/java/net/kemitix/mon/result/Success.java index 80dbea5..f10c25d 100644 --- a/src/main/java/net/kemitix/mon/result/Success.java +++ b/src/main/java/net/kemitix/mon/result/Success.java @@ -70,4 +70,9 @@ class Success implements Result { } return Result.ok(Maybe.nothing()); } + + @Override + public T orElseThrow() { + return value; + } } diff --git a/src/test/java/net/kemitix/mon/ResultTest.java b/src/test/java/net/kemitix/mon/ResultTest.java index ef79954..df36e63 100644 --- a/src/test/java/net/kemitix/mon/ResultTest.java +++ b/src/test/java/net/kemitix/mon/ResultTest.java @@ -222,6 +222,26 @@ public class ResultTest implements WithAssertions { ); } + @Test + public void success_whenOrElseThrow_isValue() throws Throwable { + //given + final Result ok = Result.ok(1); + //when + final Integer value = ok.orElseThrow(); + //then + assertThat(value).isEqualTo(1); + } + + @Test + public void error_whenOrElseThrow_throws() { + //given + final RuntimeException exception = new RuntimeException(); + final Result error = Result.error(exception); + //when + assertThatThrownBy(() -> error.orElseThrow()) + .isSameAs(exception); + } + @Test public void useCase_whenOkay_thenReturnSuccess() { //given From 8ec3069d1d2a7c0b9949f276053039d64a34a45c Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Sun, 24 Jun 2018 08:16:54 +0100 Subject: [PATCH 05/11] Suppress warning for unchecked cast --- src/main/java/net/kemitix/mon/maybe/Nothing.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/net/kemitix/mon/maybe/Nothing.java b/src/main/java/net/kemitix/mon/maybe/Nothing.java index 47448b0..c90beb5 100644 --- a/src/main/java/net/kemitix/mon/maybe/Nothing.java +++ b/src/main/java/net/kemitix/mon/maybe/Nothing.java @@ -39,6 +39,7 @@ final class Nothing implements Maybe { static final Maybe INSTANCE = new Nothing<>(); @Override + @SuppressWarnings("unchecked") public Maybe map(final Function f) { return (Maybe) INSTANCE; } From f0be46302894db9b1eacd963f29666c02ee5c1b3 Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Sun, 24 Jun 2018 22:36:02 +0100 Subject: [PATCH 06/11] Add Result.toMaybe() and Result.invert() --- .../java/net/kemitix/mon/result/Result.java | 30 ++++++++++ src/test/java/net/kemitix/mon/ResultTest.java | 60 +++++++++++++++++++ 2 files changed, 90 insertions(+) diff --git a/src/main/java/net/kemitix/mon/result/Result.java b/src/main/java/net/kemitix/mon/result/Result.java index 018db6a..ea0de64 100644 --- a/src/main/java/net/kemitix/mon/result/Result.java +++ b/src/main/java/net/kemitix/mon/result/Result.java @@ -34,6 +34,7 @@ import java.util.function.Supplier; * @param the type of the result when a success * @author Paul Campbell (pcampbell@kemitix.net) */ +@SuppressWarnings("methodcount") public interface Result { /** @@ -71,6 +72,35 @@ public interface Result { return new Success<>(value); } + /** + * Creates a Result from the Maybe, where the Result will be an error if the Maybe is Nothing. + * + * @param result the Result the might contain the value of the Result + * @param the type of the Maybe and the Result + * @return a Result containing the value of the Maybe when it is a Just, or the error when it is Nothing + */ + @SuppressWarnings("illegalcatch") + static Maybe toMaybe(final Result result) { + try { + return Maybe.just(result.orElseThrow()); + } catch (final Throwable throwable) { + return Maybe.nothing(); + } + } + + /** + * Swaps the inner Result of a Maybe, so that a Result is on the outside. + * @param maybeResult the Maybe the contains a Result + * @param the type of the value that may be in the Result + * @return a Result containing a Maybe, the value in the Maybe was the value in a successful Result within the + * original Maybe. If the original Maybe is Nothing, the Result will contain Nothing. If the original Result was an + * error, then the Result will also be an error. + */ + static Result> invert(final Maybe> maybeResult) { + return maybeResult.orElseGet(() -> Result.ok(null)) + .flatMap(value -> Result.ok(Maybe.maybe(value))); + } + /** * Checks of the Result is an error. * diff --git a/src/test/java/net/kemitix/mon/ResultTest.java b/src/test/java/net/kemitix/mon/ResultTest.java index df36e63..7c25651 100644 --- a/src/test/java/net/kemitix/mon/ResultTest.java +++ b/src/test/java/net/kemitix/mon/ResultTest.java @@ -222,6 +222,26 @@ public class ResultTest implements WithAssertions { ); } + @Test + public void success_toMaybe_isJust() { + //given + final Result ok = Result.ok(1); + //when + final Maybe maybe = Result.toMaybe(ok); + //then + assertThat(maybe.toOptional()).contains(1); + } + + @Test + public void error_toMaybe_isNothing() { + //given + final Result error = Result.error(new RuntimeException()); + //when + final Maybe maybe = Result.toMaybe(error); + //then + assertThat(maybe.toOptional()).isEmpty(); + } + @Test public void success_whenOrElseThrow_isValue() throws Throwable { //given @@ -242,6 +262,46 @@ public class ResultTest implements WithAssertions { .isSameAs(exception); } + @Test + public void JustSuccess_invert_thenSuccessJust() { + //given + final Maybe> justSuccess = Maybe.just(Result.ok(1)); + //when + final Result> result = Result.invert(justSuccess); + //then + result.match( + success -> assertThat(success.toOptional()).contains(1), + error -> fail("Not an error") + ); + } + + @Test + public void JustError_invert_thenError() { + //given + final RuntimeException exception = new RuntimeException(); + final Maybe> justError = Maybe.just(Result.error(exception)); + //when + final Result> result = Result.invert(justError); + //then + result.match( + success -> fail("Not a success"), + error -> assertThat(error).isSameAs(exception) + ); + } + + @Test + public void Nothing_invert_thenSuccessNothing() { + //given + final Maybe> nothing = Maybe.nothing(); + //when + final Result> result = Result.invert(nothing); + //then + result.match( + success -> assertThat(success.toOptional()).isEmpty(), + error -> fail("Not an error") + ); + } + @Test public void useCase_whenOkay_thenReturnSuccess() { //given From 3496fa0972b15dcf96b6b66b4b0037c5711942fb Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Mon, 25 Jun 2018 21:54:55 +0100 Subject: [PATCH 07/11] Add Result.toString() implementations --- src/main/java/net/kemitix/mon/result/Err.java | 5 +++++ .../java/net/kemitix/mon/result/Success.java | 5 +++++ src/test/java/net/kemitix/mon/ResultTest.java | 20 +++++++++++++++++++ 3 files changed, 30 insertions(+) diff --git a/src/main/java/net/kemitix/mon/result/Err.java b/src/main/java/net/kemitix/mon/result/Err.java index b8666a4..0e76126 100644 --- a/src/main/java/net/kemitix/mon/result/Err.java +++ b/src/main/java/net/kemitix/mon/result/Err.java @@ -72,4 +72,9 @@ class Err implements Result { public T orElseThrow() throws Throwable { throw error; } + + @Override + public String toString() { + return String.format("Result.Error{error=%s}", error); + } } diff --git a/src/main/java/net/kemitix/mon/result/Success.java b/src/main/java/net/kemitix/mon/result/Success.java index f10c25d..830b41e 100644 --- a/src/main/java/net/kemitix/mon/result/Success.java +++ b/src/main/java/net/kemitix/mon/result/Success.java @@ -75,4 +75,9 @@ class Success implements Result { public T orElseThrow() { return value; } + + @Override + public String toString() { + return String.format("Result.Success{value=%s}", value); + } } diff --git a/src/test/java/net/kemitix/mon/ResultTest.java b/src/test/java/net/kemitix/mon/ResultTest.java index 7c25651..75ab5d1 100644 --- a/src/test/java/net/kemitix/mon/ResultTest.java +++ b/src/test/java/net/kemitix/mon/ResultTest.java @@ -348,6 +348,26 @@ public class ResultTest implements WithAssertions { ); } + @Test + public void success_toString() { + //given + final Result ok = Result.ok(1); + //when + final String toString = ok.toString(); + //then + assertThat(toString).contains("Result.Success{value=1}"); + } + + @Test + public void err_toString() { + //given + final Result error = Result.error(new RuntimeException("failed")); + //when + final String toString = error.toString(); + //then + assertThat(toString).contains("Result.Error{error=java.lang.RuntimeException: failed}"); + } + @RequiredArgsConstructor private static class UseCase { From 7a150066a91c41dcd506a79531516c0852232bed Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Mon, 25 Jun 2018 21:59:24 +0100 Subject: [PATCH 08/11] Remove Maybe.fromOptional(Optional) --- src/main/java/net/kemitix/mon/maybe/Maybe.java | 17 ----------------- src/test/java/net/kemitix/mon/MaybeTest.java | 9 +-------- 2 files changed, 1 insertion(+), 25 deletions(-) diff --git a/src/main/java/net/kemitix/mon/maybe/Maybe.java b/src/main/java/net/kemitix/mon/maybe/Maybe.java index 51b4584..744068c 100644 --- a/src/main/java/net/kemitix/mon/maybe/Maybe.java +++ b/src/main/java/net/kemitix/mon/maybe/Maybe.java @@ -24,7 +24,6 @@ package net.kemitix.mon.maybe; import lombok.NonNull; import net.kemitix.mon.Functor; -import java.util.Optional; import java.util.function.Function; /** @@ -71,22 +70,6 @@ public interface Maybe extends Functor>, MaybeStream, MaybeOpt return new Just<>(value); } - /** - * Create a Maybe from an {@link Optional}. - * - * @param optional the Optional - * @param the type of the Optional - * @return a Maybe - * @deprecated need to find a better way of converting an Optional to a Maybe, but - * without having to pass the Optional as a parameter - * Try: Optional.of(1).map(Maybe::just).orElse(Maybe::nothing) - */ - @Deprecated - static Maybe fromOptional(final Optional optional) { - return optional.map(Maybe::maybe) - .orElse(nothing()); - } - @Override Maybe map(Function f); } diff --git a/src/test/java/net/kemitix/mon/MaybeTest.java b/src/test/java/net/kemitix/mon/MaybeTest.java index 4f3b31b..12ec238 100644 --- a/src/test/java/net/kemitix/mon/MaybeTest.java +++ b/src/test/java/net/kemitix/mon/MaybeTest.java @@ -10,9 +10,7 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Predicate; import java.util.stream.Stream; -import static net.kemitix.mon.maybe.Maybe.just; -import static net.kemitix.mon.maybe.Maybe.maybe; -import static net.kemitix.mon.maybe.Maybe.nothing; +import static net.kemitix.mon.maybe.Maybe.*; public class MaybeTest implements WithAssertions { @@ -85,15 +83,10 @@ public class MaybeTest implements WithAssertions { @Test public void fromOptional() { - // deprecated methods - assertThat(Maybe.fromOptional(Optional.of(1))).isEqualTo(just(1)); - assertThat(Maybe.fromOptional(Optional.empty())).isEqualTo(nothing()); - // recommended alternative assertThat(Optional.of(1).map(Maybe::just).orElseGet(Maybe::nothing)).isEqualTo(just(1)); assertThat(Optional.empty().map(Maybe::just).orElseGet(Maybe::nothing)).isEqualTo(nothing()); } - @Test public void peek() { final AtomicInteger ref = new AtomicInteger(0); From 2211182c7d050cf84bec1df19b266ee372cab289 Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Mon, 25 Jun 2018 22:04:23 +0100 Subject: [PATCH 09/11] Collapse Maybe{Optional,Stream} into Maybe --- .../java/net/kemitix/mon/maybe/Maybe.java | 63 +++++++++++++++- .../net/kemitix/mon/maybe/MaybeOptional.java | 71 ------------------- .../net/kemitix/mon/maybe/MaybeStream.java | 62 ---------------- 3 files changed, 62 insertions(+), 134 deletions(-) delete mode 100644 src/main/java/net/kemitix/mon/maybe/MaybeOptional.java delete mode 100644 src/main/java/net/kemitix/mon/maybe/MaybeStream.java diff --git a/src/main/java/net/kemitix/mon/maybe/Maybe.java b/src/main/java/net/kemitix/mon/maybe/Maybe.java index 744068c..bcbf64a 100644 --- a/src/main/java/net/kemitix/mon/maybe/Maybe.java +++ b/src/main/java/net/kemitix/mon/maybe/Maybe.java @@ -24,7 +24,12 @@ package net.kemitix.mon.maybe; import lombok.NonNull; import net.kemitix.mon.Functor; +import java.util.Optional; +import java.util.function.Consumer; import java.util.function.Function; +import java.util.function.Predicate; +import java.util.function.Supplier; +import java.util.stream.Stream; /** * A value that may or may not be present. @@ -32,7 +37,8 @@ import java.util.function.Function; * @param the type of the content of the Just * @author Paul Campbell (pcampbell@kemitix.net) */ -public interface Maybe extends Functor>, MaybeStream, MaybeOptional { +@SuppressWarnings("methodcount") +public interface Maybe extends Functor> { /** * Create a Maybe for the value that may or may not be present. @@ -72,4 +78,59 @@ public interface Maybe extends Functor>, MaybeStream, MaybeOpt @Override Maybe map(Function f); + + /** + * Provide a value to use when Maybe is Nothing. + * + * @param supplier supplier for an alternate value + * @return a Maybe + */ + T orElseGet(Supplier supplier); + + /** + * A value to use when Maybe is Nothing. + * + * @param otherValue an alternate value + * @return a Maybe + */ + T orElse(T otherValue); + + /** + * Convert the Maybe to an {@link Optional}. + * + * @return an Optional containing a value for a Just, or empty for a Nothing + */ + Optional toOptional(); + + /** + * Throw the exception if the Maybe is a Nothing. + * + * @param e the exception to throw + * @throws Exception if the Maybe is a Nothing + */ + @SuppressWarnings("illegalthrows") + void orElseThrow(Supplier e) throws Exception; + + /** + * Converts the Maybe into either a single value stream or and empty stream. + * + * @return a Stream containing the value or nothing. + */ + Stream stream(); + + /** + * Filter a Maybe by the predicate, replacing with Nothing when it fails. + * + * @param predicate the test + * @return the Maybe, or Nothing if the test returns false + */ + Maybe filter(Predicate predicate); + + /** + * Provide the value within the Maybe, if it exists, to the Supplier, and returns the Maybe. + * + * @param consumer the Consumer to the value if present + * @return the Maybe + */ + Maybe peek(Consumer consumer); } diff --git a/src/main/java/net/kemitix/mon/maybe/MaybeOptional.java b/src/main/java/net/kemitix/mon/maybe/MaybeOptional.java deleted file mode 100644 index 44f8700..0000000 --- a/src/main/java/net/kemitix/mon/maybe/MaybeOptional.java +++ /dev/null @@ -1,71 +0,0 @@ -/** - * The MIT License (MIT) - * - * Copyright (c) 2017 Paul Campbell - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of this software - * and associated documentation files (the "Software"), to deal in the Software without restriction, - * including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all copies - * or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, - * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE - * AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, - * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -package net.kemitix.mon.maybe; - -import java.util.Optional; -import java.util.function.Supplier; - -/** - * Enables emulation parts of and conversion to the Java Optional class for Maybe. - * - * @param the type of the content of the Just - * - * @author Paul Campbell (pcampbell@kemitix.net) - */ -interface MaybeOptional { - - /** - * Provide a value to use when Maybe is Nothing. - * - * @param supplier supplier for an alternate value - * - * @return a Maybe - */ - T orElseGet(Supplier supplier); - - /** - * A value to use when Maybe is Nothing. - * - * @param otherValue an alternate value - * - * @return a Maybe - */ - T orElse(T otherValue); - - /** - * Convert the Maybe to an {@link Optional}. - * - * @return an Optional containing a value for a Just, or empty for a Nothing - */ - Optional toOptional(); - - /** - * Throw the exception if the Maybe is a Nothing. - * - * @param e the exception to throw - * - * @throws Exception if the Maybe is a Nothing - */ - @SuppressWarnings("illegalthrows") - void orElseThrow(Supplier e) throws Exception; - -} diff --git a/src/main/java/net/kemitix/mon/maybe/MaybeStream.java b/src/main/java/net/kemitix/mon/maybe/MaybeStream.java deleted file mode 100644 index 4036ee8..0000000 --- a/src/main/java/net/kemitix/mon/maybe/MaybeStream.java +++ /dev/null @@ -1,62 +0,0 @@ -/** - * The MIT License (MIT) - * - * Copyright (c) 2017 Paul Campbell - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of this software - * and associated documentation files (the "Software"), to deal in the Software without restriction, - * including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all copies - * or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, - * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE - * AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, - * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -package net.kemitix.mon.maybe; - -import java.util.function.Consumer; -import java.util.function.Predicate; -import java.util.stream.Stream; - -/** - * Enables emulation parts of and conversion to the Java Stream class for Maybe. - * - * @param the type of the content of the Just - * - * @author Paul Campbell (pcampbell@kemitix.net) - */ -interface MaybeStream { - - /** - * Converts the Maybe into either a single value stream or and empty stream. - * - * @return a Stream containing the value or nothing. - */ - Stream stream(); - - /** - * Filter a Maybe by the predicate, replacing with Nothing when it fails. - * - * @param predicate the test - * - * @return the Maybe, or Nothing if the test returns false - */ - Maybe filter(Predicate predicate); - - /** - * Provide the value within the Maybe, if it exists, to the Supplier, and returns the Maybe. - * - * @param consumer the Consumer to the value if present - * - * @return the Maybe - */ - Maybe peek(Consumer consumer); - -} From 5d39be36ff72bc8efd013acea06eacbe61a7ea99 Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Mon, 25 Jun 2018 22:35:31 +0100 Subject: [PATCH 10/11] Maybe satisfies the three Monad Laws --- README.md | 2 + src/main/java/net/kemitix/mon/maybe/Just.java | 5 +++ .../java/net/kemitix/mon/maybe/Maybe.java | 9 ++++ .../java/net/kemitix/mon/maybe/Nothing.java | 5 +++ .../java/net/kemitix/mon/MaybeMonadTest.java | 39 +++++++++++++++++ src/test/java/net/kemitix/mon/MaybeTest.java | 43 +++++++++++++++++++ 6 files changed, 103 insertions(+) create mode 100644 src/test/java/net/kemitix/mon/MaybeMonadTest.java diff --git a/README.md b/README.md index 5036111..337b0b2 100644 --- a/README.md +++ b/README.md @@ -55,6 +55,8 @@ class Example { ### Maybe +A Monad. + A non-final substitute for Optional with `peek()` and `stream()` methods. ```java diff --git a/src/main/java/net/kemitix/mon/maybe/Just.java b/src/main/java/net/kemitix/mon/maybe/Just.java index 449e523..f801bde 100644 --- a/src/main/java/net/kemitix/mon/maybe/Just.java +++ b/src/main/java/net/kemitix/mon/maybe/Just.java @@ -43,6 +43,11 @@ final class Just implements Maybe { private final T value; + @Override + public Maybe flatMap(Function> f) { + return f.apply(value); + } + @Override public Maybe map(final Function f) { return new Just<>(f.apply(value)); diff --git a/src/main/java/net/kemitix/mon/maybe/Maybe.java b/src/main/java/net/kemitix/mon/maybe/Maybe.java index bcbf64a..0f87840 100644 --- a/src/main/java/net/kemitix/mon/maybe/Maybe.java +++ b/src/main/java/net/kemitix/mon/maybe/Maybe.java @@ -76,6 +76,15 @@ public interface Maybe extends Functor> { return new Just<>(value); } + /** + * Monad binder maps the Maybe into another Maybe using the binder method f. + * + * @param f the mapper function + * @param the type of the value in the final maybe + * @return a Maybe with the mapped value + */ + Maybe flatMap(Function> f); + @Override Maybe map(Function f); diff --git a/src/main/java/net/kemitix/mon/maybe/Nothing.java b/src/main/java/net/kemitix/mon/maybe/Nothing.java index c90beb5..7a72d46 100644 --- a/src/main/java/net/kemitix/mon/maybe/Nothing.java +++ b/src/main/java/net/kemitix/mon/maybe/Nothing.java @@ -38,6 +38,11 @@ final class Nothing implements Maybe { static final Maybe INSTANCE = new Nothing<>(); + @Override + public Maybe flatMap(Function> f) { + return Maybe.nothing(); + } + @Override @SuppressWarnings("unchecked") public Maybe map(final Function f) { diff --git a/src/test/java/net/kemitix/mon/MaybeMonadTest.java b/src/test/java/net/kemitix/mon/MaybeMonadTest.java new file mode 100644 index 0000000..28e9e8b --- /dev/null +++ b/src/test/java/net/kemitix/mon/MaybeMonadTest.java @@ -0,0 +1,39 @@ +package net.kemitix.mon; + +import net.kemitix.mon.maybe.Maybe; +import org.assertj.core.api.WithAssertions; +import org.junit.Test; + +import java.util.function.Function; + +public class MaybeMonadTest implements WithAssertions { + + @Test + public void leftIdentity() { + //given + final int value = 1; + final Maybe maybe = Maybe.maybe(value); + final Function> f = i -> Maybe.maybe(i * 2); + //then + assertThat(maybe.flatMap(f)).isEqualTo(f.apply(value)); + } + + @Test + public void rightIdentity() { + //given + final Maybe maybe = Maybe.maybe(1); + //then + assertThat(maybe.flatMap(Maybe::maybe)).isEqualTo(maybe); + } + + @Test + public void associativity() { + //given + final Maybe maybe = Maybe.maybe(1); + final Function> f = i -> Maybe.maybe(i * 2); + final Function> g = i -> Maybe.maybe(i + 6); + //then + assertThat(maybe.flatMap(f).flatMap(g)).isEqualTo(maybe.flatMap(x -> f.apply(x).flatMap(g))); + } + +} diff --git a/src/test/java/net/kemitix/mon/MaybeTest.java b/src/test/java/net/kemitix/mon/MaybeTest.java index 12ec238..61941ee 100644 --- a/src/test/java/net/kemitix/mon/MaybeTest.java +++ b/src/test/java/net/kemitix/mon/MaybeTest.java @@ -123,4 +123,47 @@ public class MaybeTest implements WithAssertions { //then assertThat(stream).isEmpty(); } + + @Test + public void justFlatMap() { + //given + final Maybe just1 = Maybe.just(1); + final Maybe just2 = Maybe.just(2); + //when + final Maybe result = just1.flatMap(v1 -> + just2.flatMap(v2 -> + Maybe.maybe(v1 + v2) + )); + //then + assertThat(result.toOptional()).contains(3); + } + + @Test + public void nothingFlatMap() { + //given + final Maybe nothing1 = Maybe.nothing(); + final Maybe nothing2 = Maybe.nothing(); + //when + final Maybe result = nothing1.flatMap(v1 -> + nothing2.flatMap(v2 -> + Maybe.maybe(v1 + v2) + )); + //then + assertThat(result.toOptional()).isEmpty(); + } + + @Test + public void justNothingFlatMap() { + //given + final Maybe just1 = Maybe.just(1); + final Maybe nothing2 = Maybe.nothing(); + //when + final Maybe result = just1.flatMap(v1 -> + nothing2.flatMap(v2 -> + Maybe.maybe(v1 + v2) + )); + //then + assertThat(result.toOptional()).isEmpty(); + } + } From 093e35b4c18f92feac9f0ffabf9a3c3e0f83eb76 Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Mon, 25 Jun 2018 22:39:25 +0100 Subject: [PATCH 11/11] Result satisfies the three Monad Laws --- README.md | 2 + .../java/net/kemitix/mon/result/Success.java | 11 ++++++ .../java/net/kemitix/mon/ResultMonadTest.java | 39 +++++++++++++++++++ 3 files changed, 52 insertions(+) create mode 100644 src/test/java/net/kemitix/mon/ResultMonadTest.java diff --git a/README.md b/README.md index 337b0b2..45c4ec4 100644 --- a/README.md +++ b/README.md @@ -101,6 +101,8 @@ class Test { ### Result +A Monad. + A container for method return values that may raise an Exception. Useful for when a checked exceptions can't be added to the method signature. diff --git a/src/main/java/net/kemitix/mon/result/Success.java b/src/main/java/net/kemitix/mon/result/Success.java index 830b41e..9093b08 100644 --- a/src/main/java/net/kemitix/mon/result/Success.java +++ b/src/main/java/net/kemitix/mon/result/Success.java @@ -24,6 +24,7 @@ package net.kemitix.mon.result; import lombok.RequiredArgsConstructor; import net.kemitix.mon.maybe.Maybe; +import java.util.Objects; import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Predicate; @@ -76,6 +77,16 @@ class Success implements Result { return value; } + @Override + public boolean equals(Object other) { + return other instanceof Success && Objects.equals(value, ((Success) other).value); + } + + @Override + public int hashCode() { + return Objects.hash(value); + } + @Override public String toString() { return String.format("Result.Success{value=%s}", value); diff --git a/src/test/java/net/kemitix/mon/ResultMonadTest.java b/src/test/java/net/kemitix/mon/ResultMonadTest.java new file mode 100644 index 0000000..8a8470f --- /dev/null +++ b/src/test/java/net/kemitix/mon/ResultMonadTest.java @@ -0,0 +1,39 @@ +package net.kemitix.mon; + +import net.kemitix.mon.result.Result; +import org.assertj.core.api.WithAssertions; +import org.junit.Test; + +import java.util.function.Function; + +public class ResultMonadTest implements WithAssertions { + + @Test + public void leftIdentity() { + //given + final int value = 1; + final Result result = Result.ok(value); + final Function> f = i -> Result.ok(i * 2); + //then + assertThat(result.flatMap(f)).isEqualTo(f.apply(value)); + } + + @Test + public void rightIdentity() { + //given + final Result result = Result.ok(1); + //then + assertThat(result.flatMap(Result::ok)).isEqualTo(result); + } + + @Test + public void associativity() { + //given + final Result result = Result.ok(1); + final Function> f = i -> Result.ok(i * 2); + final Function> g = i -> Result.ok(i + 6); + //then + assertThat(result.flatMap(f).flatMap(g)).isEqualTo(result.flatMap(x -> f.apply(x).flatMap(g))); + } + +}