From e5958ba4328575ac1e76dfe85ea0204d8f1f378a Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Mon, 16 Jul 2018 18:49:44 +0100 Subject: [PATCH 1/6] 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") + ); + } +} From c7c7c6ebeb62741acf954df2027469e78f686834 Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Mon, 16 Jul 2018 18:52:33 +0100 Subject: [PATCH 2/6] README.org: adjust headings --- README.org | 318 ++++++++++++++++++++++++++--------------------------- 1 file changed, 157 insertions(+), 161 deletions(-) diff --git a/README.org b/README.org index 411f3e0..c882f76 100644 --- a/README.org +++ b/README.org @@ -39,150 +39,146 @@ The latest version should be shown above with the nexus and maven-central badges or can be found on [[https://search.maven.org/#search%7Cga%7C1%7Cg%3A%22net.kemitix%22%20AND%20a%3A%22mon%22][Maven Central]]. -** Usage + +** TypeAlias :PROPERTIES: - :CUSTOM_ID: usage + :CUSTOM_ID: typealias :END: -*** TypeAlias - :PROPERTIES: - :CUSTOM_ID: typealias - :END: + In Haskell it is possible to create an alias for a Type, and to then use + that alias with the same behaviour as the original, except that the compiler + doesn't treat the alias as the same Type and will generate compiler errors + if you try and use them together. e.g.: - In Haskell it is possible to create an alias for a Type, and to then use - that alias with the same behaviour as the original, except that the compiler - doesn't treat the alias as the same Type and will generate compiler errors - if you try and use them together. e.g.: + #+BEGIN_SRC haskell + type PhoneNumber = String + type Name = String + type PhoneBook = [(Name,PhoneNumber)] + #+END_SRC - #+BEGIN_SRC haskell - type PhoneNumber = String - type Name = String - type PhoneBook = [(Name,PhoneNumber)] - #+END_SRC + In Java we don't have the ability to have that true alias, so TypeAlias is + more of a type-wrapper. It's as close as I could get to a Haskell type alias + in Java. - In Java we don't have the ability to have that true alias, so TypeAlias is - more of a type-wrapper. It's as close as I could get to a Haskell type alias - in Java. + The benefits of using TypeAlias are: - The benefits of using TypeAlias are: + - encapsulation of the wrapped type when passing references through code + that doesn't need to access the actual value, but only to pass it on + - type-safe parameters where you would otherwise be passing Strings, + Integers, Lists, or other general classes + - equality and hashcode + - less verbose than implementing your own - - encapsulation of the wrapped type when passing references through code - that doesn't need to access the actual value, but only to pass it on - - type-safe parameters where you would otherwise be passing Strings, - Integers, Lists, or other general classes - - equality and hashcode - - less verbose than implementing your own + *TypeAlias Example:* - *TypeAlias Example:* + #+BEGIN_SRC java + class PhoneNumber extends TypeAlias { + private PhoneNumber(final String value) { + super(value); + } + public static PhoneNumber of(final String phoneNumber) { + return new PhoneNumber(phoneNumber); + } + } + #+END_SRC - #+BEGIN_SRC java - class PhoneNumber extends TypeAlias { - private PhoneNumber(final String value) { - super(value); - } - public static PhoneNumber of(final String phoneNumber) { - return new PhoneNumber(phoneNumber); - } - } - #+END_SRC + *Roll your own:* - *Roll your own:* + #+BEGIN_SRC java + class PhoneNumber { + private final String value; + private PhoneNumber(final String value) { + this.value = value; + } + public static PhoneNumber of(final String phoneNumber) { + return new PhoneNumber(phoneNumber); + } + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + PhoneNumber that = (PhoneNumber) o; + return Objects.equals(value, that.value); + } + @Override + public int hashCode() { + return Objects.hash(value); + } + public String getValue() { + return value; + } + } + #+END_SRC - #+BEGIN_SRC java - class PhoneNumber { - private final String value; - private PhoneNumber(final String value) { - this.value = value; - } - public static PhoneNumber of(final String phoneNumber) { - return new PhoneNumber(phoneNumber); - } - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - PhoneNumber that = (PhoneNumber) o; - return Objects.equals(value, that.value); - } - @Override - public int hashCode() { - return Objects.hash(value); - } - public String getValue() { - return value; - } - } - #+END_SRC + *Lombok:* - *Lombok:* + Although, if you are using Lombok, that can be equally terse, both it and + TypeAlias coming in at 8 lines each, compared to 24 for rolling your + own: - Although, if you are using Lombok, that can be equally terse, both it and - TypeAlias coming in at 8 lines each, compared to 24 for rolling your - own: + #+BEGIN_SRC java + @Value + @RequiredArgsConstructor(access = AccessLevel.PRIVATE) + class PhoneNumber { + private final String value; + public static PhoneNumber of(final String phoneNumber) { + return new PhoneNumber(phoneNumber); + } + } + #+END_SRC - #+BEGIN_SRC java - @Value - @RequiredArgsConstructor(access = AccessLevel.PRIVATE) - class PhoneNumber { - private final String value; - public static PhoneNumber of(final String phoneNumber) { - return new PhoneNumber(phoneNumber); - } - } - #+END_SRC +** Maybe + :PROPERTIES: + :CUSTOM_ID: maybe + :END: -*** Maybe - :PROPERTIES: - :CUSTOM_ID: maybe - :END: + Allows specifying that a value may or may not be present. Similar to + =Optional=. =Maybe= provides additional methods that =Optional= doesn't: + =isNothing()=, =stream()=, =ifNothing()= and =match()=. =Maybe= does not + have a =get()= method. - Allows specifying that a value may or may not be present. Similar to - =Optional=. =Maybe= provides additional methods that =Optional= doesn't: - =isNothing()=, =stream()=, =ifNothing()= and =match()=. =Maybe= does not - have a =get()= method. + Unlike =Optional=, when a =map()= results in a =null=, the =Maybe= will + continue to be a =Just=. =Optional= would switch to being empty. [[http://blog.vavr.io/the-agonizing-death-of-an-astronaut/][vavi.io + follows the same behaviour as =Maybe=]]. - Unlike =Optional=, when a =map()= results in a =null=, the =Maybe= will - continue to be a =Just=. =Optional= would switch to being empty. [[http://blog.vavr.io/the-agonizing-death-of-an-astronaut/][vavi.io - follows the same behaviour as =Maybe=]]. + #+BEGIN_SRC java + import net.kemitix.mon.maybe.Maybe; - #+BEGIN_SRC java - import net.kemitix.mon.maybe.Maybe; + import java.util.function.Function; + import java.util.function.Predicate; - import java.util.function.Function; - import java.util.function.Predicate; + class MaybeExample { - class MaybeExample { + public static void main(String[] args) { + Maybe.just(countArgs(args)) + .filter(isEven()) + .map(validMessage()) + .match( + just -> System.out.println(just), + () -> System.out.println("Not an valid value") + ); + } - public static void main(String[] args) { - Maybe.just(countArgs(args)) - .filter(isEven()) - .map(validMessage()) - .match( - just -> System.out.println(just), - () -> System.out.println("Not an valid value") - ); - } + private static Function validMessage() { + return v -> String.format("Value %d is even", v); + } - private static Function validMessage() { - return v -> String.format("Value %d is even", v); - } + private static Predicate isEven() { + return v -> v % 2 == 0; + } - private static Predicate isEven() { - return v -> v % 2 == 0; - } + private static Integer countArgs(String[] args) { + return args.length; + } + } + #+END_SRC - private static Integer countArgs(String[] args) { - return args.length; - } - } - #+END_SRC - - In the above example, the number of command line arguments are counted, if - there are an even number of them then a message is created and printed by - the Consumer parameter in the =match= call. If there is an odd number of - arguments, then the filter will return =Maybe.nothing()=, meaning that the - =nothing= drops straight through the map and triggers the Runnable parameter - in the =match= call. + In the above example, the number of command line arguments are counted, if + there are an even number of them then a message is created and printed by + the Consumer parameter in the =match= call. If there is an odd number of + arguments, then the filter will return =Maybe.nothing()=, meaning that the + =nothing= drops straight through the map and triggers the Runnable parameter + in the =match= call. **** =Maybe= is a Monad: @@ -392,60 +388,60 @@ .toOptional(); #+END_SRC -*** Result - :PROPERTIES: - :CUSTOM_ID: result - :END: +** Result + :PROPERTIES: + :CUSTOM_ID: result + :END: - Allows handling error conditions without the need to catch exceptions. + Allows handling error conditions without the need to catch exceptions. - When a =Result= is returned from a method it will contain one of two values. - Either the actual result, or an error in the form of an =Exception=. The - exception is returned within the =Result= and is not thrown. + When a =Result= is returned from a method it will contain one of two values. + Either the actual result, or an error in the form of an =Exception=. The + exception is returned within the =Result= and is not thrown. - #+BEGIN_SRC java - import net.kemitix.mon.result.Result; + #+BEGIN_SRC java + import net.kemitix.mon.result.Result; - import java.io.IOException; + import java.io.IOException; - class ResultExample implements Runnable { + class ResultExample implements Runnable { - public static void main(final String[] args) { - new ResultExample().run(); - } + public static void main(final String[] args) { + new ResultExample().run(); + } - @Override - public void run() { - Result.of(() -> callRiskyMethod()) - .flatMap(state -> doSomething(state)) - .match( - success -> System.out.println(success), - error -> error.printStackTrace() - ); - } + @Override + public void run() { + Result.of(() -> callRiskyMethod()) + .flatMap(state -> doSomething(state)) + .match( + success -> System.out.println(success), + error -> error.printStackTrace() + ); + } - private String callRiskyMethod() throws IOException { - return "I'm fine"; - } + private String callRiskyMethod() throws IOException { + return "I'm fine"; + } - private Result doSomething(final String state) { - return Result.of(() -> state + ", it's all good."); - } + private Result doSomething(final String state) { + return Result.of(() -> state + ", it's all good."); + } - } - #+END_SRC + } + #+END_SRC - In the above example the string ="I'm fine"= is returned by - =callRiskyMethod()= within a successful =Result=. The =.flatMap()= call, - unwraps that =Result= and, as it is a success, passes the contents to - =doSomething()=, which in turn returns a =Result= that the =.flatMap()= call - returns. =match()= is called on the =Result= and, being a success, will call - the success =Consumer=. + In the above example the string ="I'm fine"= is returned by + =callRiskyMethod()= within a successful =Result=. The =.flatMap()= call, + unwraps that =Result= and, as it is a success, passes the contents to + =doSomething()=, which in turn returns a =Result= that the =.flatMap()= call + returns. =match()= is called on the =Result= and, being a success, will call + the success =Consumer=. - Had =callRiskyMethod()= thrown an exception it would have been caught by the - =Result.of()= method which would have then been an error =Result=. An error - Result would have ignored the =flatMap= and skipped to the =match()= when it - would have called the error =Consumer=. + Had =callRiskyMethod()= thrown an exception it would have been caught by the + =Result.of()= method which would have then been an error =Result=. An error + Result would have ignored the =flatMap= and skipped to the =match()= when it + would have called the error =Consumer=. **** =Result= is a Monad From 3e4629d873d34ac6c7d54e7e69de48eeea614047 Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Mon, 16 Jul 2018 19:07:19 +0100 Subject: [PATCH 3/6] README.org: reformat and adjust headers --- README.org | 713 +++++++++++++++++++++++++++-------------------------- 1 file changed, 369 insertions(+), 344 deletions(-) diff --git a/README.org b/README.org index c882f76..b2ec557 100644 --- a/README.org +++ b/README.org @@ -1,7 +1,4 @@ * Mon - :PROPERTIES: - :CUSTOM_ID: mon - :END: ** TypeAlias, Maybe and Result for Java. @@ -23,10 +20,8 @@ [[https://app.codacy.com/project/kemitix/mon/dashboard][file:https://img.shields.io/codacy/grade/d57096b0639d496aba9a7e43e7cf5b4c.svg?style=for-the-badge]] [[http://i.jpeek.org/net.kemitix/mon/index.html][file:http://i.jpeek.org/net.kemitix/mon/badge.svg]] + ** Maven - :PROPERTIES: - :CUSTOM_ID: maven - :END: #+BEGIN_SRC xml @@ -41,9 +36,6 @@ ** TypeAlias - :PROPERTIES: - :CUSTOM_ID: typealias - :END: In Haskell it is possible to create an alias for a Type, and to then use that alias with the same behaviour as the original, except that the compiler @@ -127,10 +119,8 @@ } #+END_SRC + ** Maybe - :PROPERTIES: - :CUSTOM_ID: maybe - :END: Allows specifying that a value may or may not be present. Similar to =Optional=. =Maybe= provides additional methods that =Optional= doesn't: @@ -180,218 +170,232 @@ =nothing= drops straight through the map and triggers the Runnable parameter in the =match= call. -**** =Maybe= is a Monad: +*** =Maybe= is a Monad: - #+BEGIN_SRC java - package net.kemitix.mon; + #+BEGIN_SRC java + package net.kemitix.mon; - import net.kemitix.mon.maybe.Maybe; - import org.assertj.core.api.WithAssertions; - import org.junit.Test; + import net.kemitix.mon.maybe.Maybe; + import org.assertj.core.api.WithAssertions; + import org.junit.Test; - import java.util.function.Function; + import java.util.function.Function; - public class MaybeMonadTest implements WithAssertions { + public class MaybeMonadTest implements WithAssertions { - private final int v = 1; - private final Function> f = i -> m(i * 2); - private final Function> g = i -> m(i + 6); + private final int v = 1; + private final Function> f = i -> m(i * 2); + private final Function> g = i -> m(i + 6); - private static Maybe m(int value) { - return Maybe.maybe(value); - } + private static Maybe m(int value) { + return Maybe.maybe(value); + } - @Test - public void leftIdentity() { - assertThat( - m(v).flatMap(f) - ).isEqualTo( - f.apply(v) - ); - } + @Test + public void leftIdentity() { + assertThat( + m(v).flatMap(f) + ).isEqualTo( + f.apply(v) + ); + } - @Test - public void rightIdentity() { - assertThat( - m(v).flatMap(x -> m(x)) - ).isEqualTo( - m(v) - ); - } + @Test + public void rightIdentity() { + assertThat( + m(v).flatMap(x -> m(x)) + ).isEqualTo( + m(v) + ); + } - @Test - public void associativity() { - assertThat( - m(v).flatMap(f).flatMap(g) - ).isEqualTo( - m(v).flatMap(x -> f.apply(x).flatMap(g)) - ); - } + @Test + public void associativity() { + assertThat( + m(v).flatMap(f).flatMap(g) + ).isEqualTo( + m(v).flatMap(x -> f.apply(x).flatMap(g)) + ); + } - } - #+END_SRC + } + #+END_SRC -**** Static Constructors -***** =static Maybe maybe(T value)= +*** Static Constructors - Create a Maybe for the value that may or may not be present. +**** =static Maybe maybe(T value)= - Where the value is =null=, that is taken as not being present. + Create a Maybe for the value that may or may not be present. - #+BEGIN_SRC java - final Maybe just = Maybe.maybe(1); - final Maybe nothing = Maybe.maybe(null); - #+END_SRC + Where the value is =null=, that is taken as not being present. -***** =static Maybe just(T value)= + #+BEGIN_SRC java + final Maybe just = Maybe.maybe(1); + final Maybe nothing = Maybe.maybe(null); + #+END_SRC - Create a Maybe for the value that is present. - The =value= must not be =null= or a =NullPointerException= will be thrown. - If you can't prove that the value won't be =null= you should use - =Maybe.maybe(value)= instead. +**** =static Maybe just(T value)= - #+BEGIN_SRC java - final Maybe just = Maybe.just(1); - #+END_SRC + Create a Maybe for the value that is present. -***** =static Maybe nothing()= + The =value= must not be =null= or a =NullPointerException= will be thrown. + If you can't prove that the value won't be =null= you should use + =Maybe.maybe(value)= instead. - Create a Maybe for a lack of a value. + #+BEGIN_SRC java + final Maybe just = Maybe.just(1); + #+END_SRC - #+BEGIN_SRC java - final Maybe nothing = Maybe.nothing(); - #+END_SRC -**** Instance Methods +**** =static Maybe nothing()= -***** =Maybe filter(Predicate predicate)= + Create a Maybe for a lack of a value. - Filter a Maybe by the predicate, replacing with Nothing when it fails. + #+BEGIN_SRC java + final Maybe nothing = Maybe.nothing(); + #+END_SRC - #+BEGIN_SRC java - final Maybe maybe = Maybe.maybe(getValue()) - .filter(v -> v % 2 == 0); - #+END_SRC -***** = Maybe map(Function f)= +*** Instance Methods - Applies the function to the value within the Maybe, returning the result within another Maybe. +**** =Maybe filter(Predicate predicate)= - #+BEGIN_SRC java - final Maybe maybe = Maybe.maybe(getValue()) - .map(v -> v * 100); - #+END_SRC + Filter a Maybe by the predicate, replacing with Nothing when it fails. -***** = Maybe flatMap(Function> f)= + #+BEGIN_SRC java + final Maybe maybe = Maybe.maybe(getValue()) + .filter(v -> v % 2 == 0); + #+END_SRC - Applies the function to the value within the =Maybe=, resulting in another =Maybe=, then flattens the resulting =Maybe>= into =Maybe=. - Monad binder maps the Maybe into another Maybe using the binder method f +**** = Maybe map(Function f)= - #+BEGIN_SRC java - final Maybe maybe = Maybe.maybe(getValue()) - .flatMap(v -> Maybe.maybe(getValueFor(v))); - #+END_SRC + Applies the function to the value within the Maybe, returning the result within another Maybe. -***** =void match(Consumer just, Runnable nothing)= + #+BEGIN_SRC java + final Maybe maybe = Maybe.maybe(getValue()) + .map(v -> v * 100); + #+END_SRC - Matches the Maybe, either just or nothing, and performs either the Consumer, for Just, or Runnable for nothing. - #+BEGIN_SRC java - Maybe.maybe(getValue()) - .match( - just -> workWithValue(just), - () -> nothingToWorkWith() - ); - #+END_SRC +**** = Maybe flatMap(Function> f)= -***** =T orElse(T otherValue)= + Applies the function to the value within the =Maybe=, resulting in another =Maybe=, then flattens the resulting =Maybe>= into =Maybe=. - A value to use when Maybe is Nothing. + Monad binder maps the Maybe into another Maybe using the binder method f - #+BEGIN_SRC java - final Integer value = Maybe.maybe(getValue()) - .orElse(1); - #+END_SRC + #+BEGIN_SRC java + final Maybe maybe = Maybe.maybe(getValue()) + .flatMap(v -> Maybe.maybe(getValueFor(v))); + #+END_SRC -***** =T orElseGet(Supplier otherValueSupplier)= - Provide a value to use when Maybe is Nothing. +**** =void match(Consumer just, Runnable nothing)= - #+BEGIN_SRC java - final Integer value = Maybe.maybe(getValue()) - .orElseGet(() -> getDefaultValue()); - #+END_SRC + Matches the Maybe, either just or nothing, and performs either the Consumer, for Just, or Runnable for nothing. -***** =void orElseThrow(Supplier error)= + #+BEGIN_SRC java + Maybe.maybe(getValue()) + .match( + just -> workWithValue(just), + () -> nothingToWorkWith() + ); + #+END_SRC - Throw the exception if the Maybe is a Nothing. - #+BEGIN_SRC java - final Integer value = Maybe.maybe(getValue()) - .orElseThrow(() -> new RuntimeException("error")); - #+END_SRC +**** =T orElse(T otherValue)= -***** =Maybe peek(Consumer consumer)= + A value to use when Maybe is Nothing. - Provide the value within the Maybe, if it exists, to the Consumer, and returns this Maybe. Conceptually equivalent to the idea of =ifPresent(...)=. + #+BEGIN_SRC java + final Integer value = Maybe.maybe(getValue()) + .orElse(1); + #+END_SRC - #+BEGIN_SRC java - final Maybe maybe = Maybe.maybe(getValue()) - .peek(v -> v.foo()); - #+END_SRC -***** =void ifNothing(Runnable runnable)= +**** =T orElseGet(Supplier otherValueSupplier)= - Run the runnable if the Maybe is a Nothing, otherwise do nothing. + Provide a value to use when Maybe is Nothing. - #+BEGIN_SRC java - Maybe.maybe(getValue()) - .ifNothing(() -> doSomething()); - #+END_SRC + #+BEGIN_SRC java + final Integer value = Maybe.maybe(getValue()) + .orElseGet(() -> getDefaultValue()); + #+END_SRC -***** =Stream stream()= - Converts the Maybe into either a single value stream or an empty stream. +**** =void orElseThrow(Supplier error)= - #+BEGIN_SRC java - final Stream stream = Maybe.maybe(getValue()) - .stream(); - #+END_SRC + Throw the exception if the Maybe is a Nothing. -***** =boolean isJust()= + #+BEGIN_SRC java + final Integer value = Maybe.maybe(getValue()) + .orElseThrow(() -> new RuntimeException("error")); + #+END_SRC - Checks if the Maybe is a Just. - #+BEGIN_SRC java - final boolean isJust = Maybe.maybe(getValue()) - .isJust(); - #+END_SRC +**** =Maybe peek(Consumer consumer)= -***** =boolean isNothing()= + Provide the value within the Maybe, if it exists, to the Consumer, and returns this Maybe. Conceptually equivalent to the idea of =ifPresent(...)=. - Checks if the Maybe is Nothing. + #+BEGIN_SRC java + final Maybe maybe = Maybe.maybe(getValue()) + .peek(v -> v.foo()); + #+END_SRC - #+BEGIN_SRC java - final boolean isNothing = Maybe.maybe(getValue()) - .isNothing(); - #+END_SRC -***** =Optional toOptional()= +**** =void ifNothing(Runnable runnable)= - Convert the Maybe to an Optional. + Run the runnable if the Maybe is a Nothing, otherwise do nothing. + + #+BEGIN_SRC java + Maybe.maybe(getValue()) + .ifNothing(() -> doSomething()); + #+END_SRC + + +**** =Stream stream()= + + Converts the Maybe into either a single value stream or an empty stream. + + #+BEGIN_SRC java + final Stream stream = Maybe.maybe(getValue()) + .stream(); + #+END_SRC + + +**** =boolean isJust()= + + Checks if the Maybe is a Just. + + #+BEGIN_SRC java + final boolean isJust = Maybe.maybe(getValue()) + .isJust(); + #+END_SRC + + +**** =boolean isNothing()= + + Checks if the Maybe is Nothing. + + #+BEGIN_SRC java + final boolean isNothing = Maybe.maybe(getValue()) + .isNothing(); + #+END_SRC + + +**** =Optional toOptional()= + + Convert the Maybe to an Optional. + + #+BEGIN_SRC java + final Optional optional = Maybe.maybe(getValue()) + .toOptional(); + #+END_SRC - #+BEGIN_SRC java - final Optional optional = Maybe.maybe(getValue()) - .toOptional(); - #+END_SRC ** Result - :PROPERTIES: - :CUSTOM_ID: result - :END: Allows handling error conditions without the need to catch exceptions. @@ -443,257 +447,278 @@ Result would have ignored the =flatMap= and skipped to the =match()= when it would have called the error =Consumer=. -**** =Result= is a Monad +*** =Result= is a Monad + + #+BEGIN_SRC java + 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 { + + private final int v = 1; + private final Function> f = i -> r(i * 2); + private final Function> g = i -> r(i + 6); + + private static Result r(int v) { + return Result.ok(v); + } + + @Test + public void leftIdentity() { + assertThat( + r(v).flatMap(f) + ).isEqualTo( + f.apply(v) + ); + } + + @Test + public void rightIdentity() { + assertThat( + r(v).flatMap(x -> r(x)) + ).isEqualTo( + r(v) + ); + } + + @Test + public void associativity() { + assertThat( + r(v).flatMap(f).flatMap(g) + ).isEqualTo( + r(v).flatMap(x -> f.apply(x).flatMap(g)) + ); + } + + } + #+END_SRC + + +*** Static Constructors + +**** =static Result of(Callable callable)= + + Create a Result for a output of the Callable. + + If the Callable throws and Exception, then the Result will be an error and + will contain that exception. + + This will be the main starting point for most Results where the callable + could throw an =Exception=. #+BEGIN_SRC java - 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 { - - private final int v = 1; - private final Function> f = i -> r(i * 2); - private final Function> g = i -> r(i + 6); - - private static Result r(int v) { - return Result.ok(v); - } - - @Test - public void leftIdentity() { - assertThat( - r(v).flatMap(f) - ).isEqualTo( - f.apply(v) - ); - } - - @Test - public void rightIdentity() { - assertThat( - r(v).flatMap(x -> r(x)) - ).isEqualTo( - r(v) - ); - } - - @Test - public void associativity() { - assertThat( - r(v).flatMap(f).flatMap(g) - ).isEqualTo( - r(v).flatMap(x -> f.apply(x).flatMap(g)) - ); - } - - } + final Result okay = Result.of(() -> 1); + final Result error = Result.of(() -> {throw new RuntimeException();}); #+END_SRC -**** Static Constructors -***** =static Result of(Callable callable)= +**** =static Result ok(T value)= - Create a Result for a output of the Callable. + Create a Result for a success. - If the Callable throws and Exception, then the Result will be an error and - will contain that exception. + Use this where you have a value that you want to place into the Result context. - This will be the main starting point for most Results where the callable - could throw an =Exception=. + #+BEGIN_SRC java + final Result okay = Result.ok(1); + #+END_SRC - #+BEGIN_SRC java - final Result okay = Result.of(() -> 1); - final Result error = Result.of(() -> {throw new RuntimeException();}); - #+END_SRC -***** =static Result ok(T value)= +**** =static Result error(Throwable error)= - Create a Result for a success. + Create a Result for an error. - Use this where you have a value that you want to place into the Result context. + #+BEGIN_SRC java + final Result error = Result.error(new RuntimeException()); + #+END_SRC - #+BEGIN_SRC java - final Result okay = Result.ok(1); - #+END_SRC -***** =static Result error(Throwable error)= +*** Static Methods - Create a Result for an error. + These static methods provide integration with the =Maybe= class. - #+BEGIN_SRC java - final Result error = Result.error(new RuntimeException()); - #+END_SRC + #+BEGIN_SRC java + #+END_SRC -**** Static Methods +**** =static Maybe toMaybe(Result result)= - These static methods provide integration with the =Maybe= class. + Creates a =Maybe= from the =Result=, where the =Result= is a success, then + the =Maybe= will contain the value. However, if the =Result= is an error + then the =Maybe= will be nothing. - #+BEGIN_SRC java - #+END_SRC + #+BEGIN_SRC java + final Result result = Result.of(() -> getValue()); + final Maybe maybe = Result.toMaybe(result); + #+END_SRC -***** =static Maybe toMaybe(Result result)= - Creates a =Maybe= from the =Result=, where the =Result= is a success, then - the =Maybe= will contain the value. However, if the =Result= is an error - then the =Maybe= will be nothing. +**** =static Result fromMaybe(Maybe maybe, Supplier error)= - #+BEGIN_SRC java - final Result result = Result.of(() -> getValue()); - final Maybe maybe = Result.toMaybe(result); - #+END_SRC + Creates a =Result= from the =Maybe=, where the =Result= will be an error + if the =Maybe= is nothing. Where the =Maybe= is nothing, then the + =Supplier= will provide the error for the =Result=. -***** =static Result fromMaybe(Maybe maybe, Supplier error)= + #+BEGIN_SRC java + final Maybe maybe = Maybe.maybe(getValue()); + final Result result = Result.fromMaybe(maybe, () -> new NoSuchFileException("filename")); + #+END_SRC - Creates a =Result= from the =Maybe=, where the =Result= will be an error - if the =Maybe= is nothing. Where the =Maybe= is nothing, then the - =Supplier= will provide the error for the =Result=. - #+BEGIN_SRC java - final Maybe maybe = Maybe.maybe(getValue()); - final Result result = Result.fromMaybe(maybe, () -> new NoSuchFileException("filename")); - #+END_SRC +**** =static Result> invert(Maybe> maybeResult)= -***** =static Result> invert(Maybe> maybeResult)= + Swaps the =Result= within a =Maybe=, so that =Result= contains a =Maybe=. - Swaps the =Result= within a =Maybe=, so that =Result= contains a =Maybe=. + #+BEGIN_SRC java + final Maybe> maybe = Maybe.maybe(Result.of(() -> getValue())); + final Result> result = Result.invert(maybe); + #+END_SRC - #+BEGIN_SRC java - final Maybe> maybe = Maybe.maybe(Result.of(() -> getValue())); - final Result> result = Result.invert(maybe); - #+END_SRC -***** =static Result> flatMapMaybe(Result> maybeResult, Function,Result>> f)= +**** =static Result> flatMapMaybe(Result> maybeResult, Function,Result>> f)= - Applies the function to the contents of a Maybe within the Result. + Applies the function to the contents of a Maybe within the Result. - #+BEGIN_SRC java - final Result> result = Result.of(() -> Maybe.maybe(getValue())); - final Result> maybeResult = Result.flatMapMaybe(result, maybe -> Result.of(() -> maybe.map(v -> v * 2))); - #+END_SRC + #+BEGIN_SRC java + final Result> result = Result.of(() -> Maybe.maybe(getValue())); + final Result> maybeResult = Result.flatMapMaybe(result, maybe -> Result.of(() -> maybe.map(v -> v * 2))); + #+END_SRC -**** Instance Methods -***** Result map(Function f) +*** Instance Methods - Applies the function to the value within the Functor, returning the result - within a Functor. +**** = Result map(Function f)= - #+BEGIN_SRC java - final Result result = Result.of(() -> getValue()) - .map(v -> String.valueOf(v)); - #+END_SRC + Applies the function to the value within the Functor, returning the result + within a Functor. -***** Result flatMap(Function> f) + #+BEGIN_SRC java + final Result result = Result.of(() -> getValue()) + .map(v -> String.valueOf(v)); + #+END_SRC - Returns a new Result consisting of the result of applying the function to - the contents of the Result. - #+BEGIN_SRC java - final Result result = Result.of(() -> getValue()) - .flatMap(v -> Result.of(() -> String.valueOf(v))); - #+END_SRC +**** = Result flatMap(Function> f)= -***** Result andThen(Function> f) + Returns a new Result consisting of the result of applying the function to + the contents of the Result. - Maps a Success Result to another Result using a Callable that is able to - throw a checked exception. + #+BEGIN_SRC java + final Result result = Result.of(() -> getValue()) + .flatMap(v -> Result.of(() -> String.valueOf(v))); + #+END_SRC - #+BEGIN_SRC java - final Result result = Result.of(() -> getValue()) - .andThen(v -> () -> {throw new IOException();}); - #+END_SRC -***** void match(Consumer onSuccess, Consumer onError) +**** = Result andThen(Function> f)= - Matches the Result, either success or error, and supplies the appropriate - Consumer with the value or error. + Maps a Success Result to another Result using a Callable that is able to + throw a checked exception. - #+BEGIN_SRC java - Result.of(() -> getValue()) - .match( - success -> System.out.println(success), - error -> System.err.println("error") - ); - #+END_SRC + #+BEGIN_SRC java + final Result result = Result.of(() -> getValue()) + .andThen(v -> () -> {throw new IOException();}); + #+END_SRC -***** Result recover(Function> f) - Provide a way to attempt to recover from an error state. +**** =void match(Consumer onSuccess, Consumer onError)= - #+BEGIN_SRC java + Matches the Result, either success or error, and supplies the appropriate + Consumer with the value or error. + + #+BEGIN_SRC java + Result.of(() -> getValue()) + .match( + success -> System.out.println(success), + error -> System.err.println("error") + ); + #+END_SRC + + +**** =Result recover(Function> f)= + + Provide a way to attempt to recover from an error state. + + #+BEGIN_SRC java + final Result result = Result.of(() -> getValue()) + .recover(e -> Result.of(() -> getSafeValue(e))); + #+END_SRC + + +**** =Result peek(Consumer consumer)= + + Provide the value within the Result, if it is a success, to the Consumer, + and returns this Result. + + #+BEGIN_SRC java + final Result result = Result.of(() -> getValue()) + .peek(v -> System.out.println(v)); + #+END_SRC + + +**** =Result thenWith(Function> f)= + + Perform the continuation with the current Result value then return the + current Result, assuming there was no error in the continuation. + + #+BEGIN_SRC java final Result result = Result.of(() -> getValue()) - .recover(e -> Result.of(() -> getSafeValue(e))); - #+END_SRC + .thenWith(v -> () -> System.out.println(v)) + .thenWith(v -> () -> {throw new IOException();}); + #+END_SRC -***** Result peek(Consumer consumer) - Provide the value within the Result, if it is a success, to the Consumer, - and returns this Result. +**** =Result> maybe(Predicate predicate)= - #+BEGIN_SRC java - final Result result = Result.of(() -> getValue()) - .peek(v -> System.out.println(v)); - #+END_SRC + Wraps the value within the Result in a Maybe, either a Just if the + predicate is true, or Nothing. -***** Result thenWith(Function> f) + #+BEGIN_SRC java + final Result> result = Result.of(() -> getValue()) + .maybe(v -> v % 2 == 0); + #+END_SRC - Perform the continuation with the current Result value then return the - current Result, assuming there was no error in the continuation. - #+BEGIN_SRC java - final Result result = Result.of(() -> getValue()) - .thenWith(v -> () -> System.out.println(v)) - .thenWith(v -> () -> {throw new IOException();}); - #+END_SRC +**** =T orElseThrow()= -***** Result> maybe(Predicate predicate) + Extracts the successful value from the result, or throws the error + Throwable. - Wraps the value within the Result in a Maybe, either a Just if the - predicate is true, or Nothing. + #+BEGIN_SRC java + final Integer result = Result.of(() -> getValue()) + .orElseThrow(); + #+END_SRC - #+BEGIN_SRC java - final Result> result = Result.of(() -> getValue()) - .maybe(v -> v % 2 == 0); - #+END_SRC -***** T orElseThrow() +**** =void onError(Consumer errorConsumer)= - Extracts the successful value from the result, or throws the error - Throwable. + A handler for error states. - #+BEGIN_SRC java - final Integer result = Result.of(() -> getValue()) - .orElseThrow(); - #+END_SRC + #+BEGIN_SRC java + Result.of(() -> getValue()) + .onError(e -> handleError(e)); + #+END_SRC -***** void onError(Consumer errorConsumer) - A handler for error states. +**** =boolean isOkay()= - #+BEGIN_SRC java - Result.of(() -> getValue()) - .onError(e -> handleError(e)); - #+END_SRC + Checks if the Result is a success. -***** boolean isOkay() + #+BEGIN_SRC java + final boolean isOkay = Result.of(() -> getValue()) + .isOkay(); + #+END_SRC - Checks if the Result is a success. - #+BEGIN_SRC java - final boolean isOkay = Result.of(() -> getValue()) - .isOkay(); - #+END_SRC +**** =boolean isError()= -***** boolean isError() + Checks if the Result is an error. + + #+BEGIN_SRC java + final boolean isError = Result.of(() -> getValue()) + .isError(); + #+END_SRC - Checks if the Result is an error. - #+BEGIN_SRC java - final boolean isError = Result.of(() -> getValue()) - .isError(); - #+END_SRC From 78f15df464522a26e32831b4e8b192e616cc003d Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Mon, 16 Jul 2018 19:31:18 +0100 Subject: [PATCH 4/6] README.org: add documentation for Either --- README.org | 83 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) diff --git a/README.org b/README.org index b2ec557..694f6d1 100644 --- a/README.org +++ b/README.org @@ -722,3 +722,86 @@ #+END_SRC +** Either + + Allows handling a value that can be one of two types, a left value/type or a + right value/type. + + When an =Either= is returned from a method it will contain either a left or a + right. + + Where the =Either= is used to represent success/failure, the left case is, by + convention, used to indicate the error, and right the success. An alternative + is to use the =Result= which more clearly distinguishes success from failure. + + =Either= is not a Monad. + +*** Static Constructors + +**** =static Either left(final L l)= + + Create a new Either holding a left value. + + #+BEGIN_SRC java + final Either left = Either.left(getIntegerValue()); + #+END_SRC + +**** =static Either right(final R r)= + + Create a new Either holding a right value. + + #+BEGIN_SRC java + final Either right = Either.right(getStringValue()); + #+END_SRC + + +*** Instance Methods + +**** =boolean isLeft()= + + Checks if the Either holds a left value. + + #+BEGIN_SRC java + final boolean leftIsLeft = Either.left(getIntegerValue()).isLeft(); + final boolean rightIsLeft = Either.right(getStringValue()).isLeft(); + #+END_SRC + +**** =boolean isRight()= + + Checks if the Either holds a right value. + + #+BEGIN_SRC java + final boolean leftIsRight = Either.left(getIntegerValue()).isRight(); + final boolean rightIsRight = Either.right(getStringValue()).isRight(); + #+END_SRC + + +**** =void match(Consumer onLeft, Consumer onRight)= + + Matches the Either, invoking the correct Consumer. + + #+BEGIN_SRC java + Either.left(getIntegerValue()) + .match( + left -> handleIntegerValue(left), + right -> handleStringValue(right) + ); + #+END_SRC + +**** = Either mapLeft(Function f)= + + Map the function across the left value. + + #+BEGIN_SRC java + final Either either = Either.left(getIntegerValue()) + .mapLeft(i -> i.doubleValue()); + #+END_SRC + +**** = Either mapRight(Function f)= + + Map the function across the right value. + + #+BEGIN_SRC java + final Either either = Either.left(getIntegerValue()) + .mapRight(s -> s + "x"); + #+END_SRC From e8989718f3277be6ce9790ee93fe45a02fbe7bdc Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Mon, 16 Jul 2018 20:56:56 +0100 Subject: [PATCH 5/6] README.org: expand TypeAlias documentation and add `TypeAlias.flatMap()` --- README.org | 127 +++++++++++++++++- src/main/java/net/kemitix/mon/TypeAlias.java | 18 ++- .../net/kemitix/mon/TypeAliasMonadTest.java | 55 ++++++++ 3 files changed, 195 insertions(+), 5 deletions(-) create mode 100644 src/test/java/net/kemitix/mon/TypeAliasMonadTest.java diff --git a/README.org b/README.org index 694f6d1..8117b51 100644 --- a/README.org +++ b/README.org @@ -119,6 +119,125 @@ } #+END_SRC +*** =TypeAlias= *can* be a Monad + + #+BEGIN_SRC java + package net.kemitix.mon; + + import org.assertj.core.api.WithAssertions; + import org.junit.Test; + + import java.util.function.Function; + + public class TypeAliasMonadTest implements WithAssertions { + + private final int v = 1; + private final Function> f = i -> a(i * 2); + private final Function> g = i -> a(i + 6); + + private static AnAlias a(Integer v) { + return AnAlias.of(v); + } + + @Test + public void leftIdentity() { + assertThat( + a(v).flatMap(f) + ).isEqualTo( + f.apply(v) + ); + } + + @Test + public void rightIdentity_inline() { + // java isn't able to properly infer the correct types when used in-line + assertThat( + a(v).>flatMap(x -> a(x)) + ).isEqualTo( + a(v) + ); + } + + @Test + public void rightIdentity_explicitValue() { + final AnAlias integerAnAlias = a(v).flatMap(x -> a(x)); + assertThat( + integerAnAlias + ).isEqualTo( + a(v) + ); + } + + @Test + public void associativity() { + assertThat( + a(v).flatMap(f).flatMap(g) + ).isEqualTo( + a(v).flatMap(x -> f.apply(x).flatMap(g)) + ); + } + + static class AnAlias extends TypeAlias { + private AnAlias(T value) { + super(value); + } + + static AnAlias of(T value) { + return new AnAlias<>(value); + } + } + } + #+END_SRC + + +*** Instance Methods + +**** =final R map(final Function f)= + + Map the TypeAlias into another value. + + #+BEGIN_SRC java + final StudentId studentId = StudentId.of(123); + final String idString = studentId.map(id -> String.valueOf(id)); + + class StudentId extends TypeAlias { + private StudentId(Integer value) { + super(value); + } + static StudentId of(Integer id) { + return new StudentId(id); + } + } + #+END_SRC + + +**** =final > U flatMap(final Function f)= + + Map the TypeAlias into another TypeAlias. + + #+BEGIN_SRC java + final StudentId studentId = StudentId.of(123); + final StudentName studentName = studentId.flatMap(id -> getStudentName(id)); + + class StudentName extends TypeAlias { + private StudentName(String value) { + super(value); + } + static StudentName of(final String name) { + return new StudentName(name); + } + } + #+END_SRC + + +**** =T getValue()= + + Get the value of the TypeAlias. + + #+BEGIN_SRC java + final String name = studentName.getValue(); + #+END_SRC + ** Maybe @@ -734,7 +853,7 @@ convention, used to indicate the error, and right the success. An alternative is to use the =Result= which more clearly distinguishes success from failure. - =Either= is not a Monad. +*** =Either= *is not* a Monad. *** Static Constructors @@ -746,6 +865,7 @@ final Either left = Either.left(getIntegerValue()); #+END_SRC + **** =static Either right(final R r)= Create a new Either holding a right value. @@ -766,6 +886,7 @@ final boolean rightIsLeft = Either.right(getStringValue()).isLeft(); #+END_SRC + **** =boolean isRight()= Checks if the Either holds a right value. @@ -788,6 +909,7 @@ ); #+END_SRC + **** = Either mapLeft(Function f)= Map the function across the left value. @@ -797,6 +919,7 @@ .mapLeft(i -> i.doubleValue()); #+END_SRC + **** = Either mapRight(Function f)= Map the function across the right value. @@ -805,3 +928,5 @@ final Either either = Either.left(getIntegerValue()) .mapRight(s -> s + "x"); #+END_SRC + + diff --git a/src/main/java/net/kemitix/mon/TypeAlias.java b/src/main/java/net/kemitix/mon/TypeAlias.java index 5d49978..d4df4ef 100644 --- a/src/main/java/net/kemitix/mon/TypeAlias.java +++ b/src/main/java/net/kemitix/mon/TypeAlias.java @@ -30,7 +30,6 @@ import java.util.function.Function; * for the type being aliased.

* * @param the type of the alias - * * @author Paul Campbell (pcampbell@kemitix.net) */ @SuppressWarnings("abstractclassname") @@ -55,13 +54,24 @@ public abstract class TypeAlias { * * @param f the function to create the new value * @param the type of the new value - * - * @return a TypeAlias + * @return the result of the function */ public final R map(final Function f) { return f.apply(value); } + /** + * Map the TypeAlias into another TypeAlias. + * + * @param f the function to create the new value + * @param the type of the new value within a TypeAlias + * @param the type of the TypeAlias superclass containing the new value + * @return a TypeAlias + */ + public final > U flatMap(final Function f) { + return f.apply(value); + } + @Override public final int hashCode() { return value.hashCode(); @@ -73,7 +83,7 @@ public abstract class TypeAlias { final TypeAlias other = (TypeAlias) o; final Class otherValueClass = other.value.getClass(); return otherValueClass.equals(getValue().getClass()) - && other.value.equals(getValue()); + && other.value.equals(getValue()); } return map(o::equals); } diff --git a/src/test/java/net/kemitix/mon/TypeAliasMonadTest.java b/src/test/java/net/kemitix/mon/TypeAliasMonadTest.java new file mode 100644 index 0000000..90c87df --- /dev/null +++ b/src/test/java/net/kemitix/mon/TypeAliasMonadTest.java @@ -0,0 +1,55 @@ +package net.kemitix.mon; + +import org.assertj.core.api.WithAssertions; +import org.junit.Test; + +import java.util.function.Function; + +public class TypeAliasMonadTest implements WithAssertions { + + private final int v = 1; + private final Function> f = i -> a(i * 2); + private final Function> g = i -> a(i + 6); + + private static AnAlias a(Integer v) { + return AnAlias.of(v); + } + + @Test + public void leftIdentity() { + assertThat( + a(v).flatMap(f) + ).isEqualTo( + f.apply(v) + ); + } + + @Test + public void rightIdentity() { + final AnAlias integerAnAlias = a(v).flatMap(x -> a(x)); + assertThat( + integerAnAlias + ).isEqualTo( + a(v) + ); + } + + @Test + public void associativity() { + assertThat( + a(v).flatMap(f).flatMap(g) + ).isEqualTo( + a(v).flatMap(x -> f.apply(x).flatMap(g)) + ); + } + + static class AnAlias extends TypeAlias { + private AnAlias(T value) { + super(value); + } + + static AnAlias of(T value) { + return new AnAlias<>(value); + } + } +} From 888c43d468e118656a0b027cf797c13f5389202a Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Mon, 16 Jul 2018 21:01:25 +0100 Subject: [PATCH 6/6] Update CHANGELOG --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index 7af2501..170e31b 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -11,6 +11,7 @@ CHANGELOG * BUG: `Maybe.orElseThrow()` now returns value when a Just * Rewrite README * Add `Either` (experimental) +* Add `flatMap` to `TypeAlias` 0.10.0 ------