diff --git a/CHANGELOG b/CHANGELOG
index 990a1c9..170e31b 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,8 @@ CHANGELOG
* Add `Maybe.isNothing()`
* BUG: `Maybe.orElseThrow()` now returns value when a Just
* Rewrite README
+* Add `Either` (experimental)
+* Add `flatMap` to `TypeAlias`
0.10.0
------
diff --git a/README.org b/README.org
index 411f3e0..8117b51 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
@@ -39,665 +34,899 @@
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
- :PROPERTIES:
- :CUSTOM_ID: usage
- :END:
-*** TypeAlias
- :PROPERTIES:
- :CUSTOM_ID: typealias
- :END:
+** TypeAlias
- 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:*
-
- #+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:*
-
- #+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:*
-
- 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
-
-*** 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.
-
- 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;
-
- import java.util.function.Function;
- import java.util.function.Predicate;
-
- 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")
- );
- }
-
- private static Function validMessage() {
- return v -> String.format("Value %d is even", v);
- }
-
- private static Predicate isEven() {
- return v -> v % 2 == 0;
- }
-
- 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.
-
-**** =Maybe= is a Monad:
+ *TypeAlias Example:*
#+BEGIN_SRC java
- package net.kemitix.mon;
+ 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:*
+
+ #+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:*
+
+ 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
+
+*** =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
+
+ 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=]].
+
+ #+BEGIN_SRC java
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.Predicate;
- public class MaybeMonadTest implements WithAssertions {
+ class MaybeExample {
- 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);
+ 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")
+ );
}
- @Test
- public void leftIdentity() {
- assertThat(
- m(v).flatMap(f)
- ).isEqualTo(
- f.apply(v)
- );
+ private static Function validMessage() {
+ return v -> String.format("Value %d is even", v);
}
- @Test
- public void rightIdentity() {
- assertThat(
- m(v).flatMap(x -> m(x))
- ).isEqualTo(
- m(v)
- );
+ private static Predicate isEven() {
+ return v -> v % 2 == 0;
}
- @Test
- public void associativity() {
- assertThat(
- m(v).flatMap(f).flatMap(g)
- ).isEqualTo(
- m(v).flatMap(x -> f.apply(x).flatMap(g))
- );
+ 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.
+
+*** =Maybe= is a Monad:
+
+ #+BEGIN_SRC java
+ 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 {
+
+ 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);
+ }
+
+ @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 associativity() {
+ assertThat(
+ m(v).flatMap(f).flatMap(g)
+ ).isEqualTo(
+ m(v).flatMap(x -> f.apply(x).flatMap(g))
+ );
+ }
+
+ }
+ #+END_SRC
+
+
+*** Static Constructors
+
+**** =static Maybe maybe(T value)=
+
+ Create a Maybe for the value that may or may not be present.
+
+ Where the value is =null=, that is taken as not being present.
+
+ #+BEGIN_SRC java
+ final Maybe just = Maybe.maybe(1);
+ final Maybe nothing = Maybe.maybe(null);
+ #+END_SRC
+
+
+**** =static Maybe just(T value)=
+
+ 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.
+
+ #+BEGIN_SRC java
+ final Maybe just = Maybe.just(1);
+ #+END_SRC
+
+
+**** =static Maybe nothing()=
+
+ Create a Maybe for a lack of a value.
+
+ #+BEGIN_SRC java
+ final Maybe nothing = Maybe.nothing();
+ #+END_SRC
+
+
+*** Instance Methods
+
+**** =Maybe filter(Predicate predicate)=
+
+ Filter a Maybe by the predicate, replacing with Nothing when it fails.
+
+ #+BEGIN_SRC java
+ final Maybe maybe = Maybe.maybe(getValue())
+ .filter(v -> v % 2 == 0);
+ #+END_SRC
+
+
+**** = Maybe map(Function f)=
+
+ Applies the function to the value within the Maybe, returning the result within another Maybe.
+
+ #+BEGIN_SRC java
+ final Maybe maybe = Maybe.maybe(getValue())
+ .map(v -> v * 100);
+ #+END_SRC
+
+
+**** = Maybe flatMap(Function> f)=
+
+ 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
+
+ #+BEGIN_SRC java
+ final Maybe maybe = Maybe.maybe(getValue())
+ .flatMap(v -> Maybe.maybe(getValueFor(v)));
+ #+END_SRC
+
+
+**** =void match(Consumer just, Runnable nothing)=
+
+ 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
+
+
+**** =T orElse(T otherValue)=
+
+ A value to use when Maybe is Nothing.
+
+ #+BEGIN_SRC java
+ final Integer value = Maybe.maybe(getValue())
+ .orElse(1);
+ #+END_SRC
+
+
+**** =T orElseGet(Supplier otherValueSupplier)=
+
+ Provide a value to use when Maybe is Nothing.
+
+ #+BEGIN_SRC java
+ final Integer value = Maybe.maybe(getValue())
+ .orElseGet(() -> getDefaultValue());
+ #+END_SRC
+
+
+**** =void orElseThrow(Supplier error)=
+
+ Throw the exception if the Maybe is a Nothing.
+
+ #+BEGIN_SRC java
+ final Integer value = Maybe.maybe(getValue())
+ .orElseThrow(() -> new RuntimeException("error"));
+ #+END_SRC
+
+
+**** =Maybe peek(Consumer consumer)=
+
+ 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 Maybe maybe = Maybe.maybe(getValue())
+ .peek(v -> v.foo());
+ #+END_SRC
+
+
+**** =void ifNothing(Runnable runnable)=
+
+ 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
+
+
+** Result
+
+ 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.
+
+ #+BEGIN_SRC java
+ import net.kemitix.mon.result.Result;
+
+ import java.io.IOException;
+
+ class ResultExample implements Runnable {
+
+ 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()
+ );
+ }
+
+ private String callRiskyMethod() throws IOException {
+ return "I'm fine";
+ }
+
+ private Result doSomething(final String state) {
+ return Result.of(() -> state + ", it's all good.");
}
}
#+END_SRC
-**** Static Constructors
+ 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=.
-***** =static Maybe maybe(T value)=
+ 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=.
- Create a Maybe for the value that may or may not be present.
-
- Where the value is =null=, that is taken as not being present.
-
- #+BEGIN_SRC java
- final Maybe just = Maybe.maybe(1);
- final Maybe nothing = Maybe.maybe(null);
- #+END_SRC
-
-***** =static Maybe just(T value)=
-
- 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.
-
- #+BEGIN_SRC java
- final Maybe just = Maybe.just(1);
- #+END_SRC
-
-***** =static Maybe nothing()=
-
- Create a Maybe for a lack of a value.
-
- #+BEGIN_SRC java
- final Maybe nothing = Maybe.nothing();
- #+END_SRC
-
-**** Instance Methods
-
-***** =Maybe filter(Predicate predicate)=
-
- Filter a Maybe by the predicate, replacing with Nothing when it fails.
-
- #+BEGIN_SRC java
- final Maybe maybe = Maybe.maybe(getValue())
- .filter(v -> v % 2 == 0);
- #+END_SRC
-
-***** = Maybe map(Function f)=
-
- Applies the function to the value within the Maybe, returning the result within another Maybe.
-
- #+BEGIN_SRC java
- final Maybe maybe = Maybe.maybe(getValue())
- .map(v -> v * 100);
- #+END_SRC
-
-***** = Maybe flatMap(Function> f)=
-
- 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
-
- #+BEGIN_SRC java
- final Maybe maybe = Maybe.maybe(getValue())
- .flatMap(v -> Maybe.maybe(getValueFor(v)));
- #+END_SRC
-
-***** =void match(Consumer just, Runnable nothing)=
-
- 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
-
-***** =T orElse(T otherValue)=
-
- A value to use when Maybe is Nothing.
-
- #+BEGIN_SRC java
- final Integer value = Maybe.maybe(getValue())
- .orElse(1);
- #+END_SRC
-
-***** =T orElseGet(Supplier otherValueSupplier)=
-
- Provide a value to use when Maybe is Nothing.
-
- #+BEGIN_SRC java
- final Integer value = Maybe.maybe(getValue())
- .orElseGet(() -> getDefaultValue());
- #+END_SRC
-
-***** =void orElseThrow(Supplier error)=
-
- Throw the exception if the Maybe is a Nothing.
-
- #+BEGIN_SRC java
- final Integer value = Maybe.maybe(getValue())
- .orElseThrow(() -> new RuntimeException("error"));
- #+END_SRC
-
-***** =Maybe peek(Consumer consumer)=
-
- 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 Maybe maybe = Maybe.maybe(getValue())
- .peek(v -> v.foo());
- #+END_SRC
-
-***** =void ifNothing(Runnable runnable)=
-
- 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
-
-*** Result
- :PROPERTIES:
- :CUSTOM_ID: result
- :END:
-
- 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.
+*** =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.io.IOException;
+ import java.util.function.Function;
- class ResultExample implements Runnable {
+ public class ResultMonadTest implements WithAssertions {
- public static void main(final String[] args) {
- new ResultExample().run();
+ 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);
}
- @Override
- public void run() {
- Result.of(() -> callRiskyMethod())
- .flatMap(state -> doSomething(state))
- .match(
- success -> System.out.println(success),
- error -> error.printStackTrace()
- );
+ @Test
+ public void leftIdentity() {
+ assertThat(
+ r(v).flatMap(f)
+ ).isEqualTo(
+ f.apply(v)
+ );
}
- private String callRiskyMethod() throws IOException {
- return "I'm fine";
+ @Test
+ public void rightIdentity() {
+ assertThat(
+ r(v).flatMap(x -> r(x))
+ ).isEqualTo(
+ r(v)
+ );
}
- private Result doSomething(final String state) {
- return Result.of(() -> state + ", it's all good.");
+ @Test
+ public void associativity() {
+ assertThat(
+ r(v).flatMap(f).flatMap(g)
+ ).isEqualTo(
+ r(v).flatMap(x -> f.apply(x).flatMap(g))
+ );
}
}
#+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=.
- 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=.
+*** Static Constructors
-**** =Result= is a Monad
+**** =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
+
+
+** 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
- Checks if the Result is an error.
- #+BEGIN_SRC java
- final boolean isError = Result.of(() -> getValue())
- .isError();
- #+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/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")
+ );
+ }
+}
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);
+ }
+ }
+}