From 30f903dbb29ab96b8ff8009cabfe67e09219aaa9 Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Sat, 20 Jun 2020 15:03:52 +0100 Subject: [PATCH] Add flatMapLeft/Right and getLeft/Right to Either (#128) * clean up code * tree: replace deprecated assertj methods * either: add flatMapLeft and flatMapRight * either: add getLeft and getRight --- README.org | 41 ++ .../mon/experimental/either/Either.java | 36 ++ .../kemitix/mon/experimental/either/Left.java | 22 + .../mon/experimental/either/Right.java | 23 + src/test/java/net/kemitix/mon/EitherTest.java | 473 ++++++++++++++---- .../net/kemitix/mon/tree/MutableTreeTest.java | 2 +- .../net/kemitix/mon/tree/TreeBuilderTest.java | 4 +- 7 files changed, 503 insertions(+), 98 deletions(-) diff --git a/README.org b/README.org index 94e336d..a6a3bdb 100644 --- a/README.org +++ b/README.org @@ -1184,3 +1184,44 @@ #+END_SRC +*** = Either flatMapLeft(Function> f)= + + FlatMap the function across the left value. + + #+being_src java + Either either = Either.left(2); + Either resultLeft = either.flatMapLeft(l -> Either.left(l * 2)); + Either resultRight = either.flatMapLeft(l -> Either.right(l * 2)); + #+end_src + + +*** = Either flatMapRight(Function> f)= + + FlatMap the function across the right value. + + #+being_src java + Either either = Either.right("2"); + Either resultLeft = either.flatMapRight(l -> Either.left(l * 2)); + Either resultRight = either.flatMapRight(l -> Either.right(l * 2)); + #+end_src + + +*** =Optional getLeft()= + + Returns an Optional containing the left value, if is a left, otherwise returns an empty Optional. + + #+being_src java + Either either = Either.right("2"); + Optional left = either.getLeft(); + #+end_src + + +*** =Optional getRight()= + + Returns an Optional containing the right value, if is a right, otherwise returns an empty Optional. + + #+being_src java + Either either = Either.right("2"); + Optional right = either.getRight(); + #+end_src + diff --git a/src/main/java/net/kemitix/mon/experimental/either/Either.java b/src/main/java/net/kemitix/mon/experimental/either/Either.java index 92ed29f..216fd1e 100644 --- a/src/main/java/net/kemitix/mon/experimental/either/Either.java +++ b/src/main/java/net/kemitix/mon/experimental/either/Either.java @@ -21,6 +21,7 @@ package net.kemitix.mon.experimental.either; +import java.util.Optional; import java.util.function.Consumer; import java.util.function.Function; @@ -31,6 +32,7 @@ import java.util.function.Function; * @param the type of the Either for right value * @author Paul Campbell (pcampbell@kemitix.net) */ +@SuppressWarnings("methodcount") public interface Either { /** @@ -96,4 +98,38 @@ public interface Either { * @return a new Either */ public abstract Either mapRight(Function f); + + /** + * FlatMap the function across the left value. + * + * @param f the function to apply to any left value + * @param the type to change the left value to + * @return a new Either if is a Left, else this + */ + public abstract Either flatMapLeft(Function> f); + + /** + * FlatMap the function across the right value. + * + * @param f the function to apply to any right value + * @param the type to change the right value to + * @return a new Either if is a Right, else this + */ + public abstract Either flatMapRight(Function> f); + + /** + * Returns an Optional containing the left value, if is a left, otherwise + * returns an empty Optional. + * + * @return An Optional containing any left value + */ + public abstract Optional getLeft(); + + /** + * Returns an Optional containing the right value, if is a right, otherwise + * returns an empty Optional. + * + * @return An Optional containing any right value + */ + public abstract Optional getRight(); } diff --git a/src/main/java/net/kemitix/mon/experimental/either/Left.java b/src/main/java/net/kemitix/mon/experimental/either/Left.java index 7b13fe2..7946e80 100644 --- a/src/main/java/net/kemitix/mon/experimental/either/Left.java +++ b/src/main/java/net/kemitix/mon/experimental/either/Left.java @@ -23,6 +23,7 @@ package net.kemitix.mon.experimental.either; import lombok.RequiredArgsConstructor; +import java.util.Optional; import java.util.function.Consumer; import java.util.function.Function; @@ -62,4 +63,25 @@ class Left implements Either { public Either mapRight(final Function f) { return new Left<>(value); } + + @Override + public Either flatMapLeft(final Function> f) { + return f.apply(value); + } + + @Override + @SuppressWarnings("unchecked") + public Either flatMapRight(final Function> f) { + return (Either) this; + } + + @Override + public Optional getLeft() { + return Optional.ofNullable(value); + } + + @Override + public Optional getRight() { + return Optional.empty(); + } } diff --git a/src/main/java/net/kemitix/mon/experimental/either/Right.java b/src/main/java/net/kemitix/mon/experimental/either/Right.java index aeadb10..bdffa7d 100644 --- a/src/main/java/net/kemitix/mon/experimental/either/Right.java +++ b/src/main/java/net/kemitix/mon/experimental/either/Right.java @@ -23,6 +23,7 @@ package net.kemitix.mon.experimental.either; import lombok.RequiredArgsConstructor; +import java.util.Optional; import java.util.function.Consumer; import java.util.function.Function; @@ -62,4 +63,26 @@ class Right implements Either { public Either mapRight(final Function f) { return new Right<>(f.apply(value)); } + + @Override + @SuppressWarnings("unchecked") + public Either flatMapLeft(final Function> f) { + return (Either) this; + } + + @Override + public Either flatMapRight(final Function> f) { + return f.apply(value); + } + + @Override + public Optional getLeft() { + return Optional.empty(); + } + + @Override + public Optional getRight() { + return Optional.ofNullable(value); + } + } diff --git a/src/test/java/net/kemitix/mon/EitherTest.java b/src/test/java/net/kemitix/mon/EitherTest.java index 7fd835f..a67b531 100644 --- a/src/test/java/net/kemitix/mon/EitherTest.java +++ b/src/test/java/net/kemitix/mon/EitherTest.java @@ -2,113 +2,396 @@ package net.kemitix.mon; import net.kemitix.mon.experimental.either.Either; import org.assertj.core.api.WithAssertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; +import java.time.Instant; +import java.util.Optional; +import java.util.function.BiFunction; + class EitherTest implements WithAssertions { - - @Test - void whenLeft_isLeft() { - //when - final Either either = Either.left(1); - //then - assertThat(either.isLeft()).isTrue(); + @Nested + @DisplayName("isLeft()") + public class IsLeft { + @Test + void whenLeft_isLeft() { + //when + final Either either = Either.left(1); + //then + assertThat(either.isLeft()).isTrue(); + } + @Test + void whenRight_isNotLeft() { + //when + final Either either = Either.right("1"); + //then + assertThat(either.isLeft()).isFalse(); + } } - - @Test - void whenLeft_isNotRight() { - //when - final Either either = Either.left(1); - //then - assertThat(either.isRight()).isFalse(); + @Nested + @DisplayName("isRight()") + public class IsRight { + @Test + void whenLeft_isNotRight() { + //when + final Either either = Either.left(1); + //then + assertThat(either.isRight()).isFalse(); + } + @Test + void whenRight_isRight() { + //when + final Either either = Either.right("1"); + //then + assertThat(either.isRight()).isTrue(); + } } - - @Test - void whenRight_isNotLeft() { - //when - final Either either = Either.right("1"); - //then - assertThat(either.isLeft()).isFalse(); + @Nested + @DisplayName("match()") + public class Match { + @Test + void whenLeft_matchLeft() { + //given + final Either either = Either.left(1); + //then + either.match( + left -> assertThat(left).isEqualTo(1), + right -> fail("Not a right") + ); + } + @Test + void whenRight_matchRight() { + //given + final Either either = Either.right("1"); + //then + either.match( + left -> fail("Not a left"), + right -> assertThat(right).isEqualTo("1") + ); + } } - - @Test - void whenRight_isRight() { - //when - final Either either = Either.right("1"); - //then - assertThat(either.isRight()).isTrue(); + @Nested + @DisplayName("mapLeft()") + public class MapLeft { + @Test + void givenLeft_whenMapLeft_thenMap() { + //given + final Either either = Either.left(2); + //when + final Either result = either.mapLeft(l -> l * 2); + //then + result.match( + left -> assertThat(left).isEqualTo(4), + right -> fail("Not a right") + ); + } + @Test + void givenRight_whenMapLeft_thenDoNothing() { + //given + final Either either = Either.right("2"); + //when + final Either result = either.mapLeft(l -> l * 2); + //then + result.match( + left -> fail("Not a left"), + right -> assertThat(right).isEqualTo("2") + ); + } } - - @Test - void whenLeft_matchLeft() { - //given - final Either either = Either.left(1); - //then - either.match( - left -> assertThat(left).isEqualTo(1), - right -> fail("Not a right") - ); + @Nested + @DisplayName("mapRight()") + public class MapRight { + @Test + void givenRight_whenMapRight_thenMap() { + //given + final Either either = Either.right("2"); + //when + final Either result = either.mapRight(l -> l + "2"); + //then + result.match( + left -> fail("Not a left"), + right -> assertThat(right).isEqualTo("22") + ); + } + @Test + void givenLeft_whenMapRight_thenDoNothing() { + //given + final Either either = Either.left(2); + //when + final Either result = either.mapRight(r -> r + "x"); + //then + result.match( + left -> assertThat(left).isEqualTo(2), + right -> fail("Not a right") + ); + } } - - @Test - void whenRight_matchRight() { - //given - final Either either = Either.right("1"); - //then - either.match( - left -> fail("Not a left"), - right -> assertThat(right).isEqualTo("1") - ); + @Nested + @DisplayName("flatMapLeft()") + public class FlatMapLeft { + @Test + void givenLeft_whenFlatMapLeft_whenResultLeft_thenIsNewLeft() { + //given + Either either = Either.left(2); + //when + Either result = either.flatMapLeft(l -> Either.left(l * 2)); + //then + result.match( + left -> assertThat(left).isEqualTo(4), + right -> fail("Not a right") + ); + } + @Test + void givenLeft_whenFlatMapLeft_whenResultRight_thenIsRight() { + //given + Either either = Either.left(2); + //when + Either result = either.flatMapLeft(l -> Either.right("recovered")); + //then + result.match( + left -> fail("Not a left"), + right -> assertThat(right).isEqualTo("recovered") + ); + } + @Test + void givenRight_whenFlatMapLeft_thenDoNothing() { + //given + Either either = Either.right("2"); + //when + Either result = either.flatMapLeft(l -> Either.left(l * 2)); + //then + result.match( + left -> fail("Not a left"), + right -> assertThat(right).isEqualTo("2") + ); + } } - - @Test - void givenLeft_whenMapLeft_thenMap() { - //given - final Either either = Either.left(2); - //when - final Either result = either.mapLeft(l -> l * 2); - //then - result.match( - left -> assertThat(left).isEqualTo(4), - right -> fail("Not a right") - ); + @Nested + @DisplayName("flatMapRight()") + public class FlatMapRight { + @Test + void givenLeft_whenFlatMapRight_thenDoNothing() { + //given + Either either = Either.left(2); + //when + Either result = either.flatMapRight(l -> Either.right(l + "2")); + //then + result.match( + left -> assertThat(left).isEqualTo(2), + right -> fail("Not a right") + ); + } + @Test + void givenRight_whenFlatMapRight_whenResultRight_thenIsNewRight() { + //given + Either either = Either.right("2"); + //when + Either result = either.flatMapRight(l -> Either.right(l + "2")); + //then + result.match( + left -> fail("Not a left"), + right -> assertThat(right).isEqualTo("22") + ); + } + @Test + void givenRight_whenFlatMapRight_whenResultLeft_thenIsLeft() { + //given + Either either = Either.right("2"); + //when + Either result = either.flatMapRight(l -> Either.left(7)); + //then + result.match( + left -> assertThat(left).isEqualTo(7), + right -> fail("Not a right") + ); + } } + @Nested @DisplayName("getLeft") public class GetLeft { + @Test + @DisplayName("") + public void whenLeft_thenGetValue() { + //given + Either either = Either.left("value"); + //when + Optional left = either.getLeft(); + //then + assertThat(left).contains("value"); + } - @Test - void givenRight_whenMapRight_thenMap() { - //given - final Either either = Either.right("2"); - //when - final Either result = either.mapRight(l -> l + "2"); - //then - result.match( - left -> fail("Not a left"), - right -> assertThat(right).isEqualTo("22") - ); + @Test + @DisplayName("") + public void whenRight_thenGetEmpty() { + //given + Either either = Either.right("value"); + //when + Optional left = either.getLeft(); + //then + assertThat(left).isEmpty(); + } } + @Nested @DisplayName("getRight") public class GetRight { + @Test + @DisplayName("") + public void whenLeft_thenGetEmpty() { + //given + Either either = Either.left("value"); + //when + Optional left = either.getRight(); + //then + assertThat(left).isEmpty(); + } - @Test - void givenLeft_whenMapRight_thenDoNothing() { - //given - final Either either = Either.left(2); - //when - final Either result = either.mapRight(r -> r + "x"); - //then - result.match( - left -> assertThat(left).isEqualTo(2), - right -> fail("Not a right") - ); - } - - @Test - void givenRight_whenMapLeft_thenDoNothing() { - //given - final Either either = Either.right("2"); - //when - final Either result = either.mapLeft(l -> l * 2); - //then - result.match( - left -> fail("Not a left"), - right -> assertThat(right).isEqualTo("2") - ); + @Test + @DisplayName("") + public void whenRight_thenGetValue() { + //given + Either either = Either.right("value"); + //when + Optional left = either.getRight(); + //then + assertThat(left).contains("value"); + } } +// @Nested +// @DisplayName("compose()") +// public class Compose { +// +// BiFunction> leftToLeftComposer = +// (la, lb) -> Either.left(la + lb); +// +// BiFunction> leftToRightComposer = +// (la, lb) -> Either.right(Integer.parseInt(la) + Integer.parseInt(lb)); +// +// BiFunction> rightToLeftComposer = +// (la, lb) -> Either.left("" + la + lb); +// +// BiFunction> rightToRightComposer = +// (la, lb) -> Either.right(la + lb); +// +// @Test +// public void givenRightAndRight_whenComposeRight_then() { +// //given +// Either ea = Either.right(3); +// Either eb = Either.right(7); +// //when +// Either result = ea.compose(eb, +// leftToRightComposer, +// rightToRightComposer); +// //then +// result.match( +// left -> fail("Not a left"), +// right -> assertThat(right).isEqualTo(10) +// ); +// } +// +// @Test +// public void givenRightAndRight_whenComposeLeft_then() { +// //given +// Either ea = Either.right(3); +// Either eb = Either.right(7); +// //when +// Either result = ea.compose(eb, +// leftToLeftComposer, +// rightToLeftComposer); +// //then +// result.match( +// left -> assertThat(left).isEqualTo("a+b"), +// right -> fail("Not a right") +// ); +// } +// // compose left with left +// @Test +// public void givenLeftAndLeft_whenComposeRight_then() { +// //given +// Either ea = Either.left("3"); +// Either eb = Either.left("7"); +// //when +// Either result = ea.compose(eb, +// leftToRightComposer, +// rightToRightComposer); +// //then +// result.match( +// left -> fail("Not a left"), +// right -> assertThat(right).isEqualTo(10) +// ); +// } +// @Test +// public void givenLeftAndLeft_whenComposeLeft_then() { +// //given +// Either ea = Either.left("3"); +// Either eb = Either.left("7"); +// //when +// Either result = ea.compose(eb, +// leftToLeftComposer, +// rightToLeftComposer); +// //then +// result.match( +// left -> fail("Not a left"), +// right -> assertThat(right).isEqualTo(10) +// ); +// } +// // compose left with right +// @Test +// public void givenLeftAndRight_whenComposeRight_then() { +// //given +// Either ea = Either.left("3"); +// Either eb = Either.right(7); +// //when +// Either result = ea.compose(eb, +// leftToRightComposer, +// rightToRightComposer); +// //then +// result.match( +// left -> fail("Not a left"), +// right -> assertThat(right).isEqualTo(10) +// ); +// } +// @Test +// public void givenLeftAndRight_whenComposeLeft_then() { +// //given +// Either ea = Either.left("3"); +// Either eb = Either.right(7); +// //when +// Either result = ea.compose(eb, +// leftToLeftComposer, +// rightToLeftComposer); +// //then +// result.match( +// left -> fail("Not a left"), +// right -> assertThat(right).isEqualTo(10) +// ); +// } +// // compose right with left +// @Test +// public void givenRightAndLeft_whenComposeRight_then() { +// //given +// Either ea = Either.right(3); +// Either eb = Either.left("7"); +// //when +// Either result = ea.compose(eb, +// leftToRightComposer, +// rightToRightComposer); +// //then +// result.match( +// left -> fail("Not a left"), +// right -> assertThat(right).isEqualTo(10) +// ); +// } +// @Test +// public void givenRightAndLeft_whenComposeLeft_then() { +// //given +// Either ea = Either.right(3); +// Either eb = Either.left("7"); +// //when +// Either result = ea.compose(eb, +// leftToLeftComposer, +// rightToLeftComposer); +// //then +// result.match( +// left -> fail("Not a left"), +// right -> assertThat(right).isEqualTo(10) +// ); +// } +// } } diff --git a/src/test/java/net/kemitix/mon/tree/MutableTreeTest.java b/src/test/java/net/kemitix/mon/tree/MutableTreeTest.java index 2092321..e4ff76e 100644 --- a/src/test/java/net/kemitix/mon/tree/MutableTreeTest.java +++ b/src/test/java/net/kemitix/mon/tree/MutableTreeTest.java @@ -86,7 +86,7 @@ class MutableTreeTest implements WithAssertions { asList( MutableTree.leaf(sid2), MutableTree.leaf(sid3))); - assertThat(result).isEqualToComparingFieldByFieldRecursively(expectedTree); + assertThat(result).usingRecursiveComparison().isEqualTo(expectedTree); } @Test diff --git a/src/test/java/net/kemitix/mon/tree/TreeBuilderTest.java b/src/test/java/net/kemitix/mon/tree/TreeBuilderTest.java index 4a2a89a..bb64e5f 100644 --- a/src/test/java/net/kemitix/mon/tree/TreeBuilderTest.java +++ b/src/test/java/net/kemitix/mon/tree/TreeBuilderTest.java @@ -65,7 +65,7 @@ class TreeBuilderTest { final Tree result = rootBuilder.build(); //then assertThat(result.count()).isEqualTo(3); - assertThat(result).isEqualToComparingFieldByFieldRecursively( + assertThat(result).usingRecursiveComparison().isEqualTo( MutableTree.of(rootNode, Collections.singleton( MutableTree.of(childNode, Collections.singleton( MutableTree.leaf(grandchildNode)))))); @@ -86,7 +86,7 @@ class TreeBuilderTest { .build(); //then assertThat(result.count()).isEqualTo(4); - assertThat(result).isEqualToComparingFieldByFieldRecursively( + assertThat(result).usingRecursiveComparison().isEqualTo( MutableTree.of(rootNode, asList( MutableTree.leaf(child1Node), MutableTree.leaf(child2Node),