From e5958ba4328575ac1e76dfe85ea0204d8f1f378a Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Mon, 16 Jul 2018 18:49:44 +0100 Subject: [PATCH] Add `Either` (experimental) --- CHANGELOG | 2 + .../mon/experimental/either/Either.java | 99 +++++++++++++++ .../kemitix/mon/experimental/either/Left.java | 61 ++++++++++ .../mon/experimental/either/Right.java | 61 ++++++++++ .../mon/experimental/either/package-info.java | 22 ++++ src/test/java/net/kemitix/mon/EitherTest.java | 114 ++++++++++++++++++ 6 files changed, 359 insertions(+) create mode 100644 src/main/java/net/kemitix/mon/experimental/either/Either.java create mode 100644 src/main/java/net/kemitix/mon/experimental/either/Left.java create mode 100644 src/main/java/net/kemitix/mon/experimental/either/Right.java create mode 100644 src/main/java/net/kemitix/mon/experimental/either/package-info.java create mode 100644 src/test/java/net/kemitix/mon/EitherTest.java diff --git a/CHANGELOG b/CHANGELOG index 990a1c9..7af2501 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -2,6 +2,7 @@ CHANGELOG ========= 0.11.0 +------ * Rename `Result.maybeThen()` as `Result.flatMapMaybe()` * Add `Maybe.match(Consumer,Runnable)` @@ -9,6 +10,7 @@ CHANGELOG * Add `Maybe.isNothing()` * BUG: `Maybe.orElseThrow()` now returns value when a Just * Rewrite README +* Add `Either` (experimental) 0.10.0 ------ diff --git a/src/main/java/net/kemitix/mon/experimental/either/Either.java b/src/main/java/net/kemitix/mon/experimental/either/Either.java new file mode 100644 index 0000000..90aa272 --- /dev/null +++ b/src/main/java/net/kemitix/mon/experimental/either/Either.java @@ -0,0 +1,99 @@ +/** + * 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.experimental.either; + +import java.util.function.Consumer; +import java.util.function.Function; + +/** + * An Either type for holding a one of two possible values, a left and a right, that may be of different types. + * + * @param the type of the Either for left value + * @param the type of the Either for right value + * @author Paul Campbell (pcampbell@kemitix.net) + */ +public interface Either { + + /** + * Create a new Either holding a left value. + * + * @param l the left value + * @param the type of the left value + * @param the type of the right value + * @return a Either holding the left value + */ + static Either left(final L l) { + return new Left<>(l); + } + + /** + * Create a new Either holding a right value. + * + * @param r the right value + * @param the type of the left value + * @param the type of the right value + * @return a Either holding the right value + */ + static Either right(final R r) { + return new Right<>(r); + } + + /** + * Checks if the Either holds a left value. + * + * @return true if this Either is a left + */ + boolean isLeft(); + + /** + * Checks if the Either holds a right value. + * + * @return true if this Either is a right + */ + boolean isRight(); + + /** + * Matches the Either, invoking the correct Consumer. + * + * @param onLeft the Consumer to invoke when the Either is a left + * @param onRight the Consumer to invoke when the Either is a right + */ + void match(Consumer onLeft, Consumer onRight); + + /** + * Map 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 + */ + Either mapLeft(Function f); + + /** + * Map 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 + */ + Either mapRight(Function f); +} diff --git a/src/main/java/net/kemitix/mon/experimental/either/Left.java b/src/main/java/net/kemitix/mon/experimental/either/Left.java new file mode 100644 index 0000000..5de5904 --- /dev/null +++ b/src/main/java/net/kemitix/mon/experimental/either/Left.java @@ -0,0 +1,61 @@ +/** + * 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.experimental.either; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +import java.util.function.Consumer; +import java.util.function.Function; + +/** + * An Either type holding a left value. + * + * @param the type of the Either for left value + * @param the type of the Either for right value + * @author Paul Campbell (pcampbell@kemitix.net) + */ +@RequiredArgsConstructor +class Left implements Either { + + @Getter + private final boolean left = true; + @Getter + private final boolean right = false; + + private final L value; + + @Override + public void match(final Consumer onLeft, final Consumer onRight) { + onLeft.accept(value); + } + + @Override + public Either mapLeft(final Function f) { + return new Left<>(f.apply(value)); + } + + @Override + public Either mapRight(final Function f) { + return new Left<>(value); + } +} diff --git a/src/main/java/net/kemitix/mon/experimental/either/Right.java b/src/main/java/net/kemitix/mon/experimental/either/Right.java new file mode 100644 index 0000000..35f1baf --- /dev/null +++ b/src/main/java/net/kemitix/mon/experimental/either/Right.java @@ -0,0 +1,61 @@ +/** + * 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.experimental.either; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +import java.util.function.Consumer; +import java.util.function.Function; + +/** + * An Either type holding a right value. + * + * @param the type of the Either for left value + * @param the type of the Either for right value + * @author Paul Campbell (pcampbell@kemitix.net) + */ +@RequiredArgsConstructor +class Right implements Either { + + @Getter + private final boolean left = false; + @Getter + private final boolean right = true; + + private final R value; + + @Override + public void match(final Consumer onLeft, final Consumer onRight) { + onRight.accept(value); + } + + @Override + public Either mapLeft(final Function f) { + return new Right<>(value); + } + + @Override + public Either mapRight(final Function f) { + return new Right<>(f.apply(value)); + } +} diff --git a/src/main/java/net/kemitix/mon/experimental/either/package-info.java b/src/main/java/net/kemitix/mon/experimental/either/package-info.java new file mode 100644 index 0000000..5521666 --- /dev/null +++ b/src/main/java/net/kemitix/mon/experimental/either/package-info.java @@ -0,0 +1,22 @@ +/** + * 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.experimental.either; diff --git a/src/test/java/net/kemitix/mon/EitherTest.java b/src/test/java/net/kemitix/mon/EitherTest.java new file mode 100644 index 0000000..7508b39 --- /dev/null +++ b/src/test/java/net/kemitix/mon/EitherTest.java @@ -0,0 +1,114 @@ +package net.kemitix.mon; + +import net.kemitix.mon.experimental.either.Either; +import org.assertj.core.api.WithAssertions; +import org.junit.Test; + +public class EitherTest implements WithAssertions { + + @Test + public void whenLeft_isLeft() { + //when + final Either either = Either.left(1); + //then + assertThat(either.isLeft()).isTrue(); + } + + @Test + public void whenLeft_isNotRight() { + //when + final Either either = Either.left(1); + //then + assertThat(either.isRight()).isFalse(); + } + + @Test + public void whenRight_isNotLeft() { + //when + final Either either = Either.right("1"); + //then + assertThat(either.isLeft()).isFalse(); + } + + @Test + public void whenRight_isRight() { + //when + final Either either = Either.right("1"); + //then + assertThat(either.isRight()).isTrue(); + } + + @Test + public void whenLeft_matchLeft() { + //given + final Either either = Either.left(1); + //then + either.match( + left -> assertThat(left).isEqualTo(1), + right -> fail("Not a right") + ); + } + + @Test + public void whenRight_matchRight() { + //given + final Either either = Either.right("1"); + //then + either.match( + left -> fail("Not a left"), + right -> assertThat(right).isEqualTo("1") + ); + } + + @Test + public 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 + public 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 + public 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 + public 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") + ); + } +}