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(); + } + }