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:
parent
a9d28c9950
commit
9a6c58c835
10 changed files with 864 additions and 263 deletions
203
README.md
203
README.md
|
@ -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
703
README.org
Normal 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
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
@ -128,14 +133,15 @@ 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 e the exception to throw
|
||||
* @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.
|
||||
*/
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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))
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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(
|
||||
|
|
46
src/test/java/net/kemitix/mon/OptionalMonadTest.java
Normal file
46
src/test/java/net/kemitix/mon/OptionalMonadTest.java
Normal 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))
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -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))
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue