Rewrite README and convert to org-mode

* MaybeTest use static import Maybe.*
* Maybe.orElseThrow() now returns value when is a Just
This commit is contained in:
Paul Campbell 2018-07-14 20:12:06 +01:00
parent a9d28c9950
commit 9a6c58c835
10 changed files with 864 additions and 263 deletions

203
README.md
View file

@ -1,203 +0,0 @@
Mon
===
TypeAlias, Maybe and Result for Java
[![Sonatype Nexus (Releases)](https://img.shields.io/nexus/r/https/oss.sonatype.org/net.kemitix/mon.svg?style=for-the-badge)](https://oss.sonatype.org/content/repositories/releases/net/kemitix/mon/)
[![Maven Central](https://img.shields.io/maven-central/v/net.kemitix/mon.svg?style=for-the-badge)](https://search.maven.org/#search|ga|1|g%3A"net.kemitix"%20AND%20a%3A"mon")
[![SonarQube Coverage](https://img.shields.io/sonar/https/sonarcloud.io/net.kemitix%3Amon/coverage.svg?style=for-the-badge)](https://sonarcloud.io/dashboard?id=net.kemitix%3Amon)
[![SonarQube Tech Debt](https://img.shields.io/sonar/https/sonarcloud.io/net.kemitix%3Amon/tech_debt.svg?style=for-the-badge)](https://sonarcloud.io/dashboard?id=net.kemitix%3Amon)
[![Codacy grade](https://img.shields.io/codacy/grade/d57096b0639d496aba9a7e43e7cf5b4c.svg?style=for-the-badge)](https://app.codacy.com/project/kemitix/mon/dashboard)
[![jPeek](http://i.jpeek.org/net.kemitix/mon/badge.svg)](http://i.jpeek.org/net.kemitix/mon/index.html)
## Maven
```xml
<dependency>
<groupId>net.kemitix</groupId>
<artifactId>mon</artifactId>
<version>RELEASE</version>
</dependency>
```
The latest version should be shown above with the nexus and maven-central badges.
## Usage
### TypeAlias
More of a type-wrapper really. It's as close as I could get to a Haskell type alias in Java.
```java
class Goal extends TypeAlias<String> {
private Goal(final String goal) {
super(goal);
}
public static Goal of(final String goal) {
return new Goal(goal);
}
}
```
```java
class Example {
Goal goal = Goal.of("goal");
void foo(final Goal goal) {
System.out.println("The goal is " + goal.getValue());
}
}
```
### Maybe
A Monad.
A non-final substitute for Optional with `peek()` and `stream()` methods.
```java
class Test {
@Test
public void maybeTests() {
// Constructors: maybe(T), just(T) and nothing()
assertThat(Maybe.maybe(null)).isEqualTo(Maybe.nothing());
assertThat(Maybe.maybe(1)).isEqualTo(Maybe.just(1));
// .orElseGet(Supplier<T>)
assertThat(Maybe.nothing().orElseGet(() -> 1)).isEqualTo(1);
assertThat(Maybe.just(1).orElseGet(() -> 2)).isEqualTo(1);
// .orElse(Supplier<T>)
assertThat(Maybe.nothing().orElse(1)).isEqualTo(1);
assertThat(Maybe.just(1).orElse(2)).isEqualTo(1);
// .filter(Predicate<T>)
assertThat(Maybe.just(1).filter(v -> v > 2)).isEqualTo(Maybe.nothing());
assertThat(Maybe.just(3).filter(v -> v > 2)).isEqualTo(Maybe.just(3));
assertThat(Maybe.just(1).toOptional()).isEqualTo(Optional.of(1));
assertThat(Maybe.nothing().toOptional()).isEqualTo(Optional.empty());
// .fromOptional(Optional<T>) is deprecated
assertThat(Maybe.fromOptional(Optional.of(1))).isEqualTo(Maybe.just(1));
assertThat(Maybe.fromOptional(Optional.empty())).isEqualTo(Maybe.nothing());
// An alternative to using .fromOptional(Optional<T>)
assertThat(Optional.of(1).map(Maybe::just).orElse(Maybe::nothing)).isEqualTo(Maybe.just(1));
assertThat(Optional.empty().map(Maybe::just).orElse(Maybe::nothing)).isEqualTo(Maybe.nothing());
// .peek(Consumer<T>)
final AtomicInteger reference = new AtomicInteger(0);
assertThat(Maybe.just(1).peek(reference::set)).isEqualTo(Maybe.just(1));
assertThat(reference).hasValue(1);
assertThat(Maybe.nothing().peek(v -> reference.incrementAndGet())).isEqualTo(Maybe.nothing());
assertThat(reference).hasValue(1);
// .orElseThrow(Supplier<Exception>)
assertThatCode(() -> Maybe.just(1).orElseThrow(IllegalStateException::new)).doesNotThrowAnyException();
assertThatThrownBy(() -> Maybe.nothing().orElseThrow(IllegalStateException::new)).isInstanceOf(IllegalStateException.class);
// .stream()
assertThat(Maybe.just(1).stream()).containsExactly(1);
assertThat(Maybe.nothing().stream()).isEmpty();
}
}
```
### Result
A Monad.
A container for method return values that may raise an Exception. Useful for when a checked exceptions can't be added
to the method signature.
```java
package net.kemitix.mon;
import net.kemitix.mon.result.Result;
import java.io.IOException;
class ResultExample implements Runnable {
public static void main(String[] args) {
new ResultExample().run();
}
@Override
public void run() {
System.out.println("run");
final Result<Integer> goodResult = goodMethod();
if (goodResult.isOkay()) {
doGoodThings();
}
if (goodResult.isError()) {
notCalled(0);
}
goodResult.flatMap(number -> convertToString(number))
.flatMap(str -> stringLength(str))
.match(
success -> System.out.format("Length is %s%n", success),
error -> System.out.println("Count not determine length")
);
final Result<Integer> badResult = badMethod();
badResult.match(
success -> notCalled(success),
error -> handleError(error)
);
}
private Result<Integer> goodMethod() {
System.out.println("goodMethod");
return Result.ok(1);
}
private void doGoodThings() {
System.out.println("doGoodThings");
}
private void notCalled(final Integer success) {
System.out.println("notCalled");
}
private Result<String> convertToString(final Integer number) {
System.out.println("convertToString");
return Result.ok(String.valueOf(number));
}
private Result<Integer> stringLength(final String value) {
System.out.println("stringLength");
if (value == null) {
return Result.error(new NullPointerException("value is null"));
}
return Result.ok(value.length());
}
// doesn't need to declare "throws IOException"
private Result<Integer> badMethod() {
System.out.println("badMethod");
return Result.error(new IOException("error"));
}
private void handleError(final Throwable error) {
System.out.println("handleError");
throw new RuntimeException("Handled exception", error);
}
}
```
Will output:
```text
run
goodMethod
doGoodThings
convertToString
stringLength
Length is 1
badMethod
handleError
Exception in thread "main" java.lang.RuntimeException: Handled exception
at net.kemitix.mon.ResultExample.handleError(ResultExample.java:72)
at net.kemitix.mon.ResultExample.lambda$run$5(ResultExample.java:34)
at net.kemitix.mon.result.Err.match(Err.java:56)
at net.kemitix.mon.ResultExample.run(ResultExample.java:32)
at net.kemitix.mon.ResultExample.main(ResultExample.java:10)
Caused by: java.io.IOException: error
at net.kemitix.mon.ResultExample.badMethod(ResultExample.java:67)
at net.kemitix.mon.ResultExample.run(ResultExample.java:31)
... 1 more
```

703
README.org Normal file
View file

@ -0,0 +1,703 @@
* Mon
:PROPERTIES:
:CUSTOM_ID: mon
:END:
** TypeAlias, Maybe and Result for Java.
[[https://oss.sonatype.org/content/repositories/releases/net/kemitix/mon][file:https://img.shields.io/nexus/r/https/oss.sonatype.org/net.kemitix/mon.svg?style=for-the-badge]]
[[https://search.maven.org/#search%7Cga%7C1%7Cg%3A%22net.kemitix%22%20AND%20a%3A%22mon%22][file:https://img.shields.io/maven-central/v/net.kemitix/mon.svg?style=for-the-badge]]
[[https://sonarcloud.io/dashboard?id=net.kemitix%3Amon][file:https://img.shields.io/sonar/https/sonarcloud.io/net.kemitix%3Amon/coverage.svg?style=for-the-badge#.svg]]
[[https://sonarcloud.io/dashboard?id=net.kemitix%3Amon][file:https://img.shields.io/sonar/https/sonarcloud.io/net.kemitix%3Amon/tech_debt.svg?style=for-the-badge#.svg]]
[[https://sonarcloud.io/dashboard?id=net.kemitix%3Amon][file:https://sonarcloud.io/api/project_badges/measure?project=net.kemitix%3Amon&metric=sqale_rating#.svg]]
[[https://sonarcloud.io/dashboard?id=net.kemitix%3Amon][file:https://sonarcloud.io/api/project_badges/measure?project=net.kemitix%3Amon&metric=alert_status#.svg]]
[[https://sonarcloud.io/dashboard?id=net.kemitix%3Amon][file:https://sonarcloud.io/api/project_badges/measure?project=net.kemitix%3Amon&metric=reliability_rating#.svg]]
[[https://sonarcloud.io/dashboard?id=net.kemitix%3Amon][file:https://sonarcloud.io/api/project_badges/measure?project=net.kemitix%3Amon&metric=security_rating#.svg]]
[[https://sonarcloud.io/dashboard?id=net.kemitix%3Amon][file:https://sonarcloud.io/api/project_badges/measure?project=net.kemitix%3Amon&metric=sqale_index#.svg]]
[[https://sonarcloud.io/dashboard?id=net.kemitix%3Amon][file:https://sonarcloud.io/api/project_badges/measure?project=net.kemitix%3Amon&metric=vulnerabilities#.svg]]
[[https://sonarcloud.io/dashboard?id=net.kemitix%3Amon][file:https://sonarcloud.io/api/project_badges/measure?project=net.kemitix%3Amon&metric=bugs#.svg]]
[[https://sonarcloud.io/dashboard?id=net.kemitix%3Amon][file:https://sonarcloud.io/api/project_badges/measure?project=net.kemitix%3Amon&metric=code_smells#.svg]]
[[https://sonarcloud.io/dashboard?id=net.kemitix%3Amon][file:https://sonarcloud.io/api/project_badges/measure?project=net.kemitix%3Amon&metric=ncloc#.svg]]
[[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>
<groupId>net.kemitix</groupId>
<artifactId>mon</artifactId>
<version>RELEASE</version>
</dependency>
#+END_SRC
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:
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
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:
- 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<String> {
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<String> 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<Integer, String> validMessage() {
return v -> String.format("Value %d is even", v);
}
private static Predicate<Integer> 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:
#+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<Integer, Maybe<Integer>> f = i -> m(i * 2);
private final Function<Integer, Maybe<Integer>> g = i -> m(i + 6);
private static Maybe<Integer> 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 <T> Maybe<T> 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<Integer> just = Maybe.maybe(1);
final Maybe<Integer> nothing = Maybe.maybe(null);
#+END_SRC
***** =static <T> Maybe<T> 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<Integer> just = Maybe.just(1);
#+END_SRC
***** =static <T> Maybe<T> nothing()=
Create a Maybe for a lack of a value.
#+BEGIN_SRC java
final Maybe<Integer> nothing = Maybe.nothing();
#+END_SRC
**** Instance Methods
***** =Maybe<T> filter(Predicate<T> predicate)=
Filter a Maybe by the predicate, replacing with Nothing when it fails.
#+BEGIN_SRC java
final Maybe<Integer> maybe = Maybe.maybe(getValue())
.filter(v -> v % 2 == 0);
#+END_SRC
***** =<R> Maybe<R> map(Function<T,R> f)=
Applies the function to the value within the Maybe, returning the result within another Maybe.
#+BEGIN_SRC java
final Maybe<Integer> maybe = Maybe.maybe(getValue())
.map(v -> v * 100);
#+END_SRC
***** =<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>=.
Monad binder maps the Maybe into another Maybe using the binder method f
#+BEGIN_SRC java
final Maybe<Integer> maybe = Maybe.maybe(getValue())
.flatMap(v -> Maybe.maybe(getValueFor(v)));
#+END_SRC
***** =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.
#+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<T> 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<Exception> 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<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(...)=.
#+BEGIN_SRC java
final Maybe<Integer> 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<T> stream()=
Converts the Maybe into either a single value stream or an empty stream.
#+BEGIN_SRC java
final Stream<Integer> 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<T> toOptional()=
Convert the Maybe to an Optional.
#+BEGIN_SRC java
final Optional<Integer> 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.
#+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<String> doSomething(final String state) {
return Result.of(() -> state + ", it's all good.");
}
}
#+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=.
**** =Result= is a Monad
#+BEGIN_SRC java
package net.kemitix.mon;
import net.kemitix.mon.result.Result;
import org.assertj.core.api.WithAssertions;
import org.junit.Test;
import java.util.function.Function;
public class ResultMonadTest implements WithAssertions {
private final int v = 1;
private final Function<Integer, Result<Integer>> f = i -> r(i * 2);
private final Function<Integer, Result<Integer>> g = i -> r(i + 6);
private static Result<Integer> r(int v) {
return Result.ok(v);
}
@Test
public void leftIdentity() {
assertThat(
r(v).flatMap(f)
).isEqualTo(
f.apply(v)
);
}
@Test
public void rightIdentity() {
assertThat(
r(v).flatMap(x -> r(x))
).isEqualTo(
r(v)
);
}
@Test
public void associativity() {
assertThat(
r(v).flatMap(f).flatMap(g)
).isEqualTo(
r(v).flatMap(x -> f.apply(x).flatMap(g))
);
}
}
#+END_SRC
**** Static Constructors
***** =static <T> Result<T> of(Callable<T> 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
final Result<Integer> okay = Result.of(() -> 1);
final Result<Integer> error = Result.of(() -> {throw new RuntimeException();});
#+END_SRC
***** =static <T> Result<T> ok(T value)=
Create a Result for a success.
Use this where you have a value that you want to place into the Result context.
#+BEGIN_SRC java
final Result<Integer> okay = Result.ok(1);
#+END_SRC
***** =static <T> Result<T> error(Throwable error)=
Create a Result for an error.
#+BEGIN_SRC java
final Result<Integer> error = Result.error(new RuntimeException());
#+END_SRC
**** Static Methods
These static methods provide integration with the =Maybe= class.
#+BEGIN_SRC java
#+END_SRC
***** =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
then the =Maybe= will be nothing.
#+BEGIN_SRC java
final Result<Integer> result = Result.of(() -> getValue());
final Maybe<Integer> maybe = Result.toMaybe(result);
#+END_SRC
***** =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
=Supplier<Throwable>= will provide the error for the =Result=.
#+BEGIN_SRC java
final Maybe<Integer> maybe = Maybe.maybe(getValue());
final Result<Integer> result = Result.fromMaybe(maybe, () -> new NoSuchFileException("filename"));
#+END_SRC
***** =static <T> Result<Maybe<T>> invert(Maybe<Result<T>> maybeResult)=
Swaps the =Result= within a =Maybe=, so that =Result= contains a =Maybe=.
#+BEGIN_SRC java
final Maybe<Result<Integer>> maybe = Maybe.maybe(Result.of(() -> getValue()));
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)=
Applies the function to the contents of a Maybe within the Result.
#+BEGIN_SRC java
final Result<Maybe<Integer>> result = Result.of(() -> Maybe.maybe(getValue()));
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)
Applies the function to the value within the Functor, returning the result
within a Functor.
#+BEGIN_SRC java
final Result<String> result = Result.of(() -> getValue())
.map(v -> String.valueOf(v));
#+END_SRC
***** <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.
#+BEGIN_SRC java
final Result<String> result = Result.of(() -> getValue())
.flatMap(v -> Result.of(() -> String.valueOf(v)));
#+END_SRC
***** <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.
#+BEGIN_SRC java
final Result<String> result = Result.of(() -> getValue())
.andThen(v -> () -> {throw new IOException();});
#+END_SRC
***** 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.
#+BEGIN_SRC java
Result.of(() -> getValue())
.match(
success -> System.out.println(success),
error -> System.err.println("error")
);
#+END_SRC
***** Result<T> recover(Function<Throwable,Result<T>> f)
Provide a way to attempt to recover from an error state.
#+BEGIN_SRC java
final Result<Integer> result = Result.of(() -> getValue())
.recover(e -> Result.of(() -> getSafeValue(e)));
#+END_SRC
***** Result<T> peek(Consumer<T> consumer)
Provide the value within the Result, if it is a success, to the Consumer,
and returns this Result.
#+BEGIN_SRC java
final Result<Integer> result = Result.of(() -> getValue())
.peek(v -> System.out.println(v));
#+END_SRC
***** 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.
#+BEGIN_SRC java
final Result<Integer> result = Result.of(() -> getValue())
.thenWith(v -> () -> System.out.println(v))
.thenWith(v -> () -> {throw new IOException();});
#+END_SRC
***** 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.
#+BEGIN_SRC java
final Result<Maybe<Integer>> result = Result.of(() -> getValue())
.maybe(v -> v % 2 == 0);
#+END_SRC
***** T orElseThrow()
Extracts the successful value from the result, or throws the error
Throwable.
#+BEGIN_SRC java
final Integer result = Result.of(() -> getValue())
.orElseThrow();
#+END_SRC
***** void onError(Consumer<Throwable> errorConsumer)
A handler for error states.
#+BEGIN_SRC java
Result.of(() -> getValue())
.onError(e -> handleError(e));
#+END_SRC
***** boolean isOkay()
Checks if the Result is a success.
#+BEGIN_SRC java
final boolean isOkay = Result.of(() -> getValue())
.isOkay();
#+END_SRC
***** boolean isError()
Checks if the Result is an error.
#+BEGIN_SRC java
final boolean isError = Result.of(() -> getValue())
.isError();
#+END_SRC

View file

@ -97,6 +97,11 @@ final class Just<T> implements Maybe<T> {
return Optional.of(value);
}
@Override
public <X extends Throwable> T orElseThrow(final Supplier<? extends X> e) throws X {
return value;
}
@Override
public Maybe<T> peek(final Consumer<T> consumer) {
consumer.accept(value);
@ -113,11 +118,6 @@ final class Just<T> implements Maybe<T> {
justMatcher.accept(value);
}
@Override
public void orElseThrow(final Supplier<Exception> e) {
// do not throw
}
@Override
public Stream<T> stream() {
return Stream.of(value);

View file

@ -43,6 +43,9 @@ public interface Maybe<T> extends Functor<T, Maybe<?>> {
/**
* Create a Maybe for the value that is present.
*
* <p>The {@literal value} must not be {@literal null} or a {@literal NullPointerException} will be thrown.
* If you can't prove that the value won't be {@literal null} you should use {@link #maybe(Object)} instead.</p>
*
* @param value the value, not null
* @param <T> the type of the value
* @return a Maybe of the value
@ -65,6 +68,8 @@ public interface Maybe<T> extends Functor<T, Maybe<?>> {
/**
* Create a Maybe for the value that may or may not be present.
*
* <p>Where the value is {@literal null}, that is taken as not being present.</p>
*
* @param value the value, may be null
* @param <T> the type of the value
* @return a Maybe, either a Just, or Nothing if value is null
@ -114,7 +119,7 @@ public interface Maybe<T> extends Functor<T, Maybe<?>> {
* A value to use when Maybe is Nothing.
*
* @param otherValue an alternate value
* @return a Maybe
* @return the value of the Maybe if a Just, otherwise the otherValue
*/
T orElse(T otherValue);
@ -129,13 +134,14 @@ public interface Maybe<T> extends Functor<T, Maybe<?>> {
* Throw the exception if the Maybe is a Nothing.
*
* @param e the exception to throw
* @throws Exception if the Maybe is a Nothing
* @param <X> the type of the exception to throw
* @return the value of the Maybe if a Just
* @throws X if the Maybe is nothing
*/
@SuppressWarnings("illegalthrows")
void orElseThrow(Supplier<Exception> e) throws Exception;
<X extends Throwable> T orElseThrow(Supplier<? extends X> e) throws X;
/**
* Converts the Maybe into either a single value stream or and empty stream.
* Converts the Maybe into either a single value stream or an empty stream.
*
* @return a Stream containing the value or nothing.
*/

View file

@ -80,6 +80,11 @@ final class Nothing<T> implements Maybe<T> {
return Optional.empty();
}
@Override
public <X extends Throwable> T orElseThrow(final Supplier<? extends X> e) throws X {
throw e.get();
}
@Override
public Maybe<T> peek(final Consumer<T> consumer) {
return this;
@ -95,11 +100,6 @@ final class Nothing<T> implements Maybe<T> {
nothingMatcher.run();
}
@Override
public void orElseThrow(final Supplier<Exception> e) throws Exception {
throw e.get();
}
@Override
public Stream<T> stream() {
return Stream.empty();

View file

@ -91,7 +91,9 @@ public interface Result<T> extends Functor<T, Result<?>> {
}
/**
* Creates a Result from the Maybe, where the Result will be an error if the Maybe is Nothing.
* Creates a {@link Maybe} from the Result, where the Result is a success, then the Maybe will contain the value.
*
* <p>However, if the Result is an error then the Maybe will be nothing.</p>
*
* @param result the Result the might contain the value of the Result
* @param <T> the type of the Maybe and the Result

View file

@ -8,32 +8,39 @@ import java.util.function.Function;
public class MaybeMonadTest implements WithAssertions {
private final int v = 1;
private final Function<Integer, Maybe<Integer>> f = i -> m(i * 2);
private final Function<Integer, Maybe<Integer>> g = i -> m(i + 6);
private static Maybe<Integer> m(int value) {
return Maybe.maybe(value);
}
@Test
public void leftIdentity() {
//given
final int value = 1;
final Maybe<Integer> maybe = Maybe.maybe(value);
final Function<Integer, Maybe<Integer>> f = i -> Maybe.maybe(i * 2);
//then
assertThat(maybe.flatMap(f)).isEqualTo(f.apply(value));
assertThat(
m(v).flatMap(f)
).isEqualTo(
f.apply(v)
);
}
@Test
public void rightIdentity() {
//given
final Maybe<Integer> maybe = Maybe.maybe(1);
//then
assertThat(maybe.flatMap(Maybe::maybe)).isEqualTo(maybe);
assertThat(
m(v).flatMap(x -> m(x))
).isEqualTo(
m(v)
);
}
@Test
public void associativity() {
//given
final Maybe<Integer> maybe = Maybe.maybe(1);
final Function<Integer, Maybe<Integer>> f = i -> Maybe.maybe(i * 2);
final Function<Integer, Maybe<Integer>> g = i -> Maybe.maybe(i + 6);
//then
assertThat(maybe.flatMap(f).flatMap(g)).isEqualTo(maybe.flatMap(x -> f.apply(x).flatMap(g)));
assertThat(
m(v).flatMap(f).flatMap(g)
).isEqualTo(
m(v).flatMap(x -> f.apply(x).flatMap(g))
);
}
}

View file

@ -51,6 +51,29 @@ public class MaybeTest implements WithAssertions {
assertThat(nothing().map(v -> v)).isEqualTo(nothing());
}
@Test
public void mapToNull_thenJustNull() {
//given
final Maybe<Integer> maybe = just(1);
//when
final Maybe<Object> result = maybe.map(x -> null);
//then
result.match(
just -> assertThat(just).isNull(),
() -> fail("mapped to a null, not a Nothing - use flatMap() to convert to Nothing in null")
);
}
@Test
public void optional_mapToNull_thenJustNull() {
//given
final Optional<Integer> optional = Optional.ofNullable(1);
//when
final Optional<Object> result = optional.map(x -> null);
//then
assertThat(result.isPresent()).isFalse();
}
@Test
public void justHashCode() {
assertThat(just(1).hashCode()).isNotEqualTo(just(2).hashCode());
@ -104,10 +127,20 @@ public class MaybeTest implements WithAssertions {
}
@Test
public void justOrThrow() {
public void justOrThrowDoesNotThrow() {
assertThatCode(() -> just(1).orElseThrow(IllegalStateException::new)).doesNotThrowAnyException();
}
@Test
public void justOrThrowReturnsValue() {
//given
final Maybe<Integer> maybe = just(1);
//when
final Integer result = maybe.orElseThrow(() -> new RuntimeException());
//then
assertThat(result).isEqualTo(1);
}
@Test
public void nothingOrThrow() {
assertThatThrownBy(() -> nothing().orElseThrow(IllegalStateException::new)).isInstanceOf(
@ -133,8 +166,8 @@ public class MaybeTest implements WithAssertions {
@Test
public void justFlatMap() {
//given
final Maybe<Integer> just1 = Maybe.just(1);
final Maybe<Integer> just2 = Maybe.just(2);
final Maybe<Integer> just1 = just(1);
final Maybe<Integer> just2 = just(2);
//when
final Maybe<Integer> result = just1.flatMap(v1 ->
just2.flatMap(v2 ->
@ -147,8 +180,8 @@ public class MaybeTest implements WithAssertions {
@Test
public void nothingFlatMap() {
//given
final Maybe<Integer> nothing1 = Maybe.nothing();
final Maybe<Integer> nothing2 = Maybe.nothing();
final Maybe<Integer> nothing1 = nothing();
final Maybe<Integer> nothing2 = nothing();
//when
final Maybe<Integer> result = nothing1.flatMap(v1 ->
nothing2.flatMap(v2 ->
@ -161,8 +194,8 @@ public class MaybeTest implements WithAssertions {
@Test
public void justNothingFlatMap() {
//given
final Maybe<Integer> just1 = Maybe.just(1);
final Maybe<Integer> nothing2 = Maybe.nothing();
final Maybe<Integer> just1 = just(1);
final Maybe<Integer> nothing2 = nothing();
//when
final Maybe<Integer> result = just1.flatMap(v1 ->
nothing2.flatMap(v2 ->
@ -175,7 +208,7 @@ public class MaybeTest implements WithAssertions {
@Test
public void just_ifNothing_isIgnored() {
//given
final Maybe<Integer> just = Maybe.just(1);
final Maybe<Integer> just = just(1);
final AtomicBoolean capture = new AtomicBoolean(false);
//when
just.ifNothing(() -> capture.set(true));
@ -186,7 +219,7 @@ public class MaybeTest implements WithAssertions {
@Test
public void nothing_ifNothing_isCalled() {
//given
final Maybe<Integer> nothing = Maybe.nothing();
final Maybe<Integer> nothing = nothing();
final AtomicBoolean capture = new AtomicBoolean(false);
//when
nothing.ifNothing(() -> capture.set(true));
@ -197,7 +230,7 @@ public class MaybeTest implements WithAssertions {
@Test
public void just_whenMatch_thenJustTriggers() {
//given
final Maybe<Integer> maybe = Maybe.just(1);
final Maybe<Integer> maybe = just(1);
//then
maybe.match(
just -> assertThat(just).isEqualTo(1),
@ -208,7 +241,7 @@ public class MaybeTest implements WithAssertions {
@Test
public void nothing_whenMatch_thenNothingTriggers() {
//given
final Maybe<Integer> maybe = Maybe.nothing();
final Maybe<Integer> maybe = nothing();
final AtomicBoolean flag = new AtomicBoolean(false);
//when
maybe.match(

View file

@ -0,0 +1,46 @@
package net.kemitix.mon;
import org.assertj.core.api.WithAssertions;
import org.junit.Test;
import java.util.Optional;
import java.util.function.Function;
public class OptionalMonadTest implements WithAssertions {
private final int v = 1;
private final Function<Integer, Optional<Integer>> f = i -> o(i * 2);
private final Function<Integer, Optional<Integer>> g = i -> o(i + 6);
private static Optional<Integer> o(int value) {
return Optional.ofNullable(value);
}
@Test
public void leftIdentity() {
assertThat(
o(v).flatMap(f)
).isEqualTo(
f.apply(v)
);
}
@Test
public void rightIdentity() {
assertThat(
o(v).flatMap(x -> o(x))
).isEqualTo(
o(v)
);
}
@Test
public void associativity() {
assertThat(
o(v).flatMap(f).flatMap(g)
).isEqualTo(
o(v).flatMap(x -> f.apply(x).flatMap(g))
);
}
}

View file

@ -8,32 +8,39 @@ import java.util.function.Function;
public class ResultMonadTest implements WithAssertions {
private final int v = 1;
private final Function<Integer, Result<Integer>> f = i -> r(i * 2);
private final Function<Integer, Result<Integer>> g = i -> r(i + 6);
private static Result<Integer> r(int v) {
return Result.ok(v);
}
@Test
public void leftIdentity() {
//given
final int value = 1;
final Result<Integer> result = Result.ok(value);
final Function<Integer, Result<Integer>> f = i -> Result.ok(i * 2);
//then
assertThat(result.flatMap(f)).isEqualTo(f.apply(value));
assertThat(
r(v).flatMap(f)
).isEqualTo(
f.apply(v)
);
}
@Test
public void rightIdentity() {
//given
final Result<Integer> result = Result.ok(1);
//then
assertThat(result.flatMap(Result::ok)).isEqualTo(result);
assertThat(
r(v).flatMap(x -> r(x))
).isEqualTo(
r(v)
);
}
@Test
public void associativity() {
//given
final Result<Integer> result = Result.ok(1);
final Function<Integer, Result<Integer>> f = i -> Result.ok(i * 2);
final Function<Integer, Result<Integer>> g = i -> Result.ok(i + 6);
//then
assertThat(result.flatMap(f).flatMap(g)).isEqualTo(result.flatMap(x -> f.apply(x).flatMap(g)));
assertThat(
r(v).flatMap(f).flatMap(g)
).isEqualTo(
r(v).flatMap(x -> f.apply(x).flatMap(g))
);
}
}