Merge branch 'enhance-result'
* enhance-result: Result satisfies the three Monad Laws Maybe satisfies the three Monad Laws Collapse Maybe{Optional,Stream} into Maybe Remove Maybe.fromOptional(Optional) Add Result.toString() implementations Add Result.toMaybe() and Result.invert() Suppress warning for unchecked cast Add Result.orElseThrow() Add Result.fromMaybe(Maybe, Supplier<Exception>) Added Result.maybe() Add Result.map()
This commit is contained in:
commit
7e92129dfc
13 changed files with 581 additions and 168 deletions
|
@ -55,6 +55,8 @@ class Example {
|
|||
|
||||
### Maybe
|
||||
|
||||
A Monad.
|
||||
|
||||
A non-final substitute for Optional with `peek()` and `stream()` methods.
|
||||
|
||||
```java
|
||||
|
@ -99,6 +101,8 @@ class Test {
|
|||
|
||||
### 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.
|
||||
|
||||
|
|
|
@ -36,7 +36,6 @@ import java.util.stream.Stream;
|
|||
* A Maybe where a value is present.
|
||||
*
|
||||
* @param <T> the type of the content
|
||||
*
|
||||
* @author Paul Campbell (pcampbell@kemitix.net)
|
||||
*/
|
||||
@RequiredArgsConstructor(access = AccessLevel.PROTECTED)
|
||||
|
@ -45,7 +44,12 @@ final class Just<T> implements Maybe<T> {
|
|||
private final T value;
|
||||
|
||||
@Override
|
||||
public <R> Maybe<?> map(final Function<T, R> f) {
|
||||
public <R> Maybe<R> flatMap(Function<T, Maybe<R>> f) {
|
||||
return f.apply(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <R> Maybe<R> map(final Function<T, R> f) {
|
||||
return new Just<>(f.apply(value));
|
||||
}
|
||||
|
||||
|
|
|
@ -24,21 +24,27 @@ package net.kemitix.mon.maybe;
|
|||
import lombok.NonNull;
|
||||
import net.kemitix.mon.Functor;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* A value that may or may not be present.
|
||||
*
|
||||
* @param <T> the type of the content of the Just
|
||||
*
|
||||
* @author Paul Campbell (pcampbell@kemitix.net)
|
||||
*/
|
||||
public interface Maybe<T> extends Functor<T, Maybe<?>>, MaybeStream<T>, MaybeOptional<T> {
|
||||
@SuppressWarnings("methodcount")
|
||||
public interface Maybe<T> extends Functor<T, Maybe<?>> {
|
||||
|
||||
/**
|
||||
* Create a Maybe for the value that is present.
|
||||
*
|
||||
* @param value the value, not null
|
||||
* @param <T> the type of the value
|
||||
*
|
||||
* @return a Maybe of the value
|
||||
*/
|
||||
static <T> Maybe<T> just(@NonNull final T value) {
|
||||
|
@ -49,7 +55,6 @@ public interface Maybe<T> extends Functor<T, Maybe<?>>, MaybeStream<T>, MaybeOpt
|
|||
* Create a Maybe for a lack of a value.
|
||||
*
|
||||
* @param <T> the type of the missing value
|
||||
*
|
||||
* @return an empty Maybe
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
|
@ -62,7 +67,6 @@ public interface Maybe<T> extends Functor<T, Maybe<?>>, MaybeStream<T>, MaybeOpt
|
|||
*
|
||||
* @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
|
||||
*/
|
||||
static <T> Maybe<T> maybe(final T value) {
|
||||
|
@ -72,4 +76,70 @@ public interface Maybe<T> extends Functor<T, Maybe<?>>, MaybeStream<T>, MaybeOpt
|
|||
return just(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Monad binder maps the Maybe into another Maybe using the binder method f.
|
||||
*
|
||||
* @param f the mapper function
|
||||
* @param <R> the type of the value in the final maybe
|
||||
* @return a Maybe with the mapped value
|
||||
*/
|
||||
<R> Maybe<R> flatMap(Function<T, Maybe<R>> f);
|
||||
|
||||
@Override
|
||||
<R> Maybe<R> map(Function<T, R> f);
|
||||
|
||||
/**
|
||||
* Provide a value to use when Maybe is Nothing.
|
||||
*
|
||||
* @param supplier supplier for an alternate value
|
||||
* @return a Maybe
|
||||
*/
|
||||
T orElseGet(Supplier<T> supplier);
|
||||
|
||||
/**
|
||||
* A value to use when Maybe is Nothing.
|
||||
*
|
||||
* @param otherValue an alternate value
|
||||
* @return a Maybe
|
||||
*/
|
||||
T orElse(T otherValue);
|
||||
|
||||
/**
|
||||
* Convert the Maybe to an {@link Optional}.
|
||||
*
|
||||
* @return an Optional containing a value for a Just, or empty for a Nothing
|
||||
*/
|
||||
Optional<T> toOptional();
|
||||
|
||||
/**
|
||||
* Throw the exception if the Maybe is a Nothing.
|
||||
*
|
||||
* @param e the exception to throw
|
||||
* @throws Exception if the Maybe is a Nothing
|
||||
*/
|
||||
@SuppressWarnings("illegalthrows")
|
||||
void orElseThrow(Supplier<Exception> e) throws Exception;
|
||||
|
||||
/**
|
||||
* Converts the Maybe into either a single value stream or and empty stream.
|
||||
*
|
||||
* @return a Stream containing the value or nothing.
|
||||
*/
|
||||
Stream<T> stream();
|
||||
|
||||
/**
|
||||
* Filter a Maybe by the predicate, replacing with Nothing when it fails.
|
||||
*
|
||||
* @param predicate the test
|
||||
* @return the Maybe, or Nothing if the test returns false
|
||||
*/
|
||||
Maybe<T> filter(Predicate<T> predicate);
|
||||
|
||||
/**
|
||||
* Provide the value within the Maybe, if it exists, to the Supplier, and returns the Maybe.
|
||||
*
|
||||
* @param consumer the Consumer to the value if present
|
||||
* @return the Maybe
|
||||
*/
|
||||
Maybe<T> peek(Consumer<T> consumer);
|
||||
}
|
||||
|
|
|
@ -1,71 +0,0 @@
|
|||
/**
|
||||
* 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.maybe;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* Enables emulation parts of and conversion to the Java Optional class for Maybe.
|
||||
*
|
||||
* @param <T> the type of the content of the Just
|
||||
*
|
||||
* @author Paul Campbell (pcampbell@kemitix.net)
|
||||
*/
|
||||
interface MaybeOptional<T> {
|
||||
|
||||
/**
|
||||
* Provide a value to use when Maybe is Nothing.
|
||||
*
|
||||
* @param supplier supplier for an alternate value
|
||||
*
|
||||
* @return a Maybe
|
||||
*/
|
||||
T orElseGet(Supplier<T> supplier);
|
||||
|
||||
/**
|
||||
* A value to use when Maybe is Nothing.
|
||||
*
|
||||
* @param otherValue an alternate value
|
||||
*
|
||||
* @return a Maybe
|
||||
*/
|
||||
T orElse(T otherValue);
|
||||
|
||||
/**
|
||||
* Convert the Maybe to an {@link Optional}.
|
||||
*
|
||||
* @return an Optional containing a value for a Just, or empty for a Nothing
|
||||
*/
|
||||
Optional<T> toOptional();
|
||||
|
||||
/**
|
||||
* Throw the exception if the Maybe is a Nothing.
|
||||
*
|
||||
* @param e the exception to throw
|
||||
*
|
||||
* @throws Exception if the Maybe is a Nothing
|
||||
*/
|
||||
@SuppressWarnings("illegalthrows")
|
||||
void orElseThrow(Supplier<Exception> e) throws Exception;
|
||||
|
||||
}
|
|
@ -1,62 +0,0 @@
|
|||
/**
|
||||
* 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.maybe;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* Enables emulation parts of and conversion to the Java Stream class for Maybe.
|
||||
*
|
||||
* @param <T> the type of the content of the Just
|
||||
*
|
||||
* @author Paul Campbell (pcampbell@kemitix.net)
|
||||
*/
|
||||
interface MaybeStream<T> {
|
||||
|
||||
/**
|
||||
* Converts the Maybe into either a single value stream or and empty stream.
|
||||
*
|
||||
* @return a Stream containing the value or nothing.
|
||||
*/
|
||||
Stream<T> stream();
|
||||
|
||||
/**
|
||||
* Filter a Maybe by the predicate, replacing with Nothing when it fails.
|
||||
*
|
||||
* @param predicate the test
|
||||
*
|
||||
* @return the Maybe, or Nothing if the test returns false
|
||||
*/
|
||||
Maybe<T> filter(Predicate<T> predicate);
|
||||
|
||||
/**
|
||||
* Provide the value within the Maybe, if it exists, to the Supplier, and returns the Maybe.
|
||||
*
|
||||
* @param consumer the Consumer to the value if present
|
||||
*
|
||||
* @return the Maybe
|
||||
*/
|
||||
Maybe<T> peek(Consumer<T> consumer);
|
||||
|
||||
}
|
|
@ -32,7 +32,6 @@ import java.util.stream.Stream;
|
|||
* A Maybe where no value is present.
|
||||
*
|
||||
* @param <T> the type of the missing content
|
||||
*
|
||||
* @author Paul Campbell (pcampbell@kemitix.net)
|
||||
*/
|
||||
final class Nothing<T> implements Maybe<T> {
|
||||
|
@ -40,8 +39,14 @@ final class Nothing<T> implements Maybe<T> {
|
|||
static final Maybe<?> INSTANCE = new Nothing<>();
|
||||
|
||||
@Override
|
||||
public <R> Maybe<?> map(final Function<T, R> f) {
|
||||
return this;
|
||||
public <R> Maybe<R> flatMap(Function<T, Maybe<R>> f) {
|
||||
return Maybe.nothing();
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <R> Maybe<R> map(final Function<T, R> f) {
|
||||
return (Maybe<R>) INSTANCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -22,9 +22,11 @@
|
|||
package net.kemitix.mon.result;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import net.kemitix.mon.maybe.Maybe;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
/**
|
||||
* An Error Result.
|
||||
|
@ -51,8 +53,28 @@ class Err<T> implements Result<T> {
|
|||
return Result.error(error);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <R> Result<R> map(final Function<T, R> f) {
|
||||
return Result.error(error);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void match(final Consumer<T> onSuccess, final Consumer<Throwable> onError) {
|
||||
onError.accept(error);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<Maybe<T>> maybe(final Predicate<T> predicate) {
|
||||
return Result.error(error);
|
||||
}
|
||||
|
||||
@Override
|
||||
public T orElseThrow() throws Throwable {
|
||||
throw error;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("Result.Error{error=%s}", error);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,23 +21,51 @@
|
|||
|
||||
package net.kemitix.mon.result;
|
||||
|
||||
import net.kemitix.mon.maybe.Maybe;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* An Either type for holding a result or an error (exception).
|
||||
* An Either type for holding a result or an error (Throwable).
|
||||
*
|
||||
* @param <T> the type of the result when a success
|
||||
*
|
||||
* @author Paul Campbell (pcampbell@kemitix.net)
|
||||
*/
|
||||
@SuppressWarnings("methodcount")
|
||||
public interface Result<T> {
|
||||
|
||||
/**
|
||||
* Creates a Result from the Maybe, where the Result will be an error if the Maybe is Nothing.
|
||||
*
|
||||
* @param maybe the Maybe the might contain the value of the Result
|
||||
* @param error the error that will be the Result if maybe is Nothing
|
||||
* @param <T> the type of the Maybe and the Result
|
||||
* @return a Result containing the value of the Maybe when it is a Just, or the error when it is Nothing
|
||||
*/
|
||||
static <T> Result<T> fromMaybe(final Maybe<T> maybe, final Supplier<Throwable> error) {
|
||||
return maybe.map(Result::ok)
|
||||
.orElseGet(() -> Result.error(error.get()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a Result for an error.
|
||||
*
|
||||
* @param error the error (Throwable)
|
||||
* @param <T> the type had the result been a success
|
||||
* @return an error Result
|
||||
*/
|
||||
static <T> Result<T> error(final Throwable error) {
|
||||
return new Err<>(error);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a Result for a success.
|
||||
*
|
||||
* @param value the value
|
||||
* @param <T> the type of the value
|
||||
* @param <T> the type of the value
|
||||
* @return a successful Result
|
||||
*/
|
||||
static <T> Result<T> ok(final T value) {
|
||||
|
@ -45,14 +73,32 @@ public interface Result<T> {
|
|||
}
|
||||
|
||||
/**
|
||||
* Create a Result for an error.
|
||||
* Creates a Result from the Maybe, where the Result will be an error if the Maybe is Nothing.
|
||||
*
|
||||
* @param error the error (exception)
|
||||
* @param <T> the type had the result been a success
|
||||
* @return an error Result
|
||||
* @param result the Result the might contain the value of the Result
|
||||
* @param <T> the type of the Maybe and the Result
|
||||
* @return a Result containing the value of the Maybe when it is a Just, or the error when it is Nothing
|
||||
*/
|
||||
static <T> Result<T> error(final Throwable error) {
|
||||
return new Err<>(error);
|
||||
@SuppressWarnings("illegalcatch")
|
||||
static <T> Maybe<T> toMaybe(final Result<T> result) {
|
||||
try {
|
||||
return Maybe.just(result.orElseThrow());
|
||||
} catch (final Throwable throwable) {
|
||||
return Maybe.nothing();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Swaps the inner Result of a Maybe, so that a Result is on the outside.
|
||||
* @param maybeResult the Maybe the contains a Result
|
||||
* @param <T> the type of the value that may be in the Result
|
||||
* @return a Result containing a Maybe, the value in the Maybe was the value in a successful Result within the
|
||||
* original Maybe. If the original Maybe is Nothing, the Result will contain Nothing. If the original Result was an
|
||||
* error, then the Result will also be an error.
|
||||
*/
|
||||
static <T> Result<Maybe<T>> invert(final Maybe<Result<T>> maybeResult) {
|
||||
return maybeResult.orElseGet(() -> Result.ok(null))
|
||||
.flatMap(value -> Result.ok(Maybe.maybe(value)));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -73,18 +119,42 @@ public interface Result<T> {
|
|||
* Returns a new Result consisting of the result of applying the function to the contents of the Result.
|
||||
*
|
||||
* @param f the mapping function the produces a Result
|
||||
* @param <R> the type of the result of the mapping function
|
||||
*
|
||||
* @param <R> the type of the value withing the Result of the mapping function
|
||||
* @return a Result
|
||||
*/
|
||||
<R> Result<R> flatMap(Function<T, Result<R>> f);
|
||||
|
||||
/**
|
||||
* Applies the functions to the value of a successful result, while doing nothing with an error.
|
||||
*
|
||||
* @param f the mapping function to produce the new value
|
||||
* @param <R> the type of the result of the mapping function
|
||||
* @return a Result
|
||||
*/
|
||||
<R> Result<R> map(Function<T, R> f);
|
||||
|
||||
/**
|
||||
* Matches the Result, either success or error, and supplies the appropriate Consumer with the value or error.
|
||||
*
|
||||
* @param onSuccess the Consumer to pass the value of a successful Result to
|
||||
* @param onError the Consumer to pass the error from an error Result to
|
||||
* @param onError the Consumer to pass the error from an error Result to
|
||||
*/
|
||||
void match(Consumer<T> onSuccess, Consumer<Throwable> onError);
|
||||
|
||||
/**
|
||||
* Wraps the value within the Result in a Maybe, either a Just if the predicate is true, or Nothing.
|
||||
*
|
||||
* @param predicate the test to decide
|
||||
* @return a Result containing a Maybe that may or may not contain a value
|
||||
*/
|
||||
Result<Maybe<T>> maybe(Predicate<T> predicate);
|
||||
|
||||
/**
|
||||
* Extracts the successful value from the result, or throws the error Throwable.
|
||||
*
|
||||
* @return the value if a success
|
||||
* @throws Throwable the result is an error
|
||||
*/
|
||||
@SuppressWarnings("illegalthrows")
|
||||
T orElseThrow() throws Throwable;
|
||||
}
|
||||
|
|
|
@ -22,9 +22,12 @@
|
|||
package net.kemitix.mon.result;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import net.kemitix.mon.maybe.Maybe;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
/**
|
||||
* A Successful Result.
|
||||
|
@ -51,8 +54,41 @@ class Success<T> implements Result<T> {
|
|||
return f.apply(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <R> Result<R> map(final Function<T, R> f) {
|
||||
return Result.ok(f.apply(value));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void match(final Consumer<T> onSuccess, final Consumer<Throwable> onError) {
|
||||
onSuccess.accept(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<Maybe<T>> maybe(final Predicate<T> predicate) {
|
||||
if (predicate.test(value)) {
|
||||
return Result.ok(Maybe.just(value));
|
||||
}
|
||||
return Result.ok(Maybe.nothing());
|
||||
}
|
||||
|
||||
@Override
|
||||
public T orElseThrow() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
return other instanceof Success && Objects.equals(value, ((Success) other).value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("Result.Success{value=%s}", value);
|
||||
}
|
||||
}
|
||||
|
|
39
src/test/java/net/kemitix/mon/MaybeMonadTest.java
Normal file
39
src/test/java/net/kemitix/mon/MaybeMonadTest.java
Normal file
|
@ -0,0 +1,39 @@
|
|||
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 {
|
||||
|
||||
@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));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rightIdentity() {
|
||||
//given
|
||||
final Maybe<Integer> maybe = Maybe.maybe(1);
|
||||
//then
|
||||
assertThat(maybe.flatMap(Maybe::maybe)).isEqualTo(maybe);
|
||||
}
|
||||
|
||||
@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)));
|
||||
}
|
||||
|
||||
}
|
|
@ -10,9 +10,7 @@ import java.util.concurrent.atomic.AtomicInteger;
|
|||
import java.util.function.Predicate;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static net.kemitix.mon.maybe.Maybe.just;
|
||||
import static net.kemitix.mon.maybe.Maybe.maybe;
|
||||
import static net.kemitix.mon.maybe.Maybe.nothing;
|
||||
import static net.kemitix.mon.maybe.Maybe.*;
|
||||
|
||||
public class MaybeTest implements WithAssertions {
|
||||
|
||||
|
@ -89,7 +87,6 @@ public class MaybeTest implements WithAssertions {
|
|||
assertThat(Optional.empty().map(Maybe::just).orElseGet(Maybe::nothing)).isEqualTo(nothing());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void peek() {
|
||||
final AtomicInteger ref = new AtomicInteger(0);
|
||||
|
@ -126,4 +123,47 @@ public class MaybeTest implements WithAssertions {
|
|||
//then
|
||||
assertThat(stream).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void justFlatMap() {
|
||||
//given
|
||||
final Maybe<Integer> just1 = Maybe.just(1);
|
||||
final Maybe<Integer> just2 = Maybe.just(2);
|
||||
//when
|
||||
final Maybe<Integer> result = just1.flatMap(v1 ->
|
||||
just2.flatMap(v2 ->
|
||||
Maybe.maybe(v1 + v2)
|
||||
));
|
||||
//then
|
||||
assertThat(result.toOptional()).contains(3);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nothingFlatMap() {
|
||||
//given
|
||||
final Maybe<Integer> nothing1 = Maybe.nothing();
|
||||
final Maybe<Integer> nothing2 = Maybe.nothing();
|
||||
//when
|
||||
final Maybe<Integer> result = nothing1.flatMap(v1 ->
|
||||
nothing2.flatMap(v2 ->
|
||||
Maybe.maybe(v1 + v2)
|
||||
));
|
||||
//then
|
||||
assertThat(result.toOptional()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void justNothingFlatMap() {
|
||||
//given
|
||||
final Maybe<Integer> just1 = Maybe.just(1);
|
||||
final Maybe<Integer> nothing2 = Maybe.nothing();
|
||||
//when
|
||||
final Maybe<Integer> result = just1.flatMap(v1 ->
|
||||
nothing2.flatMap(v2 ->
|
||||
Maybe.maybe(v1 + v2)
|
||||
));
|
||||
//then
|
||||
assertThat(result.toOptional()).isEmpty();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
39
src/test/java/net/kemitix/mon/ResultMonadTest.java
Normal file
39
src/test/java/net/kemitix/mon/ResultMonadTest.java
Normal file
|
@ -0,0 +1,39 @@
|
|||
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 {
|
||||
|
||||
@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));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rightIdentity() {
|
||||
//given
|
||||
final Result<Integer> result = Result.ok(1);
|
||||
//then
|
||||
assertThat(result.flatMap(Result::ok)).isEqualTo(result);
|
||||
}
|
||||
|
||||
@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)));
|
||||
}
|
||||
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
package net.kemitix.mon;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import net.kemitix.mon.maybe.Maybe;
|
||||
import net.kemitix.mon.result.Result;
|
||||
import org.assertj.core.api.WithAssertions;
|
||||
import org.junit.Test;
|
||||
|
@ -105,6 +106,202 @@ public class ResultTest implements WithAssertions {
|
|||
assertThat(flatMap.isError()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void success_whenMap_isSuccess() {
|
||||
//given
|
||||
final Result<Integer> okResult = Result.ok(1);
|
||||
//when
|
||||
final Result<String> result = okResult.map(value -> String.valueOf(value));
|
||||
//then
|
||||
assertThat(result.isOkay()).isTrue();
|
||||
result.match(
|
||||
success -> assertThat(success).isEqualTo("1"),
|
||||
error -> fail("not an error")
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void error_whenMap_isError() {
|
||||
//given
|
||||
final RuntimeException exception = new RuntimeException();
|
||||
final Result<Integer> errorResult = Result.error(exception);
|
||||
//when
|
||||
final Result<String> result = errorResult.map(value -> String.valueOf(value));
|
||||
//then
|
||||
assertThat(result.isError()).isTrue();
|
||||
result.match(
|
||||
success -> fail("not an success"),
|
||||
error -> assertThat(error).isSameAs(exception)
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void successMaybe_whenPasses_isSuccessJust() {
|
||||
//given
|
||||
final Result<Integer> okResult = Result.ok(1);
|
||||
//when
|
||||
final Result<Maybe<Integer>> maybeResult = okResult.maybe(value -> value >= 0);
|
||||
//then
|
||||
assertThat(maybeResult.isOkay()).isTrue();
|
||||
maybeResult.match(
|
||||
success -> assertThat(success.toOptional()).contains(1),
|
||||
error -> fail("not an error")
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void successMaybe_whenFails_isSuccessNothing() {
|
||||
//given
|
||||
final Result<Integer> okResult = Result.ok(1);
|
||||
//when
|
||||
final Result<Maybe<Integer>> maybeResult = okResult.maybe(value -> value >= 4);
|
||||
//then
|
||||
assertThat(maybeResult.isOkay()).isTrue();
|
||||
maybeResult.match(
|
||||
success -> assertThat(success.toOptional()).isEmpty(),
|
||||
error -> fail("not an error")
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void errorMaybe_whenPasses_isError() {
|
||||
//given
|
||||
final RuntimeException exception = new RuntimeException();
|
||||
final Result<Integer> errorResult = Result.error(exception);
|
||||
//when
|
||||
final Result<Maybe<Integer>> maybeResult = errorResult.maybe(value -> value >= 0);
|
||||
//then
|
||||
assertThat(maybeResult.isError()).isTrue();
|
||||
maybeResult.match(
|
||||
success -> fail("not a success"),
|
||||
error -> assertThat(error).isSameAs(exception)
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void errorMaybe_whenFails_isError() {
|
||||
//given
|
||||
final RuntimeException exception = new RuntimeException();
|
||||
final Result<Integer> errorResult = Result.error(exception);
|
||||
//when
|
||||
final Result<Maybe<Integer>> maybeResult = errorResult.maybe(value -> value >= 4);
|
||||
//then
|
||||
assertThat(maybeResult.isError()).isTrue();
|
||||
maybeResult.match(
|
||||
success -> fail("not a success"),
|
||||
error -> assertThat(error).isSameAs(exception)
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void justMaybe_isSuccess() {
|
||||
//given
|
||||
final Maybe<Integer> just = Maybe.just(1);
|
||||
//when
|
||||
final Result<Integer> result = Result.fromMaybe(just, () -> new RuntimeException());
|
||||
//then
|
||||
assertThat(result.isOkay()).isTrue();
|
||||
result.match(
|
||||
success -> assertThat(success).isEqualTo(1),
|
||||
error -> fail("not an error")
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nothingMaybe_isError() {
|
||||
//given
|
||||
final Maybe<Object> nothing = Maybe.nothing();
|
||||
final RuntimeException exception = new RuntimeException();
|
||||
//when
|
||||
final Result<Object> result = Result.fromMaybe(nothing, () -> exception);
|
||||
//then
|
||||
assertThat(result.isError()).isTrue();
|
||||
result.match(
|
||||
success -> fail("not a success"),
|
||||
error -> assertThat(error).isSameAs(exception)
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void success_toMaybe_isJust() {
|
||||
//given
|
||||
final Result<Integer> ok = Result.ok(1);
|
||||
//when
|
||||
final Maybe<Integer> maybe = Result.toMaybe(ok);
|
||||
//then
|
||||
assertThat(maybe.toOptional()).contains(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void error_toMaybe_isNothing() {
|
||||
//given
|
||||
final Result<Object> error = Result.error(new RuntimeException());
|
||||
//when
|
||||
final Maybe<Object> maybe = Result.toMaybe(error);
|
||||
//then
|
||||
assertThat(maybe.toOptional()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void success_whenOrElseThrow_isValue() throws Throwable {
|
||||
//given
|
||||
final Result<Integer> ok = Result.ok(1);
|
||||
//when
|
||||
final Integer value = ok.orElseThrow();
|
||||
//then
|
||||
assertThat(value).isEqualTo(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void error_whenOrElseThrow_throws() {
|
||||
//given
|
||||
final RuntimeException exception = new RuntimeException();
|
||||
final Result<Integer> error = Result.error(exception);
|
||||
//when
|
||||
assertThatThrownBy(() -> error.orElseThrow())
|
||||
.isSameAs(exception);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void JustSuccess_invert_thenSuccessJust() {
|
||||
//given
|
||||
final Maybe<Result<Integer>> justSuccess = Maybe.just(Result.ok(1));
|
||||
//when
|
||||
final Result<Maybe<Integer>> result = Result.invert(justSuccess);
|
||||
//then
|
||||
result.match(
|
||||
success -> assertThat(success.toOptional()).contains(1),
|
||||
error -> fail("Not an error")
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void JustError_invert_thenError() {
|
||||
//given
|
||||
final RuntimeException exception = new RuntimeException();
|
||||
final Maybe<Result<Object>> justError = Maybe.just(Result.error(exception));
|
||||
//when
|
||||
final Result<Maybe<Object>> result = Result.invert(justError);
|
||||
//then
|
||||
result.match(
|
||||
success -> fail("Not a success"),
|
||||
error -> assertThat(error).isSameAs(exception)
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void Nothing_invert_thenSuccessNothing() {
|
||||
//given
|
||||
final Maybe<Result<Integer>> nothing = Maybe.nothing();
|
||||
//when
|
||||
final Result<Maybe<Integer>> result = Result.invert(nothing);
|
||||
//then
|
||||
result.match(
|
||||
success -> assertThat(success.toOptional()).isEmpty(),
|
||||
error -> fail("Not an error")
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void useCase_whenOkay_thenReturnSuccess() {
|
||||
//given
|
||||
|
@ -151,6 +348,26 @@ public class ResultTest implements WithAssertions {
|
|||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void success_toString() {
|
||||
//given
|
||||
final Result<Integer> ok = Result.ok(1);
|
||||
//when
|
||||
final String toString = ok.toString();
|
||||
//then
|
||||
assertThat(toString).contains("Result.Success{value=1}");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void err_toString() {
|
||||
//given
|
||||
final Result<Integer> error = Result.error(new RuntimeException("failed"));
|
||||
//when
|
||||
final String toString = error.toString();
|
||||
//then
|
||||
assertThat(toString).contains("Result.Error{error=java.lang.RuntimeException: failed}");
|
||||
}
|
||||
|
||||
@RequiredArgsConstructor
|
||||
private static class UseCase {
|
||||
|
||||
|
@ -175,20 +392,20 @@ public class ResultTest implements WithAssertions {
|
|||
calculateAverage(adjustedIntFromFile1, intFromFile2))));
|
||||
}
|
||||
|
||||
private Result<Double> calculateAverage(final Integer val1, final Integer val2) {
|
||||
return Result.ok((double) (val1 + val2) / 2);
|
||||
}
|
||||
|
||||
private Result<Integer> adjustValue(Integer value) {
|
||||
return Result.ok(value + 2);
|
||||
}
|
||||
|
||||
private Result<Integer> readIntFromFile(String fileName) {
|
||||
private Result<Integer> readIntFromFile(final String fileName) {
|
||||
if (okay) {
|
||||
return Result.ok(fileName.length());
|
||||
}
|
||||
return Result.error(new RuntimeException(fileName));
|
||||
}
|
||||
|
||||
private Result<Integer> adjustValue(final Integer value) {
|
||||
return Result.ok(value + 2);
|
||||
}
|
||||
|
||||
private Result<Double> calculateAverage(final Integer val1, final Integer val2) {
|
||||
return Result.ok((double) (val1 + val2) / 2);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue