Merge pull request #38 from kemitix/either
Add `Either<L,R>` and add `TypeAlias<T>.flatMap()`
This commit is contained in:
commit
a53db86452
9 changed files with 1210 additions and 556 deletions
|
@ -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
|
||||
------
|
||||
|
|
357
README.org
357
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
|
||||
<dependency>
|
||||
|
@ -39,15 +34,8 @@
|
|||
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
|
||||
|
@ -131,10 +119,127 @@
|
|||
}
|
||||
#+END_SRC
|
||||
|
||||
*** Maybe
|
||||
:PROPERTIES:
|
||||
:CUSTOM_ID: maybe
|
||||
:END:
|
||||
*** =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<Integer, AnAlias<Integer>> f = i -> a(i * 2);
|
||||
private final Function<Integer, AnAlias<Integer>> g = i -> a(i + 6);
|
||||
|
||||
private static AnAlias<Integer> 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).<Integer, AnAlias<Integer>>flatMap(x -> a(x))
|
||||
).isEqualTo(
|
||||
a(v)
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rightIdentity_explicitValue() {
|
||||
final AnAlias<Integer> 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<T> extends TypeAlias<T> {
|
||||
private AnAlias(T value) {
|
||||
super(value);
|
||||
}
|
||||
|
||||
static <T> AnAlias<T> of(T value) {
|
||||
return new AnAlias<>(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
#+END_SRC
|
||||
|
||||
|
||||
*** Instance Methods
|
||||
|
||||
**** =final <R> R map(final Function<T, R> 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<Integer> {
|
||||
private StudentId(Integer value) {
|
||||
super(value);
|
||||
}
|
||||
static StudentId of(Integer id) {
|
||||
return new StudentId(id);
|
||||
}
|
||||
}
|
||||
#+END_SRC
|
||||
|
||||
|
||||
**** =final <R, U extends TypeAlias<R>> U flatMap(final Function<T, U> 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<String> {
|
||||
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:
|
||||
|
@ -184,7 +289,7 @@
|
|||
=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;
|
||||
|
@ -235,9 +340,10 @@
|
|||
}
|
||||
#+END_SRC
|
||||
|
||||
**** Static Constructors
|
||||
|
||||
***** =static <T> Maybe<T> maybe(T value)=
|
||||
*** Static Constructors
|
||||
|
||||
**** =static <T> Maybe<T> maybe(T value)=
|
||||
|
||||
Create a Maybe for the value that may or may not be present.
|
||||
|
||||
|
@ -248,7 +354,8 @@
|
|||
final Maybe<Integer> nothing = Maybe.maybe(null);
|
||||
#+END_SRC
|
||||
|
||||
***** =static <T> Maybe<T> just(T value)=
|
||||
|
||||
**** =static <T> Maybe<T> just(T value)=
|
||||
|
||||
Create a Maybe for the value that is present.
|
||||
|
||||
|
@ -260,7 +367,8 @@
|
|||
final Maybe<Integer> just = Maybe.just(1);
|
||||
#+END_SRC
|
||||
|
||||
***** =static <T> Maybe<T> nothing()=
|
||||
|
||||
**** =static <T> Maybe<T> nothing()=
|
||||
|
||||
Create a Maybe for a lack of a value.
|
||||
|
||||
|
@ -268,9 +376,10 @@
|
|||
final Maybe<Integer> nothing = Maybe.nothing();
|
||||
#+END_SRC
|
||||
|
||||
**** Instance Methods
|
||||
|
||||
***** =Maybe<T> filter(Predicate<T> predicate)=
|
||||
*** Instance Methods
|
||||
|
||||
**** =Maybe<T> filter(Predicate<T> predicate)=
|
||||
|
||||
Filter a Maybe by the predicate, replacing with Nothing when it fails.
|
||||
|
||||
|
@ -279,7 +388,8 @@
|
|||
.filter(v -> v % 2 == 0);
|
||||
#+END_SRC
|
||||
|
||||
***** =<R> Maybe<R> map(Function<T,R> f)=
|
||||
|
||||
**** =<R> Maybe<R> map(Function<T,R> f)=
|
||||
|
||||
Applies the function to the value within the Maybe, returning the result within another Maybe.
|
||||
|
||||
|
@ -288,7 +398,8 @@
|
|||
.map(v -> v * 100);
|
||||
#+END_SRC
|
||||
|
||||
***** =<R> Maybe<R> flatMap(Function<T,Maybe<R>> f)=
|
||||
|
||||
**** =<R> Maybe<R> flatMap(Function<T,Maybe<R>> f)=
|
||||
|
||||
Applies the function to the value within the =Maybe=, resulting in another =Maybe=, then flattens the resulting =Maybe<Maybe<T>>= into =Maybe<T>=.
|
||||
|
||||
|
@ -299,7 +410,8 @@
|
|||
.flatMap(v -> Maybe.maybe(getValueFor(v)));
|
||||
#+END_SRC
|
||||
|
||||
***** =void match(Consumer<T> just, Runnable nothing)=
|
||||
|
||||
**** =void match(Consumer<T> just, Runnable nothing)=
|
||||
|
||||
Matches the Maybe, either just or nothing, and performs either the Consumer, for Just, or Runnable for nothing.
|
||||
|
||||
|
@ -311,7 +423,8 @@
|
|||
);
|
||||
#+END_SRC
|
||||
|
||||
***** =T orElse(T otherValue)=
|
||||
|
||||
**** =T orElse(T otherValue)=
|
||||
|
||||
A value to use when Maybe is Nothing.
|
||||
|
||||
|
@ -320,7 +433,8 @@
|
|||
.orElse(1);
|
||||
#+END_SRC
|
||||
|
||||
***** =T orElseGet(Supplier<T> otherValueSupplier)=
|
||||
|
||||
**** =T orElseGet(Supplier<T> otherValueSupplier)=
|
||||
|
||||
Provide a value to use when Maybe is Nothing.
|
||||
|
||||
|
@ -329,7 +443,8 @@
|
|||
.orElseGet(() -> getDefaultValue());
|
||||
#+END_SRC
|
||||
|
||||
***** =void orElseThrow(Supplier<Exception> error)=
|
||||
|
||||
**** =void orElseThrow(Supplier<Exception> error)=
|
||||
|
||||
Throw the exception if the Maybe is a Nothing.
|
||||
|
||||
|
@ -338,7 +453,8 @@
|
|||
.orElseThrow(() -> new RuntimeException("error"));
|
||||
#+END_SRC
|
||||
|
||||
***** =Maybe<T> peek(Consumer<T> consumer)=
|
||||
|
||||
**** =Maybe<T> peek(Consumer<T> consumer)=
|
||||
|
||||
Provide the value within the Maybe, if it exists, to the Consumer, and returns this Maybe. Conceptually equivalent to the idea of =ifPresent(...)=.
|
||||
|
||||
|
@ -347,7 +463,8 @@
|
|||
.peek(v -> v.foo());
|
||||
#+END_SRC
|
||||
|
||||
***** =void ifNothing(Runnable runnable)=
|
||||
|
||||
**** =void ifNothing(Runnable runnable)=
|
||||
|
||||
Run the runnable if the Maybe is a Nothing, otherwise do nothing.
|
||||
|
||||
|
@ -356,7 +473,8 @@
|
|||
.ifNothing(() -> doSomething());
|
||||
#+END_SRC
|
||||
|
||||
***** =Stream<T> stream()=
|
||||
|
||||
**** =Stream<T> stream()=
|
||||
|
||||
Converts the Maybe into either a single value stream or an empty stream.
|
||||
|
||||
|
@ -365,7 +483,8 @@
|
|||
.stream();
|
||||
#+END_SRC
|
||||
|
||||
***** =boolean isJust()=
|
||||
|
||||
**** =boolean isJust()=
|
||||
|
||||
Checks if the Maybe is a Just.
|
||||
|
||||
|
@ -374,7 +493,8 @@
|
|||
.isJust();
|
||||
#+END_SRC
|
||||
|
||||
***** =boolean isNothing()=
|
||||
|
||||
**** =boolean isNothing()=
|
||||
|
||||
Checks if the Maybe is Nothing.
|
||||
|
||||
|
@ -383,7 +503,8 @@
|
|||
.isNothing();
|
||||
#+END_SRC
|
||||
|
||||
***** =Optional<T> toOptional()=
|
||||
|
||||
**** =Optional<T> toOptional()=
|
||||
|
||||
Convert the Maybe to an Optional.
|
||||
|
||||
|
@ -392,10 +513,8 @@
|
|||
.toOptional();
|
||||
#+END_SRC
|
||||
|
||||
*** Result
|
||||
:PROPERTIES:
|
||||
:CUSTOM_ID: result
|
||||
:END:
|
||||
|
||||
** Result
|
||||
|
||||
Allows handling error conditions without the need to catch exceptions.
|
||||
|
||||
|
@ -447,7 +566,7 @@
|
|||
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;
|
||||
|
@ -498,9 +617,10 @@
|
|||
}
|
||||
#+END_SRC
|
||||
|
||||
**** Static Constructors
|
||||
|
||||
***** =static <T> Result<T> of(Callable<T> callable)=
|
||||
*** Static Constructors
|
||||
|
||||
**** =static <T> Result<T> of(Callable<T> callable)=
|
||||
|
||||
Create a Result for a output of the Callable.
|
||||
|
||||
|
@ -515,7 +635,8 @@
|
|||
final Result<Integer> error = Result.of(() -> {throw new RuntimeException();});
|
||||
#+END_SRC
|
||||
|
||||
***** =static <T> Result<T> ok(T value)=
|
||||
|
||||
**** =static <T> Result<T> ok(T value)=
|
||||
|
||||
Create a Result for a success.
|
||||
|
||||
|
@ -525,7 +646,8 @@
|
|||
final Result<Integer> okay = Result.ok(1);
|
||||
#+END_SRC
|
||||
|
||||
***** =static <T> Result<T> error(Throwable error)=
|
||||
|
||||
**** =static <T> Result<T> error(Throwable error)=
|
||||
|
||||
Create a Result for an error.
|
||||
|
||||
|
@ -533,14 +655,15 @@
|
|||
final Result<Integer> error = Result.error(new RuntimeException());
|
||||
#+END_SRC
|
||||
|
||||
**** Static Methods
|
||||
|
||||
*** Static Methods
|
||||
|
||||
These static methods provide integration with the =Maybe= class.
|
||||
|
||||
#+BEGIN_SRC java
|
||||
#+END_SRC
|
||||
|
||||
***** =static <T> Maybe<T> toMaybe(Result<T> result)=
|
||||
**** =static <T> Maybe<T> toMaybe(Result<T> 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
|
||||
|
@ -551,7 +674,8 @@
|
|||
final Maybe<Integer> maybe = Result.toMaybe(result);
|
||||
#+END_SRC
|
||||
|
||||
***** =static <T> Result<T> fromMaybe(Maybe<T> maybe, Supplier<Throwable> error)=
|
||||
|
||||
**** =static <T> Result<T> fromMaybe(Maybe<T> maybe, Supplier<Throwable> error)=
|
||||
|
||||
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
|
||||
|
@ -562,7 +686,8 @@
|
|||
final Result<Integer> result = Result.fromMaybe(maybe, () -> new NoSuchFileException("filename"));
|
||||
#+END_SRC
|
||||
|
||||
***** =static <T> Result<Maybe<T>> invert(Maybe<Result<T>> maybeResult)=
|
||||
|
||||
**** =static <T> Result<Maybe<T>> invert(Maybe<Result<T>> maybeResult)=
|
||||
|
||||
Swaps the =Result= within a =Maybe=, so that =Result= contains a =Maybe=.
|
||||
|
||||
|
@ -571,7 +696,8 @@
|
|||
final Result<Maybe<Integer>> result = Result.invert(maybe);
|
||||
#+END_SRC
|
||||
|
||||
***** =static <T,R> Result<Maybe<R>> flatMapMaybe(Result<Maybe<T>> maybeResult, Function<Maybe<T>,Result<Maybe<R>>> f)=
|
||||
|
||||
**** =static <T,R> Result<Maybe<R>> flatMapMaybe(Result<Maybe<T>> maybeResult, Function<Maybe<T>,Result<Maybe<R>>> f)=
|
||||
|
||||
Applies the function to the contents of a Maybe within the Result.
|
||||
|
||||
|
@ -580,9 +706,10 @@
|
|||
final Result<Maybe<Integer>> maybeResult = Result.flatMapMaybe(result, maybe -> Result.of(() -> maybe.map(v -> v * 2)));
|
||||
#+END_SRC
|
||||
|
||||
**** Instance Methods
|
||||
|
||||
***** <R> Result<R> map(Function<T,R> f)
|
||||
*** Instance Methods
|
||||
|
||||
**** =<R> Result<R> map(Function<T,R> f)=
|
||||
|
||||
Applies the function to the value within the Functor, returning the result
|
||||
within a Functor.
|
||||
|
@ -592,7 +719,8 @@
|
|||
.map(v -> String.valueOf(v));
|
||||
#+END_SRC
|
||||
|
||||
***** <R> Result<R> flatMap(Function<T,Result<R>> f)
|
||||
|
||||
**** =<R> Result<R> flatMap(Function<T,Result<R>> f)=
|
||||
|
||||
Returns a new Result consisting of the result of applying the function to
|
||||
the contents of the Result.
|
||||
|
@ -602,7 +730,8 @@
|
|||
.flatMap(v -> Result.of(() -> String.valueOf(v)));
|
||||
#+END_SRC
|
||||
|
||||
***** <R> Result<R> andThen(Function<T,Callable<R>> f)
|
||||
|
||||
**** =<R> Result<R> andThen(Function<T,Callable<R>> f)=
|
||||
|
||||
Maps a Success Result to another Result using a Callable that is able to
|
||||
throw a checked exception.
|
||||
|
@ -612,7 +741,8 @@
|
|||
.andThen(v -> () -> {throw new IOException();});
|
||||
#+END_SRC
|
||||
|
||||
***** void match(Consumer<T> onSuccess, Consumer<Throwable> onError)
|
||||
|
||||
**** =void match(Consumer<T> onSuccess, Consumer<Throwable> onError)=
|
||||
|
||||
Matches the Result, either success or error, and supplies the appropriate
|
||||
Consumer with the value or error.
|
||||
|
@ -625,7 +755,8 @@
|
|||
);
|
||||
#+END_SRC
|
||||
|
||||
***** Result<T> recover(Function<Throwable,Result<T>> f)
|
||||
|
||||
**** =Result<T> recover(Function<Throwable,Result<T>> f)=
|
||||
|
||||
Provide a way to attempt to recover from an error state.
|
||||
|
||||
|
@ -634,7 +765,8 @@
|
|||
.recover(e -> Result.of(() -> getSafeValue(e)));
|
||||
#+END_SRC
|
||||
|
||||
***** Result<T> peek(Consumer<T> consumer)
|
||||
|
||||
**** =Result<T> peek(Consumer<T> consumer)=
|
||||
|
||||
Provide the value within the Result, if it is a success, to the Consumer,
|
||||
and returns this Result.
|
||||
|
@ -644,7 +776,8 @@
|
|||
.peek(v -> System.out.println(v));
|
||||
#+END_SRC
|
||||
|
||||
***** Result<T> thenWith(Function<T,WithResultContinuation<T>> f)
|
||||
|
||||
**** =Result<T> thenWith(Function<T,WithResultContinuation<T>> f)=
|
||||
|
||||
Perform the continuation with the current Result value then return the
|
||||
current Result, assuming there was no error in the continuation.
|
||||
|
@ -655,7 +788,8 @@
|
|||
.thenWith(v -> () -> {throw new IOException();});
|
||||
#+END_SRC
|
||||
|
||||
***** Result<Maybe<T>> maybe(Predicate<T> predicate)
|
||||
|
||||
**** =Result<Maybe<T>> maybe(Predicate<T> predicate)=
|
||||
|
||||
Wraps the value within the Result in a Maybe, either a Just if the
|
||||
predicate is true, or Nothing.
|
||||
|
@ -665,7 +799,8 @@
|
|||
.maybe(v -> v % 2 == 0);
|
||||
#+END_SRC
|
||||
|
||||
***** T orElseThrow()
|
||||
|
||||
**** =T orElseThrow()=
|
||||
|
||||
Extracts the successful value from the result, or throws the error
|
||||
Throwable.
|
||||
|
@ -675,7 +810,8 @@
|
|||
.orElseThrow();
|
||||
#+END_SRC
|
||||
|
||||
***** void onError(Consumer<Throwable> errorConsumer)
|
||||
|
||||
**** =void onError(Consumer<Throwable> errorConsumer)=
|
||||
|
||||
A handler for error states.
|
||||
|
||||
|
@ -684,7 +820,8 @@
|
|||
.onError(e -> handleError(e));
|
||||
#+END_SRC
|
||||
|
||||
***** boolean isOkay()
|
||||
|
||||
**** =boolean isOkay()=
|
||||
|
||||
Checks if the Result is a success.
|
||||
|
||||
|
@ -693,7 +830,8 @@
|
|||
.isOkay();
|
||||
#+END_SRC
|
||||
|
||||
***** boolean isError()
|
||||
|
||||
**** =boolean isError()=
|
||||
|
||||
Checks if the Result is an error.
|
||||
|
||||
|
@ -701,3 +839,94 @@
|
|||
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 <L, R> Either<L, R> left(final L l)=
|
||||
|
||||
Create a new Either holding a left value.
|
||||
|
||||
#+BEGIN_SRC java
|
||||
final Either<Integer, String> left = Either.left(getIntegerValue());
|
||||
#+END_SRC
|
||||
|
||||
|
||||
**** =static <L, R> Either<L, R> right(final R r)=
|
||||
|
||||
Create a new Either holding a right value.
|
||||
|
||||
#+BEGIN_SRC java
|
||||
final Either<Integer, String> 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.<Integer, String>left(getIntegerValue()).isLeft();
|
||||
final boolean rightIsLeft = Either.<Integer, String>right(getStringValue()).isLeft();
|
||||
#+END_SRC
|
||||
|
||||
|
||||
**** =boolean isRight()=
|
||||
|
||||
Checks if the Either holds a right value.
|
||||
|
||||
#+BEGIN_SRC java
|
||||
final boolean leftIsRight = Either.<Integer, String>left(getIntegerValue()).isRight();
|
||||
final boolean rightIsRight = Either.<Integer, String>right(getStringValue()).isRight();
|
||||
#+END_SRC
|
||||
|
||||
|
||||
**** =void match(Consumer<L> onLeft, Consumer<R> onRight)=
|
||||
|
||||
Matches the Either, invoking the correct Consumer.
|
||||
|
||||
#+BEGIN_SRC java
|
||||
Either.<Integer, String>left(getIntegerValue())
|
||||
.match(
|
||||
left -> handleIntegerValue(left),
|
||||
right -> handleStringValue(right)
|
||||
);
|
||||
#+END_SRC
|
||||
|
||||
|
||||
**** =<T> Either<T, R> mapLeft(Function<L, T> f)=
|
||||
|
||||
Map the function across the left value.
|
||||
|
||||
#+BEGIN_SRC java
|
||||
final Either<Double, String> either = Either.<Integer, String>left(getIntegerValue())
|
||||
.mapLeft(i -> i.doubleValue());
|
||||
#+END_SRC
|
||||
|
||||
|
||||
**** =<T> Either<L, T> mapRight(Function<R, T> f)=
|
||||
|
||||
Map the function across the right value.
|
||||
|
||||
#+BEGIN_SRC java
|
||||
final Either<Integer, String> either = Either.<Integer, String>left(getIntegerValue())
|
||||
.mapRight(s -> s + "x");
|
||||
#+END_SRC
|
||||
|
||||
|
||||
|
|
|
@ -30,7 +30,6 @@ import java.util.function.Function;
|
|||
* for the type being aliased.</p>
|
||||
*
|
||||
* @param <T> the type of the alias
|
||||
*
|
||||
* @author Paul Campbell (pcampbell@kemitix.net)
|
||||
*/
|
||||
@SuppressWarnings("abstractclassname")
|
||||
|
@ -55,13 +54,24 @@ public abstract class TypeAlias<T> {
|
|||
*
|
||||
* @param f the function to create the new value
|
||||
* @param <R> the type of the new value
|
||||
*
|
||||
* @return a TypeAlias
|
||||
* @return the result of the function
|
||||
*/
|
||||
public final <R> R map(final Function<T, R> f) {
|
||||
return f.apply(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Map the TypeAlias into another TypeAlias.
|
||||
*
|
||||
* @param f the function to create the new value
|
||||
* @param <R> the type of the new value within a TypeAlias
|
||||
* @param <U> the type of the TypeAlias superclass containing the new value
|
||||
* @return a TypeAlias
|
||||
*/
|
||||
public final <R, U extends TypeAlias<R>> U flatMap(final Function<T, U> f) {
|
||||
return f.apply(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final int hashCode() {
|
||||
return value.hashCode();
|
||||
|
|
|
@ -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 <L> the type of the Either for left value
|
||||
* @param <R> the type of the Either for right value
|
||||
* @author Paul Campbell (pcampbell@kemitix.net)
|
||||
*/
|
||||
public interface Either<L, R> {
|
||||
|
||||
/**
|
||||
* Create a new Either holding a left value.
|
||||
*
|
||||
* @param l the left value
|
||||
* @param <L> the type of the left value
|
||||
* @param <R> the type of the right value
|
||||
* @return a Either holding the left value
|
||||
*/
|
||||
static <L, R> Either<L, R> left(final L l) {
|
||||
return new Left<>(l);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new Either holding a right value.
|
||||
*
|
||||
* @param r the right value
|
||||
* @param <L> the type of the left value
|
||||
* @param <R> the type of the right value
|
||||
* @return a Either holding the right value
|
||||
*/
|
||||
static <L, R> Either<L, R> 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<L> onLeft, Consumer<R> onRight);
|
||||
|
||||
/**
|
||||
* Map the function across the left value.
|
||||
*
|
||||
* @param f the function to apply to any left value
|
||||
* @param <T> the type to change the left value to
|
||||
* @return a new Either
|
||||
*/
|
||||
<T> Either<T, R> mapLeft(Function<L, T> f);
|
||||
|
||||
/**
|
||||
* Map the function across the right value.
|
||||
*
|
||||
* @param f the function to apply to any right value
|
||||
* @param <T> the type to change the right value to
|
||||
* @return a new Either
|
||||
*/
|
||||
<T> Either<L, T> mapRight(Function<R, T> f);
|
||||
}
|
61
src/main/java/net/kemitix/mon/experimental/either/Left.java
Normal file
61
src/main/java/net/kemitix/mon/experimental/either/Left.java
Normal file
|
@ -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 <L> the type of the Either for left value
|
||||
* @param <R> the type of the Either for right value
|
||||
* @author Paul Campbell (pcampbell@kemitix.net)
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
class Left<L, R> implements Either<L, R> {
|
||||
|
||||
@Getter
|
||||
private final boolean left = true;
|
||||
@Getter
|
||||
private final boolean right = false;
|
||||
|
||||
private final L value;
|
||||
|
||||
@Override
|
||||
public void match(final Consumer<L> onLeft, final Consumer<R> onRight) {
|
||||
onLeft.accept(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Either<T, R> mapLeft(final Function<L, T> f) {
|
||||
return new Left<>(f.apply(value));
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Either<L, T> mapRight(final Function<R, T> f) {
|
||||
return new Left<>(value);
|
||||
}
|
||||
}
|
61
src/main/java/net/kemitix/mon/experimental/either/Right.java
Normal file
61
src/main/java/net/kemitix/mon/experimental/either/Right.java
Normal file
|
@ -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 <L> the type of the Either for left value
|
||||
* @param <R> the type of the Either for right value
|
||||
* @author Paul Campbell (pcampbell@kemitix.net)
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
class Right<L, R> implements Either<L, R> {
|
||||
|
||||
@Getter
|
||||
private final boolean left = false;
|
||||
@Getter
|
||||
private final boolean right = true;
|
||||
|
||||
private final R value;
|
||||
|
||||
@Override
|
||||
public void match(final Consumer<L> onLeft, final Consumer<R> onRight) {
|
||||
onRight.accept(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Either<T, R> mapLeft(final Function<L, T> f) {
|
||||
return new Right<>(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Either<L, T> mapRight(final Function<R, T> f) {
|
||||
return new Right<>(f.apply(value));
|
||||
}
|
||||
}
|
|
@ -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;
|
114
src/test/java/net/kemitix/mon/EitherTest.java
Normal file
114
src/test/java/net/kemitix/mon/EitherTest.java
Normal file
|
@ -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<Integer, String> either = Either.left(1);
|
||||
//then
|
||||
assertThat(either.isLeft()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void whenLeft_isNotRight() {
|
||||
//when
|
||||
final Either<Integer, String> either = Either.left(1);
|
||||
//then
|
||||
assertThat(either.isRight()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void whenRight_isNotLeft() {
|
||||
//when
|
||||
final Either<Integer, String> either = Either.right("1");
|
||||
//then
|
||||
assertThat(either.isLeft()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void whenRight_isRight() {
|
||||
//when
|
||||
final Either<Integer, String> either = Either.right("1");
|
||||
//then
|
||||
assertThat(either.isRight()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void whenLeft_matchLeft() {
|
||||
//given
|
||||
final Either<Integer, String> 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<Integer, String> 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<Integer, String> either = Either.left(2);
|
||||
//when
|
||||
final Either<Integer, String> 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<Integer, String> either = Either.right("2");
|
||||
//when
|
||||
final Either<Integer, String> 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<Integer, String> either = Either.left(2);
|
||||
//when
|
||||
final Either<Integer, String> 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<Integer, String> either = Either.right("2");
|
||||
//when
|
||||
final Either<Integer, String> result = either.mapLeft(l -> l * 2);
|
||||
//then
|
||||
result.match(
|
||||
left -> fail("Not a left"),
|
||||
right -> assertThat(right).isEqualTo("2")
|
||||
);
|
||||
}
|
||||
}
|
55
src/test/java/net/kemitix/mon/TypeAliasMonadTest.java
Normal file
55
src/test/java/net/kemitix/mon/TypeAliasMonadTest.java
Normal file
|
@ -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<Integer, AnAlias<Integer>> f = i -> a(i * 2);
|
||||
private final Function<Integer, AnAlias<Integer>> g = i -> a(i + 6);
|
||||
|
||||
private static AnAlias<Integer> 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<Integer> 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<T> extends TypeAlias<T> {
|
||||
private AnAlias(T value) {
|
||||
super(value);
|
||||
}
|
||||
|
||||
static <T> AnAlias<T> of(T value) {
|
||||
return new AnAlias<>(value);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue