Merge pull request #38 from kemitix/either

Add `Either<L,R>` and add `TypeAlias<T>.flatMap()`
This commit is contained in:
Paul Campbell 2018-07-16 21:07:06 +01:00 committed by GitHub
commit a53db86452
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 1210 additions and 556 deletions

View file

@ -2,6 +2,7 @@ CHANGELOG
========= =========
0.11.0 0.11.0
------
* Rename `Result.maybeThen()` as `Result.flatMapMaybe()` * Rename `Result.maybeThen()` as `Result.flatMapMaybe()`
* Add `Maybe.match(Consumer,Runnable)` * Add `Maybe.match(Consumer,Runnable)`
@ -9,6 +10,8 @@ CHANGELOG
* Add `Maybe.isNothing()` * Add `Maybe.isNothing()`
* BUG: `Maybe.orElseThrow()` now returns value when a Just * BUG: `Maybe.orElseThrow()` now returns value when a Just
* Rewrite README * Rewrite README
* Add `Either` (experimental)
* Add `flatMap` to `TypeAlias`
0.10.0 0.10.0
------ ------

View file

@ -1,7 +1,4 @@
* Mon * Mon
:PROPERTIES:
:CUSTOM_ID: mon
:END:
** TypeAlias, Maybe and Result for Java. ** 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]] [[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]] [[http://i.jpeek.org/net.kemitix/mon/index.html][file:http://i.jpeek.org/net.kemitix/mon/badge.svg]]
** Maven ** Maven
:PROPERTIES:
:CUSTOM_ID: maven
:END:
#+BEGIN_SRC xml #+BEGIN_SRC xml
<dependency> <dependency>
@ -39,15 +34,8 @@
The latest version should be shown above with the nexus and maven-central 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]]. 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 ** TypeAlias
:PROPERTIES:
:CUSTOM_ID: typealias
:END:
In Haskell it is possible to create an alias for a Type, and to then use 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 that alias with the same behaviour as the original, except that the compiler
@ -131,10 +119,127 @@
} }
#+END_SRC #+END_SRC
*** Maybe *** =TypeAlias= *can* be a Monad
:PROPERTIES:
:CUSTOM_ID: maybe #+BEGIN_SRC java
:END: 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 Allows specifying that a value may or may not be present. Similar to
=Optional=. =Maybe= provides additional methods that =Optional= doesn't: =Optional=. =Maybe= provides additional methods that =Optional= doesn't:
@ -184,7 +289,7 @@
=nothing= drops straight through the map and triggers the Runnable parameter =nothing= drops straight through the map and triggers the Runnable parameter
in the =match= call. in the =match= call.
**** =Maybe= is a Monad: *** =Maybe= is a Monad:
#+BEGIN_SRC java #+BEGIN_SRC java
package net.kemitix.mon; package net.kemitix.mon;
@ -235,9 +340,10 @@
} }
#+END_SRC #+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. Create a Maybe for the value that may or may not be present.
@ -248,7 +354,8 @@
final Maybe<Integer> nothing = Maybe.maybe(null); final Maybe<Integer> nothing = Maybe.maybe(null);
#+END_SRC #+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. Create a Maybe for the value that is present.
@ -260,7 +367,8 @@
final Maybe<Integer> just = Maybe.just(1); final Maybe<Integer> just = Maybe.just(1);
#+END_SRC #+END_SRC
***** =static <T> Maybe<T> nothing()=
**** =static <T> Maybe<T> nothing()=
Create a Maybe for a lack of a value. Create a Maybe for a lack of a value.
@ -268,9 +376,10 @@
final Maybe<Integer> nothing = Maybe.nothing(); final Maybe<Integer> nothing = Maybe.nothing();
#+END_SRC #+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. Filter a Maybe by the predicate, replacing with Nothing when it fails.
@ -279,7 +388,8 @@
.filter(v -> v % 2 == 0); .filter(v -> v % 2 == 0);
#+END_SRC #+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. Applies the function to the value within the Maybe, returning the result within another Maybe.
@ -288,7 +398,8 @@
.map(v -> v * 100); .map(v -> v * 100);
#+END_SRC #+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>=. 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))); .flatMap(v -> Maybe.maybe(getValueFor(v)));
#+END_SRC #+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. Matches the Maybe, either just or nothing, and performs either the Consumer, for Just, or Runnable for nothing.
@ -311,7 +423,8 @@
); );
#+END_SRC #+END_SRC
***** =T orElse(T otherValue)=
**** =T orElse(T otherValue)=
A value to use when Maybe is Nothing. A value to use when Maybe is Nothing.
@ -320,7 +433,8 @@
.orElse(1); .orElse(1);
#+END_SRC #+END_SRC
***** =T orElseGet(Supplier<T> otherValueSupplier)=
**** =T orElseGet(Supplier<T> otherValueSupplier)=
Provide a value to use when Maybe is Nothing. Provide a value to use when Maybe is Nothing.
@ -329,7 +443,8 @@
.orElseGet(() -> getDefaultValue()); .orElseGet(() -> getDefaultValue());
#+END_SRC #+END_SRC
***** =void orElseThrow(Supplier<Exception> error)=
**** =void orElseThrow(Supplier<Exception> error)=
Throw the exception if the Maybe is a Nothing. Throw the exception if the Maybe is a Nothing.
@ -338,7 +453,8 @@
.orElseThrow(() -> new RuntimeException("error")); .orElseThrow(() -> new RuntimeException("error"));
#+END_SRC #+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(...)=. 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()); .peek(v -> v.foo());
#+END_SRC #+END_SRC
***** =void ifNothing(Runnable runnable)=
**** =void ifNothing(Runnable runnable)=
Run the runnable if the Maybe is a Nothing, otherwise do nothing. Run the runnable if the Maybe is a Nothing, otherwise do nothing.
@ -356,7 +473,8 @@
.ifNothing(() -> doSomething()); .ifNothing(() -> doSomething());
#+END_SRC #+END_SRC
***** =Stream<T> stream()=
**** =Stream<T> stream()=
Converts the Maybe into either a single value stream or an empty stream. Converts the Maybe into either a single value stream or an empty stream.
@ -365,7 +483,8 @@
.stream(); .stream();
#+END_SRC #+END_SRC
***** =boolean isJust()=
**** =boolean isJust()=
Checks if the Maybe is a Just. Checks if the Maybe is a Just.
@ -374,7 +493,8 @@
.isJust(); .isJust();
#+END_SRC #+END_SRC
***** =boolean isNothing()=
**** =boolean isNothing()=
Checks if the Maybe is Nothing. Checks if the Maybe is Nothing.
@ -383,7 +503,8 @@
.isNothing(); .isNothing();
#+END_SRC #+END_SRC
***** =Optional<T> toOptional()=
**** =Optional<T> toOptional()=
Convert the Maybe to an Optional. Convert the Maybe to an Optional.
@ -392,10 +513,8 @@
.toOptional(); .toOptional();
#+END_SRC #+END_SRC
*** Result
:PROPERTIES: ** Result
:CUSTOM_ID: result
:END:
Allows handling error conditions without the need to catch exceptions. 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 Result would have ignored the =flatMap= and skipped to the =match()= when it
would have called the error =Consumer=. would have called the error =Consumer=.
**** =Result= is a Monad *** =Result= is a Monad
#+BEGIN_SRC java #+BEGIN_SRC java
package net.kemitix.mon; package net.kemitix.mon;
@ -498,9 +617,10 @@
} }
#+END_SRC #+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. Create a Result for a output of the Callable.
@ -515,7 +635,8 @@
final Result<Integer> error = Result.of(() -> {throw new RuntimeException();}); final Result<Integer> error = Result.of(() -> {throw new RuntimeException();});
#+END_SRC #+END_SRC
***** =static <T> Result<T> ok(T value)=
**** =static <T> Result<T> ok(T value)=
Create a Result for a success. Create a Result for a success.
@ -525,7 +646,8 @@
final Result<Integer> okay = Result.ok(1); final Result<Integer> okay = Result.ok(1);
#+END_SRC #+END_SRC
***** =static <T> Result<T> error(Throwable error)=
**** =static <T> Result<T> error(Throwable error)=
Create a Result for an error. Create a Result for an error.
@ -533,14 +655,15 @@
final Result<Integer> error = Result.error(new RuntimeException()); final Result<Integer> error = Result.error(new RuntimeException());
#+END_SRC #+END_SRC
**** Static Methods
*** Static Methods
These static methods provide integration with the =Maybe= class. These static methods provide integration with the =Maybe= class.
#+BEGIN_SRC java #+BEGIN_SRC java
#+END_SRC #+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 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 the =Maybe= will contain the value. However, if the =Result= is an error
@ -551,7 +674,8 @@
final Maybe<Integer> maybe = Result.toMaybe(result); final Maybe<Integer> maybe = Result.toMaybe(result);
#+END_SRC #+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 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 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")); final Result<Integer> result = Result.fromMaybe(maybe, () -> new NoSuchFileException("filename"));
#+END_SRC #+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=. Swaps the =Result= within a =Maybe=, so that =Result= contains a =Maybe=.
@ -571,7 +696,8 @@
final Result<Maybe<Integer>> result = Result.invert(maybe); final Result<Maybe<Integer>> result = Result.invert(maybe);
#+END_SRC #+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. 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))); final Result<Maybe<Integer>> maybeResult = Result.flatMapMaybe(result, maybe -> Result.of(() -> maybe.map(v -> v * 2)));
#+END_SRC #+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 Applies the function to the value within the Functor, returning the result
within a Functor. within a Functor.
@ -592,7 +719,8 @@
.map(v -> String.valueOf(v)); .map(v -> String.valueOf(v));
#+END_SRC #+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 Returns a new Result consisting of the result of applying the function to
the contents of the Result. the contents of the Result.
@ -602,7 +730,8 @@
.flatMap(v -> Result.of(() -> String.valueOf(v))); .flatMap(v -> Result.of(() -> String.valueOf(v)));
#+END_SRC #+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 Maps a Success Result to another Result using a Callable that is able to
throw a checked exception. throw a checked exception.
@ -612,7 +741,8 @@
.andThen(v -> () -> {throw new IOException();}); .andThen(v -> () -> {throw new IOException();});
#+END_SRC #+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 Matches the Result, either success or error, and supplies the appropriate
Consumer with the value or error. Consumer with the value or error.
@ -625,7 +755,8 @@
); );
#+END_SRC #+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. Provide a way to attempt to recover from an error state.
@ -634,7 +765,8 @@
.recover(e -> Result.of(() -> getSafeValue(e))); .recover(e -> Result.of(() -> getSafeValue(e)));
#+END_SRC #+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, Provide the value within the Result, if it is a success, to the Consumer,
and returns this Result. and returns this Result.
@ -644,7 +776,8 @@
.peek(v -> System.out.println(v)); .peek(v -> System.out.println(v));
#+END_SRC #+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 Perform the continuation with the current Result value then return the
current Result, assuming there was no error in the continuation. current Result, assuming there was no error in the continuation.
@ -655,7 +788,8 @@
.thenWith(v -> () -> {throw new IOException();}); .thenWith(v -> () -> {throw new IOException();});
#+END_SRC #+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 Wraps the value within the Result in a Maybe, either a Just if the
predicate is true, or Nothing. predicate is true, or Nothing.
@ -665,7 +799,8 @@
.maybe(v -> v % 2 == 0); .maybe(v -> v % 2 == 0);
#+END_SRC #+END_SRC
***** T orElseThrow()
**** =T orElseThrow()=
Extracts the successful value from the result, or throws the error Extracts the successful value from the result, or throws the error
Throwable. Throwable.
@ -675,7 +810,8 @@
.orElseThrow(); .orElseThrow();
#+END_SRC #+END_SRC
***** void onError(Consumer<Throwable> errorConsumer)
**** =void onError(Consumer<Throwable> errorConsumer)=
A handler for error states. A handler for error states.
@ -684,7 +820,8 @@
.onError(e -> handleError(e)); .onError(e -> handleError(e));
#+END_SRC #+END_SRC
***** boolean isOkay()
**** =boolean isOkay()=
Checks if the Result is a success. Checks if the Result is a success.
@ -693,7 +830,8 @@
.isOkay(); .isOkay();
#+END_SRC #+END_SRC
***** boolean isError()
**** =boolean isError()=
Checks if the Result is an error. Checks if the Result is an error.
@ -701,3 +839,94 @@
final boolean isError = Result.of(() -> getValue()) final boolean isError = Result.of(() -> getValue())
.isError(); .isError();
#+END_SRC #+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

View file

@ -30,7 +30,6 @@ import java.util.function.Function;
* for the type being aliased.</p> * for the type being aliased.</p>
* *
* @param <T> the type of the alias * @param <T> the type of the alias
*
* @author Paul Campbell (pcampbell@kemitix.net) * @author Paul Campbell (pcampbell@kemitix.net)
*/ */
@SuppressWarnings("abstractclassname") @SuppressWarnings("abstractclassname")
@ -55,13 +54,24 @@ public abstract class TypeAlias<T> {
* *
* @param f the function to create the new value * @param f the function to create the new value
* @param <R> the type of the new value * @param <R> the type of the new value
* * @return the result of the function
* @return a TypeAlias
*/ */
public final <R> R map(final Function<T, R> f) { public final <R> R map(final Function<T, R> f) {
return f.apply(value); 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 @Override
public final int hashCode() { public final int hashCode() {
return value.hashCode(); return value.hashCode();

View file

@ -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);
}

View 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);
}
}

View 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));
}
}

View file

@ -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;

View 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")
);
}
}

View 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);
}
}
}