Result enhancements (#215)

* pom: don’t override java version - use LTS - JDK 11

* result: nest tests

* Add Result.{from(Either|Optional),ofVoid,ok(),onSuccess,applyOver}

* Add Result.flatApplyOver

* result: create separate documentation file

* Add Result.toEither()

* Add Result.onError - by type

* Simplify creation of Result in Result.ofVoid

* Make Result.ofVoid tolerate exceptions and no return statement

* Add missing javadoc for result.VoidCallable

* Bump pitest-junit5-plugin from 0.12 to 0.14 (#187)

* Bump tiles-maven-plugin from 2.19 to 2.20 (#188)

* Bump lombok from 1.18.18 to 1.18.20 (#189)

* Bump spotbugs-annotations from 4.2.2 to 4.2.3 (#191)

* Bump mockito-junit-jupiter from 3.8.0 to 3.9.0 (#190)

* Bump pitest-maven from 1.6.4 to 1.6.5 (#192)

* Bump pitest-maven from 1.6.5 to 1.6.6 (#194)

* Bump tiles-maven-plugin from 2.20 to 2.21 (#195)

* Bump mockito-junit-jupiter from 3.9.0 to 3.10.0 (#196)

* Bump junit-bom from 5.7.1 to 5.7.2 (#197)

* Bump tiles-maven-plugin from 2.21 to 2.22 (#198)

* Bump pitest-maven from 1.6.6 to 1.6.7 (#199)

* Bump mockito-junit-jupiter from 3.10.0 to 3.11.0 (#200)

* Bump assertj-core from 3.19.0 to 3.20.0 (#202)

* Bump mockito-junit-jupiter from 3.11.0 to 3.11.1 (#201)

* Bump assertj-core from 3.20.0 to 3.20.1 (#203)

* Bump tiles-maven-plugin from 2.22 to 2.23 (#204)

* Bump assertj-core from 3.20.1 to 3.20.2 (#205)

* Bump mockito-junit-jupiter from 3.11.1 to 3.11.2 (#206)

* Upgrade to GitHub-native Dependabot (#193)

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
Co-authored-by: Paul Campbell <pcampbell@kemitix.net>

* Create dependabot.yml (#207)

* Bump actions/setup-java from 1 to 2.1.0 (#210)

* Bump actions/setup-java from 1 to 2.1.0

Bumps [actions/setup-java](https://github.com/actions/setup-java) from 1 to 2.1.0.
- [Release notes](https://github.com/actions/setup-java/releases)
- [Commits](https://github.com/actions/setup-java/compare/v1...v2.1.0)

---
updated-dependencies:
- dependency-name: actions/setup-java
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

* Update build-maven.yml

* Update deploy-sonatype.yml

* Update build-maven.yml

* Update build-maven.yml

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Paul Campbell <pcampbell@kemitix.net>

* Bump release-drafter/release-drafter from 5.14.0 to 5.15.0 (#209)

Bumps [release-drafter/release-drafter](https://github.com/release-drafter/release-drafter) from 5.14.0 to 5.15.0.
- [Release notes](https://github.com/release-drafter/release-drafter/releases)
- [Commits](https://github.com/release-drafter/release-drafter/compare/v5.14.0...v5.15.0)

---
updated-dependencies:
- dependency-name: release-drafter/release-drafter
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Paul Campbell <pcampbell@kemitix.net>

* Add ResultVoid special Result type

- Result now extends ThrowableFunctor allowing mapping functions to throw exceptions
- Added ResultVoid to replace the usage of Result<Void> and handle void cases better
- Add `flatMapV` and `andThenV` which return ResultVoid

* result: Migrate static constructor docs to javadoc

* result.Result: move static constructors to top of class

* result: add docs and tests for from(Maybe) and from(Either)

* pom: add apigaurdian dependency

* Remove migrated docs

* result: add missing api status

* result.Result: group static methods together in file

* result.Result: reorder static methods

* result.Result: deprecate static maybe methods that don’t include Maybe in name

* result.Result.toMaybe: update docs and tests

* fix up toMaybe example

* result.Result.flatMapMaybe: update docs and tests

* result.Result.applyOver(Stream,Consumer): updated docs and tests

* result.Result.applyOver: update docs and tests

* result.Result: fix bad javadoc encoding

* result.Result.flatMapApply: update docs and tests

* result.Result.error(Class, Throwable): added

* result.Result: suppress warning on long class

* result.Result: apply warning supression to entire interface

* result.Result.toEither: update docs and tests

* result.Result.err: deprecate

* [BREAKING] result.Result.err: removed

* [BREAKING] result.Result.result: remove method

* [BREAKING] result.Result.success: remove method

* result.Result.orElseThrow: update docs and tests

* result.Result.toVoid: add javadoc and test

* result.Result.map: update docs and tests

* result.Result.map: add api status

* result.Result.isError: update docs and test

* result.Result.isError: update docs and test

* result.Result.isOkay: update docs and test

* result.Result.onError: update docs and test

* result.Result.onError: update docs and tests

* result.Result: tidy up and add some API statuses

* result.Result.orElseThrowUnchecked: update docs and tests

* result.Result.orElseThrow: update docs and tests

* result.Result.maybe: removed

* result.Result.thenWith: update docs and test

* result.Result.peek: update docs and tests

* result.Result.recover: update docs and tests

* result.Result.match: update docs and tests

* result.Result.andThen: deprecate in favour of map

* result.Result.flatMap: update docs and tests

* Update link to documentation for Result

* result.Result.flatMapV: add docs and test

* result.Result.match: update docs

* result.Result.onSuccess: update docs

* result.Result.reduce: mark experimental

* pom: remove java.version (conflict resolution mistake)

* Result: add deprecation annotations

* result.Err: remove unused imports

* result.BaseResult: extract interface

* result.ResultVoid.match: add javadoc

* result.ResultVoid: regroup tests and add javadoc for recover and onSuccess

* result.ResultVoid.onError: update docs and tests

* result.Err.andThen: don't create new object needlessly

* result.ResultVoid.andThen: add javadoc and update tests

* result.ResultVoid.inject: on error returns new exception, update docs and tests

* result.Result.error: suppress false positive unused parameter warning

* result.Success.getInstance added

* ErrVoid: fix toString

* SuccessVoid: break circular dependency

* Err: break circular dependency

* Result.result: added

* Success: break cyclic dependency

* Result.result: add javadoc and test

* Result.result: reuse static of(…)

* github/dependabot: clean up

* github/workflow: remove unused graphviz

* github/workflow: drop java  support

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
This commit is contained in:
Paul Campbell 2021-08-05 06:49:32 +01:00 committed by GitHub
parent a885474ff0
commit 571fd2641a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 3428 additions and 1167 deletions

View file

@ -4,12 +4,6 @@ updates:
directory: "/" directory: "/"
schedule: schedule:
interval: daily interval: daily
open-pull-requests-limit: 10
ignore:
- dependency-name: com.github.spotbugs:spotbugs-annotations
versions:
- 4.2.0
- 4.2.1
- package-ecosystem: "github-actions" - package-ecosystem: "github-actions"
directory: "/" directory: "/"
schedule: schedule:

View file

@ -11,9 +11,8 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
matrix: matrix:
java: [ 8, 11, 16 ] java: [ 11, 16 ]
steps: steps:
- uses: kamiazya/setup-graphviz@v1
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: setup-jdk-${{ matrix.java }} - name: setup-jdk-${{ matrix.java }}
uses: actions/setup-java@v2.1.0 uses: actions/setup-java@v2.1.0

View file

@ -10,13 +10,12 @@ jobs:
deploy: deploy:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: kamiazya/setup-graphviz@v1
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Set up JDK - name: Set up JDK
uses: actions/setup-java@v2.1.0 uses: actions/setup-java@v2.1.0
with: with:
distribution: 'adopt' distribution: 'adopt'
java-version: 8 java-version: 11
- name: Build with Maven - name: Build with Maven
run: mvn -B install run: mvn -B install
- name: Nexus Repo Publish - name: Nexus Repo Publish

287
README.md
View file

@ -18,7 +18,7 @@ https://search.maven.org/artifact/net.kemitix/mon)
- [Wrapper](#Wrapper) - light-weight type-alias-like - [Wrapper](#Wrapper) - light-weight type-alias-like
- [TypeAlias](#TypeAlias) - type-alias-like monadic wrapper - [TypeAlias](#TypeAlias) - type-alias-like monadic wrapper
- [Maybe](#Maybe) - Maybe, Just or Nothing - [Maybe](#Maybe) - Maybe, Just or Nothing
- [Result](#Result) - Result, Success or Err - [Result](https://kemitix.github.io/mon/net/kemitix/mon/result/package-summary.html) - Result, Success or Err
- [Tree](#Tree) - generic trees - [Tree](#Tree) - generic trees
- [Lazy](#Lazy) - lazy evaluation - [Lazy](#Lazy) - lazy evaluation
- [Either](#Either) - Either, Left or Right - [Either](#Either) - Either, Left or Right
@ -402,292 +402,7 @@ Optional<Integer> optional = Maybe.maybe(getValue())
.toOptional(); .toOptional();
``` ```
--- ---
## Result
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.
`Result` is a Monad.
### Example
``` 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.");
}
}
```
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 then return an error `Result`. An error
`Result` would skip the `flatMap` and continue at the `match()` where it
would have called the error `Consumer`.
### Static Constructors
#### `static <T> Result<T> of(Callable<T> callable)`
Create a `Result` for the output of the `Callable`.
If the `Callable` throws an `Exception`, then the `Result` will be an error and
will contain that exception.
This will be the main starting point for most `Result`s where the callable
could throw an `Exception`.
``` java
Result<Integer> okay = Result.of(() -> 1);
Result<Integer> error = Result.of(() -> {throw new RuntimeException();});
```
---
#### `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.
``` java
Result<Integer> okay = Result.ok(1);
```
---
#### `static <T> Result<T> error(Throwable error)`
Create a `Result` for an error.
``` java
Result<Integer> error = Result.error(new RuntimeException());
```
---
### Static Methods
These static methods provide integration with the `Maybe` class.
#### `static <T> Maybe<T> toMaybe(Result<T> result)`
Creates a `Maybe` from the `Result`, where the `Result` is a success, then
the `Maybe` will be a `Just` contain the value of the `Result`. However, if the
`Result` is an error, then the `Maybe` will be `Nothing`.
``` java
Result<Integer> result = Result.of(() -> getValue());
Maybe<Integer> maybe = Result.toMaybe(result);
```
---
#### `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`.
``` java
Maybe<Integer> maybe = Maybe.maybe(getValue());
Result<Integer> result = Result.fromMaybe(maybe,
() -> new NoSuchFileException("filename"));
```
---
#### `static <T> Result<Maybe<T>> invert(Maybe<Result<T>> maybeResult)`
Swaps the `Result` within a `Maybe`, so that `Result` contains a `Maybe`.
``` java
Maybe<Result<Integer>> maybe = Maybe.maybe(Result.of(() -> getValue()));
Result<Maybe<Integer>> result = Result.invert(maybe);
```
---
#### `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`.
``` java
Result<Maybe<Integer>> result = Result.of(() -> Maybe.maybe(getValue()));
Result<Maybe<Integer>> maybeResult = Result.flatMapMaybe(result,
maybe -> Result.of(() -> maybe.map(v -> v * 2)));
```
---
### Instance Methods
#### `<R> Result<R> map(Function<T,R> f)`
If the `Result` is a success, then apply the function to the value within the
`Result`, returning the result within another `Result`. If the `Result` is an
error, then return the error.
``` java
Result<String> result = Result.of(() -> getValue())
.map(v -> String.valueOf(v));
```
---
#### `<R> Result<R> flatMap(Function<T,Result<R>> f)`
If the `Result` is a success, then return a new `Result` containing the result
of applying the function to the contents of the `Result`. If the `Result` is an
error, then return the error.
``` java
Result<String> result =
Result.of(() -> getValue())
.flatMap(v -> Result.of(() -> String.valueOf(v)));
```
---
#### `<R> Result<R> andThen(Function<T,Callable<R>> f)`
Maps a successful `Result` to another `Result` using a `Callable` that is able
to throw a checked exception.
``` java
Result<String> result =
Result.of(() -> getValue())
.andThen(v -> () -> {throw new IOException();});
```
---
#### `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.
``` java
Result.of(() -> getValue())
.match(
success -> System.out.println(success),
error -> System.err.println(error.getMessage())
);
```
---
#### `Result<T> recover(Function<Throwable,Result<T>> f)`
Provide a way to attempt to recover from an error state.
``` java
Result<Integer> result = Result.of(() -> getValue())
.recover(e -> Result.of(() -> getSafeValue(e)));
```
---
#### `Result<T> peek(Consumer<T> consumer)`
Provide the value within the Result, if it is a success, to the `Consumer`,
and returns this Result.
``` java
Result<Integer> result = Result.of(() -> getValue())
.peek(v -> System.out.println(v));
```
---
#### `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.
``` java
Result<Integer> result =
Result.of(() -> getValue())
.thenWith(v -> () -> System.out.println(v))
.thenWith(v -> () -> {throw new IOException();});
```
---
#### `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`.
``` java
Result<Maybe<Integer>> result = Result.of(() -> getValue())
.maybe(v -> v % 2 == 0);
```
---
#### `T orElseThrow()`
Extracts the successful value from the `Result`, or throws the error
within a `CheckedErrorResultException`.
``` java
Integer result = Result.of(() -> getValue())
.orElseThrow();
```
---
#### `<E extends Exception> T orElseThrow(Class<E> type) throws E`
Extracts the successful value from the `Result`, or throws the error when it
is of the given type. Any other errors will be thrown inside an
`UnexpectedErrorResultException`.
``` java
Integer result = Result.of(() -> getValue())
.orElseThrow(IOException.class);
```
---
#### `T orElseThrowUnchecked()`
Extracts the successful value from the `Result`, or throws the error within
an `ErrorResultException`.
``` java
Integer result = Result.of(() -> getValue())
.orElseThrowUnchecked();
```
---
#### `void onError(Consumer<Throwable> errorConsumer)`
A handler for error states. If the `Result` is an error, then supply the error
to the `Consumer`. Does nothing if the `Result` is a success.
``` java
Result.of(() -> getValue())
.onError(e -> handleError(e));
```
---
#### `boolean isOkay()`
Checks if the `Result` is a success.
``` java
boolean isOkay = Result.of(() -> getValue())
.isOkay();
```
---
#### `boolean isError()`
Checks if the `Result` is an error.
``` java
boolean isError = Result.of(() -> getValue())
.isError();
```
---
## Tree ## Tree
A Generalised tree, where each node may or may not have an item, and may have A Generalised tree, where each node may or may not have an item, and may have

View file

@ -33,7 +33,6 @@
<inceptionYear>2017</inceptionYear> <inceptionYear>2017</inceptionYear>
<properties> <properties>
<java.version>1.8</java.version>
<junit.version>5.7.2</junit.version> <junit.version>5.7.2</junit.version>
<mockito.version>3.11.2</mockito.version> <mockito.version>3.11.2</mockito.version>
<assertj.version>3.20.2</assertj.version> <assertj.version>3.20.2</assertj.version>
@ -45,6 +44,7 @@
<pitest-maven-plugin.version>1.6.8</pitest-maven-plugin.version> <pitest-maven-plugin.version>1.6.8</pitest-maven-plugin.version>
<pitest-junit5-plugin.version>0.14</pitest-junit5-plugin.version> <pitest-junit5-plugin.version>0.14</pitest-junit5-plugin.version>
<spotbugs.version>4.2.3</spotbugs.version> <spotbugs.version>4.2.3</spotbugs.version>
<apiguardian-api.version>1.1.1</apiguardian-api.version>
</properties> </properties>
<dependencies> <dependencies>
@ -55,6 +55,12 @@
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
<dependency>
<groupId>org.apiguardian</groupId>
<artifactId>apiguardian-api</artifactId>
<version>${apiguardian-api.version}</version>
</dependency>
<dependency> <dependency>
<groupId>org.junit.jupiter</groupId> <groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId> <artifactId>junit-jupiter-api</artifactId>

View file

@ -0,0 +1,36 @@
package net.kemitix.mon;
import net.kemitix.mon.result.ThrowableFunction;
/**
* The ThrowableFunctor is used for types that can be mapped over.
*
* <p>A ThrowableFunctor is identical to a normal Functor except that the
* map method may throw an exception.</p>
*
* <p>Implementations of ThrowableFunctor should satisfy the following laws:</p>
*
* <ul>
* <li>map id == id</li>
* <li>map (f . g) == map f . map g</li>
* </ul>
*
* @param <T> the type of the Functor
* @param <F> the type of the mapped Functor
*
* @author Paul Campbell (pcampbell@kemitix.net)
*/
public interface ThrowableFunctor<T, F extends ThrowableFunctor<?, ?>> {
/**
* Applies the function to the value within the ThrowableFunctor, returning
* the result within another ThrowableFunctor.
*
* @param f the function to apply
* @param <R> the type of the content of the mapped functor
*
* @return a ThrowableFunctor containing the result of the function
* {@code f} applied to the value
*/
<R> F map(ThrowableFunction<T, R, ?> f);
}

View file

@ -0,0 +1,31 @@
package net.kemitix.mon;
/**
* Helper class to capture a reference to a type.
*
* <p>Usually to be used when passing a type as a parameter to method.</p>
*
* <pre><code>
* TypeReference&lt;Integer&gt; ref1 = TypeReference.create();
* var ref2 = TypeReference.&lt;Integer&gt;create();
* </code></pre>
*
* @param <T> the type being references
*/
@SuppressWarnings("PMD.ClassNamingConventions")
final public class TypeReference<T> {
private TypeReference() {
}
/**
* Creates a new instance of a TypeReference.
*
* @param <R> the type being references.
* @return the TypeReference
*/
public static <R> TypeReference<R> create() {
return new TypeReference<>();
}
}

View file

@ -94,6 +94,18 @@ public interface Maybe<T> extends Functor<T, Maybe<?>> {
.orElseGet(Maybe::nothing); .orElseGet(Maybe::nothing);
} }
/**
* Creates a Maybe from an Optional.
*
* @param optional the Optional
* @param <T> the type of the value
* @return a Just if the Optional contains a value, otherwise a Nothing
*/
static <T> Maybe<T> fromOptional(Optional<T> optional) {
return optional.map(Maybe::maybe)
.orElseGet(Maybe::nothing);
}
/** /**
* Checks if the Maybe is a Just. * Checks if the Maybe is a Just.
* *

View file

@ -0,0 +1,61 @@
package net.kemitix.mon.result;
import org.apiguardian.api.API;
import java.util.function.Consumer;
import static org.apiguardian.api.API.Status.STABLE;
/**
* Base interface for {@link Result} and {@link ResultVoid}.
*/
public interface BaseResult {
/**
* Checks if the Result is an error.
*
* <pre><code>
* boolean isError = Result.of(() -&gt; getValue())
* .isError();
* </code></pre>
*
* @return true if the Result is an error.
*/
@API(status = STABLE)
boolean isError();
/**
* Checks if the Result is a success.
*
* <pre><code>
* boolean isOkay = Result.of(() -> getValue())
* .isOkay();
* </code></pre>
*
* @return true if the Result is a success.
*/
@API(status = STABLE)
boolean isOkay();
/**
* A handler for error states.
*
* <p>If the {@code Result} is an error, then supply the error
* to the {@code Consumer}. Does nothing if the {@code Result} is a
* success.</p>
*
* <p>When this is an error then tne Consumer will be supplied with the
* error. When this is a success, then nothing happens.</p>
*
* <pre><code>
* void handleError(Throwable e) {...}
* Result.of(() -> doSomething())
* .onError(e -> handleError(e));
* </code></pre>
*
* @param errorConsumer the consumer to handle the error
*/
@API(status = STABLE)
void onError(Consumer<Throwable> errorConsumer);
}

View file

@ -22,14 +22,12 @@
package net.kemitix.mon.result; package net.kemitix.mon.result;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import net.kemitix.mon.maybe.Maybe;
import java.util.Objects; import java.util.Objects;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
import java.util.function.BinaryOperator; import java.util.function.BinaryOperator;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.function.Function; import java.util.function.Function;
import java.util.function.Predicate;
/** /**
* An Error Result. * An Error Result.
@ -37,7 +35,8 @@ import java.util.function.Predicate;
* @param <T> the type of the value in the Result if it has been a success * @param <T> the type of the value in the Result if it has been a success
*/ */
@RequiredArgsConstructor @RequiredArgsConstructor
@SuppressWarnings({"methodcount", "PMD.CyclomaticComplexity"}) @SuppressWarnings({"methodcount", "PMD.TooManyMethods", "PMD.ExcessivePublicCount",
"PMD.CyclomaticComplexity"})
class Err<T> implements Result<T> { class Err<T> implements Result<T> {
private final Throwable error; private final Throwable error;
@ -54,12 +53,17 @@ class Err<T> implements Result<T> {
@Override @Override
public <R> Result<R> flatMap(final Function<T, Result<R>> f) { public <R> Result<R> flatMap(final Function<T, Result<R>> f) {
return err(error); return new Err<>(error);
} }
@Override @Override
public <R> Result<R> map(final Function<T, R> f) { public ResultVoid flatMapV(final Function<T, ResultVoid> f) {
return err(error); return new ErrVoid(error);
}
@Override
public <R> Result<R> map(final ThrowableFunction<T, R, ?> f) {
return new Err<>(error);
} }
@Override @Override
@ -67,18 +71,13 @@ class Err<T> implements Result<T> {
onError.accept(error); onError.accept(error);
} }
@Override
public Result<Maybe<T>> maybe(final Predicate<T> predicate) {
return err(error);
}
@Override @Override
public T orElseThrow() throws CheckedErrorResultException { public T orElseThrow() throws CheckedErrorResultException {
throw CheckedErrorResultException.with(error); throw CheckedErrorResultException.with(error);
} }
@Override @Override
@SuppressWarnings("unchecked") @SuppressWarnings({"unchecked", "PMD.AvoidDuplicateLiterals"})
public <E extends Exception> T orElseThrow(final Class<E> type) throws E { public <E extends Exception> T orElseThrow(final Class<E> type) throws E {
if (type.isInstance(error)) { if (type.isInstance(error)) {
throw (E) error; throw (E) error;
@ -101,14 +100,31 @@ class Err<T> implements Result<T> {
return f.apply(error); return f.apply(error);
} }
@Override
public void onSuccess(final Consumer<T> successConsumer) {
// do nothing
}
@Override @Override
public void onError(final Consumer<Throwable> errorConsumer) { public void onError(final Consumer<Throwable> errorConsumer) {
errorConsumer.accept(error); errorConsumer.accept(error);
} }
@Override
@SuppressWarnings("unchecked")
public <E extends Throwable> Result<T> onError(
final Class<E> errorClass,
final Consumer<E> consumer
) {
if (error.getClass().isAssignableFrom(errorClass)) {
consumer.accept((E) error);
}
return this;
}
@Override @Override
public <R> Result<R> andThen(final Function<T, Callable<R>> f) { public <R> Result<R> andThen(final Function<T, Callable<R>> f) {
return err(error); return (Result<R>) this;
} }
@Override @Override
@ -116,6 +132,11 @@ class Err<T> implements Result<T> {
return this; return this;
} }
@Override
public ResultVoid thenWithV(final Function<T, WithResultContinuation<T>> f) {
return toVoid();
}
@Override @Override
public Result<T> reduce(final Result<T> identify, final BinaryOperator<T> operator) { public Result<T> reduce(final Result<T> identify, final BinaryOperator<T> operator) {
return this; return this;
@ -123,7 +144,8 @@ class Err<T> implements Result<T> {
@Override @Override
public boolean equals(final Object other) { public boolean equals(final Object other) {
return other instanceof Err && Objects.equals(error, ((Err) other).error); return other instanceof Err
&& Objects.equals(error, ((Err) other).error);
} }
@Override @Override
@ -135,4 +157,9 @@ class Err<T> implements Result<T> {
public String toString() { public String toString() {
return String.format("Result.Error{error=%s}", error); return String.format("Result.Error{error=%s}", error);
} }
@Override
public ResultVoid toVoid() {
return new ErrVoid(error);
}
} }

View file

@ -0,0 +1,79 @@
package net.kemitix.mon.result;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.function.Function;
public class ErrVoid implements ResultVoid {
private final Throwable error;
ErrVoid(final Throwable error) {
this.error = error;
}
@Override
public boolean isError() {
return true;
}
@Override
public boolean isOkay() {
return false;
}
@Override
public void match(
final Runnable onSuccess,
final Consumer<Throwable> onError
) {
onError.accept(error);
}
@Override
public ResultVoid recover(final Function<Throwable, ResultVoid> f) {
return f.apply(error);
}
@Override
public void onSuccess(final Runnable runnable) {
// do nothing
}
@Override
public void onError(final Consumer<Throwable> errorConsumer) {
errorConsumer.accept(error);
}
@Override
public <E extends Throwable> ResultVoid onError(
final Class<E> errorClass,
final Consumer<E> consumer
) {
if (error.getClass().isAssignableFrom(errorClass)) {
consumer.accept((E) error);
}
return this;
}
@Override
public ResultVoid andThen(final VoidCallable f) {
return this;
}
@Override
public String toString() {
return String.format("Result.ErrVoid{error=%s}", error);
}
@Override
public boolean equals(final Object other) {
return other instanceof ErrVoid && Objects.equals(error, ((ErrVoid) other).error);
}
@Override
public int hashCode() {
return Objects.hash(error);
}
}

View file

@ -21,178 +21,416 @@
package net.kemitix.mon.result; package net.kemitix.mon.result;
import net.kemitix.mon.Functor; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import net.kemitix.mon.ThrowableFunctor;
import net.kemitix.mon.TypeReference;
import net.kemitix.mon.experimental.either.Either;
import net.kemitix.mon.maybe.Maybe; import net.kemitix.mon.maybe.Maybe;
import org.apiguardian.api.API;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.*; import java.util.function.*;
import java.util.stream.Stream;
import static org.apiguardian.api.API.Status.*;
/** /**
* An Either type for holding a result or an error (Throwable). * A type for holding a <strong>result</strong> or an <strong>error</strong>.
*
* <h2>Static Constructors:</h2>
* <ul>
* <li>{@link #ok()}</li>
* <li>{@link #ok(Object)}</li>
* <li>{@link #of(Callable)}</li>
* <li>{@link #ofVoid(VoidCallable)}</li>
* <li>{@link #error(Throwable)}</li>
* <li>{@link #error(TypeReference, Throwable)}</li>
* <li>{@link #from(Either)}</li>
* <li>{@link #from(Maybe, Supplier)}</li>
* </ul>
* *
* @param <T> the type of the result when a success * @param <T> the type of the result when a success
* @author Paul Campbell (pcampbell@kemitix.net) * @author Paul Campbell (pcampbell@kemitix.net)
*/ */
@SuppressWarnings({"methodcount", "PMD.TooManyMethods"}) @SuppressWarnings({"methodcount", "PMD.TooManyMethods", "PMD.ExcessivePublicCount", "PMD.ExcessiveClassLength",
public interface Result<T> extends Functor<T, Result<?>> { "PMD.AvoidCatchingThrowable"})
public interface Result<T> extends BaseResult, ThrowableFunctor<T, ThrowableFunctor<?, ?>> {
// BEGIN Static Constructors
/** /**
* Creates a Result from the Maybe, where the Result will be an error if the Maybe is Nothing. * Creates a success Result with no value.
* *
* @param maybe the Maybe the might contain the value of the Result * <pre><code>
* @param error the error that will be the Result if maybe is Nothing * ResultVoid okay = Result.ok();
* @param <T> the type of the Maybe and the Result * </code></pre>
* @return a Result containing the value of the Maybe when it is a Just, or the error when it is Nothing * @return a successful Result
*/ */
static <T> Result<T> fromMaybe(final Maybe<T> maybe, final Supplier<Throwable> error) { @API(status = STABLE)
return maybe.map(Result::ok) static ResultVoid ok() {
.orElseGet(() -> Result.error(error.get())); return SuccessVoid.getInstance();
} }
/** /**
* Create a Result for an error. * Create a success Result with a value.
* *
* @param error the error (Throwable) * <pre><code>
* @param <T> the type had the result been a success * Result&lt;Integer&gt; okay = Result.ok(1);
* @return an error Result * </code></pre>
*
* @param value the value
* @param <R> the type of the value
* @return a successful Result
*/ */
default <T> Result<T> err(final Throwable error) { @API(status = STABLE)
return new Err<>(error); static <R> Result<R> ok(final R value) {
return new Success<>(value);
} }
/** /**
* Create a Result for an error. * Create a {@link Result} for the output of the {@link Callable}.
* *
* @param error the error (Throwable) * <p>If the {@code Callable} succeeds then the {@code Result} will be a
* @param <T> the type had the result been a success * {@link Success} and will contain the value.
* @return an error Result * If it throws an {@code Exception}, then the {@code Result} will be an
*/ * {@link Err} and will contain that exception.</p>
static <T> Result<T> error(final Throwable error) { *
return new Err<>(error); * <pre><code>
} * Result&lt;Integer$gt; okay = Result.of(() -&gt; 1);
* Result&lt;Integer&gt; error = Result.of(() -&gt; {
/** * throw new RuntimeException();
* Create a Result for a output of the Callable. * });
* </code></pre>
* *
* @param callable the callable to produce the result * @param callable the callable to produce the result
* @param <T> the type of the value * @param <T> the type of the value
* @return a Result * @return a Result
*/ */
@SuppressWarnings({"illegalcatch", "PMD.AvoidCatchingThrowable", "PMD.AvoidDuplicateLiterals"}) @API(status = STABLE)
default <T> Result<T> result(final Callable<T> callable) {
try {
return Result.ok(callable.call());
} catch (final Throwable e) {
return Result.error(e);
}
}
/**
* Create a Result for a output of the Callable.
*
* @param callable the callable to produce the result
* @param <T> the type of the value
* @return a Result
*/
@SuppressWarnings({"illegalcatch", "PMD.AvoidCatchingThrowable"})
static <T> Result<T> of(final Callable<T> callable) { static <T> Result<T> of(final Callable<T> callable) {
try { try {
return Result.ok(callable.call()); return Result.ok(callable.call());
} catch (final Throwable e) {
return new Err<>(e);
}
}
/**
* Create a {@code ResultVoid} after calling a {@link VoidCallable}
* that produces no output.
*
* <p>If the {@code callable} completes successfully then a
* {@link SuccessVoid} will be returned. if the {@code callable} throws an
* exception, then a {@link ErrVoid} containing the exception will be
* returned.</p>
*
* <pre><code>
* ResultVoid okay = Result.ofVoid(() -&gt; System.out.println("Hello, World!"));
* ResultVoid error = Result.ofVoid(() -&gt; {
* throw new Exception();
* });
* </code></pre>
*
* @param callable the callable to call
* @return a Result with no value
*/
@API(status = STABLE)
static ResultVoid ofVoid(final VoidCallable callable) {
try {
callable.call();
return Result.ok();
} catch (final Throwable e) { } catch (final Throwable e) {
return Result.error(e); return Result.error(e);
} }
} }
/** /**
* Create a Result for a success. * Create a Result for an error.
* *
* @param value the value * <pre><code>
* @param <T> the type of the value * ResultVoid error = Result.error(new RuntimeException());
* @return a successful Result * </code></pre>
*
* @param error the error (Throwable)
* @return an error Result
*/ */
default <T> Result<T> success(final T value) { @API(status = STABLE)
return new Success<>(value); static ResultVoid error(final Throwable error) {
return new ErrVoid(error);
} }
/** /**
* Create a Result for a success. * Create a Result for an error.
* *
* @param value the value * <pre><code>
* @param <T> the type of the value * Result&lt;Integer&gt; error = Result.error(TypeReference.create(), new RuntimeException());
* @return a successful Result * </code></pre>
*
* @param type the type of the missing) value
* @param error the error (Throwable)
* @param <R> The type of the missing value
* @return an error Result
*/ */
static <T> Result<T> ok(final T value) { @API(status = STABLE)
return new Success<>(value); @SuppressFBWarnings(value = "UP_UNUSED_PARAMETER",
justification = "Use the type parameter to fingerprint the return type")
static <R> Result<R> error(final TypeReference<R> type, final Throwable error) {
return new Err<>(error);
} }
/** /**
* Creates a {@link Maybe} from the Result, where the Result is a success, then the Maybe will contain the value. * Creates a Result from the Either, where the Result will be an error if
* the Either is a Left, and a success if it is a Right.
* *
* <p>However, if the Result is an error then the Maybe will be nothing.</p> * <pre><code>
* import net.kemitix.mon.experimental.either.Either;
* *
* @param result the Result the might contain the value of the Result * Either&lt;Throwable, String&gt; eitherRight = Either.right("Hello, World!");
* @param <T> the type of the Maybe and the Result * Either&lt;Throwable, String&gt; eitherLeft = Either.left(new RuntimeException());
*
* Result&lt;String&gt; success = Result.from(eitherRight);
* Result&lt;String&gt; error = Result.from(eitherLeft);
* </code></pre>
*
* @param either the either that could contain an error in left or a value in right
* @param <T> the type of the right value
* @return a Result containing the right value of the Either when it is a
* Right, or the left error when it is a Left.
*/
@API(status = EXPERIMENTAL)
static <T> Result<T> from(Either<Throwable, T> either) {
return Result.from(
Maybe.fromOptional(either.getRight()),
() -> either.getLeft().get());
}
/**
* Creates a Result from the Maybe, where the Result will be an error if the Maybe is Nothing.
*
* <p>Where the {@code Maybe} is nothing, then the Supplier will provide the error for the Result.</p>
*
* <pre><code>
* Maybe&lt;Integer&gt; maybe = Maybe.maybe(1);
* Result&lt;Integer&gt; result = Result.from(maybe, () -&gt; new RuntimeException());&lt;/p&gt;
* </code></pre>
*
* @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 value in 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 * @return a Result containing the value of the Maybe when it is a Just, or the error when it is Nothing
*/ */
static <T> Maybe<T> toMaybe(final Result<T> result) { @API(status = EXPERIMENTAL)
try { static <T> Result<T> from(final Maybe<T> maybe, final Supplier<Throwable> error) {
return Maybe.just(result.orElseThrow()); return maybe.map(Result::ok)
} catch (final CheckedErrorResultException throwable) { .orElseGet(() -> new Err<>(error.get()));
return Maybe.nothing(); }
}
// END Static Constructors
// BEGIN Static methods
/**
* Applies a function to a stream of values, folding the results using the
* zero value and accumulator function.
*
* <p>Returns a success {@code Result} of the accumulated outputs if all
* values were transformed successfully by the function, or an error
* {@code Result} for the first error. If any value results in an error when applying the function, then
* processing stops and a Result containing that error is returned,</p>
*
* <pre><code>
* Function&lt;String, Integer&gt; f = s -&gt; {
* if ("dd".equals(s)) {
* throw new RuntimeException("Invalid input: " + s);
* }
* return s.length();
* };
*
* Stream&lt;String&gt; okayStream = Stream.of("aa", "bb");
* Result&lt;Integer&gt; resultOkay = Result.applyOver(okayStream, f, 0, Integer::sum);
* resultOkay.match(
* success -&gt; System.out.println("Total length: " + success),
* error -&gt; System.out.println("Error: " + error.getMessage())
* );
* // Total length: 4
*
* Stream&lt;String&gt; errorStream = Stream.of("cc", "dd");
* Result&lt;Integer&gt; resultError = Result.applyOver(errorStream, f, 0, Integer::sum);
* resultError.match(
* success -&gt; System.out.println("Total length: " + success), // will not match
* error -&gt; System.out.println("Error: " + error.getMessage())
* );
* // Error: Invalid input: dd
* </code></pre>
*
* @param stream the values to apply the function to
* @param f the function to apply to the values
* @param zero the initial value to use with the accumulator
* @param accumulator the function to combine function outputs together
* @param <N> the type of the stream values
* @param <R> the type of the output value
* @return a Success Result of the accumulated function outputs if all
* values were transformed successfully by the function, or an Err Result
* for the first value that failed.
*/
@API(status = STABLE)
static <N, R> Result<R> applyOver(
Stream<N> stream,
Function<N, R> f,
R zero,
BiFunction<R, R, R> accumulator
) {
var acc = new AtomicReference<>(Result.ok(zero));
stream.map(t -> Result.of(() -> f.apply(t)))
.peek(r ->
r.onSuccess(vNew ->
acc.getAndUpdate(rResult ->
rResult.map(vOld ->
accumulator.apply(vNew, vOld)))))
.dropWhile(Result::isOkay)
.limit(1)
.forEach(acc::set);
return acc.get();
} }
/** /**
* Extracts the successful value from the result, or throws the error within a {@link CheckedErrorResultException}. * Applies a consumer to a stream of values.
* *
* @return the value if a success * <p>If any value results in an error when accepted by the consumer, then
* @throws CheckedErrorResultException if the result is an error * processing stops and a Result containing that error is returned,</p>
*
* <p>Returns a success Result (with no value) if all values were consumed
* successfully by the function, or an error Result for the first value that
* failed.</p>
*
* <pre><code>
* List&lt;String&gt; processed = new ArrayList&lt;&gt;();
* Consumer&lt;String&gt; consumer = s -&gt; {
* if ("dd".equals(s)) {
* throw new RuntimeException("Invalid input: " + s);
* }
* processed.add(s);
* };
*
* Stream&lt;String&gt; okayStream = Stream.of("aa", "bb");
* ResultVoid resultOkay = Result.applyOver(okayStream, consumer);
* resultOkay.match(
* () -&gt; System.out.println("All processed okay."),
* error -&gt; System.out.println("Error: " + error.getMessage())
* );
* System.out.println("Processed: " + processed);
* // All processed okay.
* // Processed: [aa, bb]
*
* processed.add("--");
* Stream&lt;String&gt; errorStream = Stream.of("cc", "dd", "ee");// fails at 'dd'
* ResultVoid resultError = Result.applyOver(errorStream, consumer);
* resultError.match(
* () -&gt; System.out.println("All processed okay."),
* error -&gt; System.out.println("Error: " + error.getMessage())
* );
* System.out.println("Processed: " + processed);
* // Error: Invalid input: dd
* // Processed: [aa, bb, --, cc]
* </code></pre>
*
* @param stream the value to supply to the consumer
* @param consumer the consumer to receive the values
* @param <N> the type of the stream values
* @return a Success Result (with no value) if all values were transformed
* successfully by the function, or an Err Result for the first value that
* failed.
*/ */
T orElseThrow() throws CheckedErrorResultException; @API(status = STABLE)
static <N> ResultVoid applyOver(
/** Stream<N> stream,
* Extracts the successful value from the result, or throws the error Throwable. Consumer<N> consumer
* ) {
* @param type the type of checked exception that may be thrown return applyOver(stream, n -> {
* @param <E> the type of the checked exception to throw consumer.accept(n);
* return null;
* @return the value if a success }, null, (unused1, unused2) -> null)
* @throws E if the result is an error .toVoid();
*/
<E extends Exception> T orElseThrow(Class<E> type) throws E;
/**
* Extracts the successful value from the result, or throws the error in a {@link UnexpectedErrorResultException}.
*
* @return the value if a success
*/
T orElseThrowUnchecked();
/**
* 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>> swap(final Maybe<Result<T>> maybeResult) {
return maybeResult.orElseGet(() -> Result.ok(null))
.flatMap(value -> Result.ok(Maybe.maybe(value)));
} }
/** /**
* Returns a new Result consisting of the result of applying the function to the contents of the Result. * Applies a function to a stream of values, folding the results using the
* zero value and accumulator function.
* *
* @param f the mapping function the produces a Result * <p>If any value results in an error when applying the function, then
* @param <R> the type of the value withing the Result of the mapping function * processing stops and a {@code Result} containing that error is returned.</p>
* @return a Result *
* <p>Returns a success {@code Result} of the accumulated function outputs
* if all values were transformed successfully, or an error {@code Result}
* for the first value that failed.</p>
*
* <p>Similar to {@link #applyOver(Stream, Function, Object, BiFunction)},
* except that the result of the {@code f} function is a {@code Result}; and
* to a {@code flatMap} method in that the {@code Result} is not nested with
* in another {@code Result}.</p>
*
* <pre><code>
* Function&lt;String, Integer&gt; f = s -&gt; {
* if ("dd".equals(s)) {
* throw new RuntimeException("Invalid input: " + s);
* }
* return s.length();
* };
*
* Stream&lt;String&gt; okayStream = Stream.of("aa", "bb");
* Result&lt;Integer&gt; resultOkay = Result.applyOver(okayStream, f, 0, Integer::sum);
* resultOkay.match(
* success -&gt; assertThat(success).isEqualTo(4),
* error -&gt; fail("not an err")
* );
* // Total length: 4
*
* Stream&lt;String&gt; errorStream = Stream.of("cc", "dd");
* Result&lt;Integer&gt; resultError = Result.applyOver(errorStream, f, 0, Integer::sum);
* resultError.match(
* success -&gt; fail("not a success"), // will not match
* error -&gt; assertThat(error.getMessage()).isEqualTo("Invalid input: dd")
* );
* // Error: Invalid input: dd
* </code></pre>
*
* @param stream the values to apply the function to
* @param f the function to apply to the values
* @param zero the initial value to use with the accumulator
* @param accumulator the function to combine function outputs together
* @param <T> the type of the stream values
* @param <R> the type of the output value
* @return a Success Result of the accumulated function outputs if all
* values were transformed successfully by the function, or an Err Result
* for the first value that failed.
*/ */
<R> Result<R> flatMap(Function<T, Result<R>> f); @API(status = STABLE)
static <T, R> Result<R> flatApplyOver(
Stream<T> stream,
Function<T, Result<R>> f,
R zero,
BiFunction<R, R, R> accumulator
) {
var acc = new AtomicReference<>(Result.ok(zero));
stream.map(f)
.peek(r -> r.onSuccess(vNew ->
acc.getAndUpdate(rResult ->
rResult.map(vOld ->
accumulator.apply(vNew, vOld)))))
.dropWhile(Result::isOkay)
.limit(1)
.forEach(acc::set);
return acc.get();
}
/** /**
* Applies the function to the contents of a Maybe within the Result. * Applies the function to the contents of a {@link Maybe} within the {@code Result}.
*
* <pre><code>
* Result&lt;Maybe&lt;Integer&gt;&gt; result = Result.of(() -&gt; Maybe.maybe(getValue()));
* Result&lt;Maybe&lt;Integer&gt;&gt; maybeResult = Result.flatMapMaybe(result,
* maybe -&gt; Result.of(() -&gt; maybe.map(v -&gt; v * 2)));
* </code></pre>
* *
* @param maybeResult the Result that may contain a value * @param maybeResult the Result that may contain a value
* @param f the function to apply to the value * @param f the function to apply to the value
@ -200,6 +438,7 @@ public interface Result<T> extends Functor<T, Result<?>> {
* @param <R> the type of the updated Result * @param <R> the type of the updated Result
* @return a new Maybe within a Result * @return a new Maybe within a Result
*/ */
@API(status = EXPERIMENTAL)
static <T, R> Result<Maybe<R>> flatMapMaybe( static <T, R> Result<Maybe<R>> flatMapMaybe(
final Result<Maybe<T>> maybeResult, final Result<Maybe<T>> maybeResult,
final Function<Maybe<T>, Result<Maybe<R>>> f final Function<Maybe<T>, Result<Maybe<R>>> f
@ -208,63 +447,296 @@ public interface Result<T> extends Functor<T, Result<?>> {
} }
/** /**
* Checks if the Result is an error. * Swaps the inner {@code Result} of a {@link Maybe}, so that a {@code Result} contains a {@code Maybe}.
* *
* @return true if the Result is an error. * @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.
* @deprecated
*/ */
boolean isError(); @API(status = DEPRECATED)
@Deprecated
static <T> Result<Maybe<T>> swap(final Maybe<Result<T>> maybeResult) {
return maybeResult.orElseGet(() -> Result.ok(null))
.flatMap(value -> Result.ok(Maybe.maybe(value)));
}
/** /**
* Checks if the Result is a success. * Creates a {@link Maybe} from the {@code Result}.
* *
* @return true if the Result is a success. * <p>Where the {@code Result} is a {@link Success}, the {@code Maybe} will be a {@code Just} contain the value of
* the {@code Result}.</p>
*
* <p>However, if the {@code Result} is an {@link Err}, then the {@code Maybe} will be {@code Nothing}.</p>
*
* <pre><code>
* Result&lt;Integer&gt; result = Result.of(() -&gt; getValue());
* Maybe&lt;Integer&gt; maybe = Result.toMaybe(result);
* </code></pre>
*
* @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
*/ */
boolean isOkay(); @API(status = EXPERIMENTAL)
static <T> Maybe<T> toMaybe(final Result<T> result) {
try {
return Maybe.just(result.orElseThrow());
} catch (final CheckedErrorResultException throwable) {
return Maybe.nothing();
}
}
// END Static methods
/**
* Create a {@link Result} for the output of the {@link Callable}.
*
* <p>If the {@code Callable} succeeds then the {@code Result} will be a
* {@link Success} and will contain the value.
* If it throws an {@code Exception}, then the {@code Result} will be an
* {@link Err} and will contain that exception.</p>
*
* <pre><code>
* Result&lt;Integer&gt; start = Result.ok(1);
* Result&lt;Integer&gt; okay = start.result(() -&gt; 1);
* Result&lt;Integer&gt; error = start.result(() -&gt; {
* throw new RuntimeException();
* });
* </code></pre>
*
* @param callable the callable to produce the result
* @param <R> the type of the value
* @return a Result
*/
@API(status = EXPERIMENTAL)
default <R> Result<R> result(final Callable<R> callable) {
return Result.of(callable);
}
/**
* Converts the {@code Result} into an {@link Either}.
*
* <pre><code>
* Result&lt;String&gt; success = Result.ok("success");
* RuntimeException exception = new RuntimeException();
* Result&lt;String&gt; error = Result.error(String.class, exception);
*
* Either&lt;Throwable, String&gt; eitherRight = success.toEither();
* Either&lt;Throwable, String&gt; eitherLeft = error.toEither();
* </code></pre>
*
* @return A {@code Right} for a success or a {@code Left} for an error.
*/
@API(status = EXPERIMENTAL)
default Either<Throwable, T> toEither() {
var either = new AtomicReference<Either<Throwable, T>>();
match(
success -> either.set(Either.right(success)),
error -> either.set(Either.left(error))
);
return either.get();
}
/**
* Extracts the successful value from the result, or throws a {@link CheckedErrorResultException} with the error
* as the cause.
*
* <pre><code>
* Integer result = Result.of(() -&gt; getValue())
* .orElseThrow();
* </code></pre>
*
* @return the value if a success
* @throws CheckedErrorResultException if the result is an error
*/
@API(status = STABLE)
T orElseThrow() throws CheckedErrorResultException;
/**
* Return the successful value from the result, or throws the error if it is an instance of the type specified,
* otherwise it will throw an {@link UnexpectedErrorResultException} with the error as the cause.
*
* <pre><code>
* Integer result = Result.of(() -&gt; getValue())
* .orElseThrow(IOException.class);
* </code></pre>
*
* @param type the type of checked exception that may be thrown
* @param <E> the type of the checked exception to throw
* @return the value if a success
* @throws E if the result is an error
*/
@API(status = STABLE)
<E extends Exception> T orElseThrow(Class<E> type) throws E;
/**
* Returns the successful value from the result, or throws an {@link ErrorResultException}, an unchecked exception,
* with the error as the cause.
*
* <pre><code>
* Integer result = Result.of(() -&gt; getValue())
* .orElseThrowUnchecked();
* </code></pre>
*
* @return the value if a success
*/
@API(status = STABLE)
T orElseThrowUnchecked();
/**
* Applies the function to the value within the {@code Result} and returns the result if this is a success,
* otherwise returns a new {@code Result} with the existing error.
*
* <pre><code>
* Result&lt;String&gt; result = Result.of(() -&gt; getValue())
* .flatMap(v -&gt; Result.of(() -&gt; String.valueOf(v)));
* </code></pre>
*
* @param f the mapping function the produces a Result
* @param <R> the type of the value withing the Result of the mapping function
* @return a Result
*/
@API(status = STABLE)
<R> Result<R> flatMap(Function<T, Result<R>> f);
/**
* Applies the function to the value within the {@code Result} and returns the void result if this is a success,
* otherwise returns a new {@code Result} with the existing error.
*
* <pre><code>
* ResultVoid result = Result.of(() -> getValue())
* .flatMapV(v -> Result.ok());
* </code></pre>
*
* @param f the mapping function the produces a ResultVoid
* @return a ResultVoid
*/
@API(status = STABLE)
ResultVoid flatMapV(Function<T, ResultVoid> f);
/**
* Applies the function to the value within the {@code Result}, returning
* the result within another {@code Result}.
*
* <p>If the initial {@code Result} is a success, then apply the function to
* the value within the {@code Result}, returning the result within another
* {@code Result}. If the initial {@code Result} is an error, then return
* another error without invoking the supplied function.</p>
*
* <p>If the supplied function throws an exception, then an error
* {@code Result} will be returned containing that exception.</p>
*
* <pre><code>
* Result&lt;String&gt; result = Result.of(() -&gt; getValue())
* .map(v -&gt; String.valueOf(v));
* </code></pre>
*
* @param f the function to apply
* @param <R> the type of the value returned by the function to be applied
* @return A {@code Result} containing either the original error, the
* function output, or any exception thrown by the supplied function.
*/
@Override @Override
<R> Result<R> map(Function<T, R> f); @API(status = STABLE)
<R> Result<R> map(ThrowableFunction<T, R, ?> f);
/** /**
* Matches the Result, either success or error, and supplies the appropriate Consumer with the value or error. * Matches the Result, either success or error, and supplies the appropriate
* Consumer with the value or error.
*
* <pre><code>
* Result.of(()-> getValue())
* .match(
* success -> doSomething(success),
* error -> handleError(error)
* );
* </code></pre>
* *
* @param onSuccess the Consumer to pass the value of a successful Result to * @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
*/ */
@API(status = STABLE)
void match(Consumer<T> onSuccess, Consumer<Throwable> onError); 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);
/** /**
* Provide the value within the Result, if it is a success, to the Consumer, and returns this Result. * Provide the value within the Result, if it is a success, to the Consumer, and returns this Result.
* *
* <pre><code>
* Result&lt;Integer&gt; result = Result.of(() -&gt; getValue())
* .peek(v -&gt; System.out.println(v));
* </code></pre>
*
* @param consumer the Consumer to the value if a success * @param consumer the Consumer to the value if a success
* @return this Result * @return this Result
*/ */
@API(status = STABLE)
Result<T> peek(Consumer<T> consumer); Result<T> peek(Consumer<T> consumer);
/** /**
* Provide a way to attempt to recover from an error state. * Attempts to restore an error {@code Result} to a success.
*
* <p>When the Result is already a success, then the result is returned
* unmodified.</p>
*
* <pre><code>
* Result&lt;Integer&gt; result = Result.of(() -&gt; getValue())
* .recover(e -> Result.of(() -&gt; getSafeValue(e)));
* </code></pre>
* *
* @param f the function to recover from the error * @param f the function to recover from the error
* @return a new Result, either a Success, or if recovery is not possible an other Err. * @return if Result is an error, a new Result, either a Success, or if
* recovery is not possible another error. If the Result is already a
* success, then this returns itself.
*/ */
@API(status = STABLE)
Result<T> recover(Function<Throwable, Result<T>> f); Result<T> recover(Function<Throwable, Result<T>> f);
/** /**
* A handler for error states. * A handler for success states.
* *
* <p>When this is an error then tne Consumer will be supplier with the error. When this is a success, then nothing * <pre><code>
* happens.</p> * void handleSuccess(Integer value) {...}
* Result.of(() -> getValue())
* .onSuccess(v -> handleSuccess(v));
* </code></pre>
* *
* @param errorConsumer the consumer to handle the error * <p>When this is a success then tne Consumer will be supplied with the
* success value. When this is an error, then nothing happens.</p>
*
* @param successConsumer the consumer to handle the success
*/ */
void onError(Consumer<Throwable> errorConsumer); @API(status = STABLE)
void onSuccess(Consumer<T> successConsumer);
/**
* A handler for error state, when the error matches the errorClass.
*
* <p>If the `Result` is an error and that error is an instance of the
* errorClass, then supply the error to the `Consumer`. Does nothing if the
* error is not an instance of the errorClass, or is a success.</p>
*
* <p>Similar to the catch block in a try-catch.</p>
*
* <pre><code>
* void handleError(UnsupportedOperationException e) {...}
* Result.of(() -&gt; getValue())
* .onError(UnsupportedOperationException.class,
* e -&gt; handleError(e))
* </code></pre>
*
* @param errorClass the class of Throwable to match
* @param consumer the consumer to call if it matches
* @param <E> the Type of the Throwable to match
* @return the original unmodified Result
*/
@API(status = STABLE)
<E extends Throwable> Result<T> onError(
Class<E> errorClass,
Consumer<E> consumer
);
/** /**
* Maps a Success Result to another Result using a Callable that is able to throw a checked exception. * Maps a Success Result to another Result using a Callable that is able to throw a checked exception.
@ -272,10 +744,10 @@ public interface Result<T> extends Functor<T, Result<?>> {
* <p>Combination of {@link #flatMap(Function)} and {@link #of(Callable)}.</p> * <p>Combination of {@link #flatMap(Function)} and {@link #of(Callable)}.</p>
* *
* <pre><code> * <pre><code>
* Integer doSomething() {...} * Integer doSomething() {...}
* String doSomethingElse(final Integer value) {...} * String doSomethingElse(final Integer value) {...}
* Result&lt;String&gt; r = Result.of(() -&gt; doSomething()) * Result&lt;String&gt; r = Result.of(() -&gt; doSomething())
* .andThen(value -&gt; () -&gt; doSomethingElse(value)); * .andThen(value -&gt; () -&gt; doSomethingElse(value));
* </code></pre> * </code></pre>
* *
* <p>When the Result is an Err, then the original error is carried over and the Callable is never called.</p> * <p>When the Result is an Err, then the original error is carried over and the Callable is never called.</p>
@ -283,18 +755,47 @@ public interface Result<T> extends Functor<T, Result<?>> {
* @param f the function to map the Success value into the Callable * @param f the function to map the Success value into the Callable
* @param <R> the type of the final Result * @param <R> the type of the final Result
* @return a new Result * @return a new Result
* @deprecated Use {@link #map(ThrowableFunction)}
*/ */
@API(status = DEPRECATED)
@Deprecated
<R> Result<R> andThen(Function<T, Callable<R>> f); <R> Result<R> andThen(Function<T, Callable<R>> f);
/**
* Perform the continuation with the value within the success {@code Result}
* and return itself.
*
* <p>Where the {@code Result} is a success, then if an exception is thrown
* by the continuation the {@code Result} returned will be a new error
* {@code Result} containing that exception, otherwise the original
* {@code Result}will be returned.</p>
* <p>Where the {@code Result} is an error, then the {@code Result} is
* returned immediately and the continuation is ignored.</p>
*
* <pre><code>
* Integer doSomething() {...}
* void doSomethingElse(final Integer value) {...}
* Result&lt;Integer&gt; r = Result.of(() -&gt; doSomething())
* .thenWith(value -&gt; () -&gt; doSomethingElse(value));
* </code></pre>
*
* @param f the function to map the Success value into the result
* continuation
* @return the Result or a new error Result
*/
@API(status = STABLE)
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 * Perform the continuation with the current Result value then return the current Result, assuming there was no
* error in the continuation. * error in the continuation.
* *
* <pre><code> * <pre><code>
* Integer doSomething() {...} * Integer doSomething() {...}
* void doSomethingElse(final Integer value) {...} * void doSomethingElse(final Integer value) {...}
* Result&lt;Integer&gt; r = Result.of(() -&gt; doSomething()) * Result&lt;Integer&gt; r = Result.of(() -&gt; doSomething())
* .thenWith(value -&gt; () -&gt; doSomethingElse(value)); * .thenWith(value -&gt; () -&gt; doSomethingElse(value));
* </code></pre> * </code></pre>
* *
* <p>Where the Result is an Err, then the Result is returned immediately and the continuation is ignored.</p> * <p>Where the Result is an Err, then the Result is returned immediately and the continuation is ignored.</p>
@ -304,7 +805,8 @@ public interface Result<T> extends Functor<T, Result<?>> {
* @param f the function to map the Success value into the result continuation * @param f the function to map the Success value into the result continuation
* @return the Result or a new error Result * @return the Result or a new error Result
*/ */
Result<T> thenWith(Function<T, WithResultContinuation<T>> f); @API(status = STABLE)
ResultVoid thenWithV(Function<T, WithResultContinuation<T>> f);
/** /**
* Reduce two Results of the same type into one using the reducing function provided. * Reduce two Results of the same type into one using the reducing function provided.
@ -312,9 +814,24 @@ public interface Result<T> extends Functor<T, Result<?>> {
* <p>If either Result is an error, then the reduce will return the error. If both are errors, then the error of * <p>If either Result is an error, then the reduce will return the error. If both are errors, then the error of
* {@code this} Result will be returned.</p> * {@code this} Result will be returned.</p>
* *
* @param identify the identify Result * @param identify the identity Result
* @param operator the function to combine the values the Results * @param operator the function to combine the values the Results
* @return a Result containing the combination of the two Results * @return a Result containing the combination of the two Results
*/ */
@API(status = EXPERIMENTAL)
Result<T> reduce(Result<T> identify, BinaryOperator<T> operator); Result<T> reduce(Result<T> identify, BinaryOperator<T> operator);
/**
* Discard any success value while retaining any error.
*
* <pre><code>
* ResultVoid result = Result.of(() -&gt; getResultValue())
* .toVoid();
* </code></pre>
*
* @return A {@code SuccessVoid} for a {@code Success} or a {@code ErrVoid} for an {@code Err}.
*/
@API(status = STABLE)
ResultVoid toVoid();
} }

View file

@ -0,0 +1,134 @@
package net.kemitix.mon.result;
import org.apiguardian.api.API;
import java.util.concurrent.Callable;
import java.util.function.Consumer;
import java.util.function.Function;
import static org.apiguardian.api.API.Status.STABLE;
/**
* A @{link Result} with no value.
*/
public interface ResultVoid extends BaseResult {
/**
* Matches the Result, either success or error, and supplies the appropriate
* Consumer with the value or error.
*
* <pre><code>
* Result.ok()
* .match(
* () -> doSomething(),
* error -> handleError(error)
* );
* </code></pre>
*
* @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
*/
@API(status = STABLE)
void match(Runnable onSuccess, Consumer<Throwable> onError);
/**
* Attempts to restore an error {@code ResultVoid} to a success.
*
* <p>When the Result is already a success, then the result is returned
* unmodified.</p>
*
* <pre><code>
* void doSomethingRisky(String s) throws Exception {...}
* ResultVoid result = Result.ofVoid(() -> doSomethingRisky("first"))
* .recover(e -> Result.ofVoid(() -> doSomethingRisky("second")));
* </code></pre>
*
* @param f the function to recover from the error
* @return if Result is an error, a new Result, either a Success, or if
* recovery is not possible another error. If the Result is already a
* success, then this returns itself.
*/
@API(status = STABLE)
ResultVoid recover(Function<Throwable, ResultVoid> f);
/**
* A handler for success states.
*
* <pre><code>
* void doSomethingRisky() throws Exception {...}
* void handleSuccess() {...}
* Result.ofVoid(() -> doSomethingRisky()) // ResultVoid
* .onSuccess(() -> handleSuccess());
* </code></pre>
*
* <p>When this is a success then tne Consumer will be supplied with the
* success value. When this is an error, then nothing happens.</p>
*
* @param runnable the call if the Result is a success
*/
@API(status = STABLE)
void onSuccess(Runnable runnable);
/**
* A handler for error state, when the error matches the errorClass.
*
* <p>If the `Result` is an error and that error is an instance of the
* errorClass, then supply the error to the `Consumer`. Does nothing if the
* error is not an instance of the errorClass, or is a success.</p>
*
* <p>Similar to the catch block in a try-catch.</p>
*
* <pre><code>
* void handleError(UnsupportedOperationException e) {...}
* Result.of(() -&gt; getValue())
* .onError(UnsupportedOperationException.class,
* e -&gt; handleError(e))
* </code></pre>
*
* @param errorClass the class of Throwable to match
* @param consumer the consumer to call if it matches
* @param <E> the Type of the Throwable to match
* @return the original unmodified Result
*/
@API(status = STABLE)
<E extends Throwable> ResultVoid onError(
Class<E> errorClass,
Consumer<E> consumer
);
/**
* Execute the callable if the {@code Result} is a success, ignore it if is an error.
*
* <pre><code>
* Result.ofVoid(() -&gt; doSomethingRisky())
* .andThen(() -&gt; doSomethingRisky("again"));
* </code></pre>
*
* @param f the function to map the Success value into the Callable
* @return itself unless the callable fails when it will return a new error Result
*/
@API(status = STABLE)
ResultVoid andThen(VoidCallable f);
/**
* Replaces the current Result with the result of the callable.
*
* <p>Discards the success/error state or the current Result.</p>
*
* <p>If the callable results in a new error, then that error will be in the returned Result.</p>
*
* <pre><code>
* Result&lt;Integer&gt; result = Result.ofVoid(() -&gt; doSomethingRisky())
* .inject(() -&gt; 1);
* </code></pre>
*
* @param callable the callable to create the new value
* @param <T> the type of the new value
* @return a new Result with the result of callable
*/
default <T> Result<T> inject(Callable<T> callable) {
return Result.of(callable);
}
}

View file

@ -22,14 +22,12 @@
package net.kemitix.mon.result; package net.kemitix.mon.result;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import net.kemitix.mon.maybe.Maybe;
import java.util.Objects; import java.util.Objects;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
import java.util.function.BinaryOperator; import java.util.function.BinaryOperator;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.function.Function; import java.util.function.Function;
import java.util.function.Predicate;
/** /**
* A Successful Result. * A Successful Result.
@ -37,7 +35,8 @@ import java.util.function.Predicate;
* @param <T> the type of the value in the Result * @param <T> the type of the value in the Result
*/ */
@RequiredArgsConstructor @RequiredArgsConstructor
@SuppressWarnings({"methodcount", "PMD.CyclomaticComplexity"}) @SuppressWarnings({"methodcount", "PMD.TooManyMethods", "PMD.ExcessivePublicCount",
"PMD.CyclomaticComplexity"})
class Success<T> implements Result<T> { class Success<T> implements Result<T> {
private final T value; private final T value;
@ -54,11 +53,11 @@ class Success<T> implements Result<T> {
@Override @Override
@SuppressWarnings({"illegalcatch", "PMD.AvoidCatchingThrowable"}) @SuppressWarnings({"illegalcatch", "PMD.AvoidCatchingThrowable"})
public <R> Result<R> map(final Function<T, R> f) { public <R> Result<R> map(final ThrowableFunction<T, R, ?> f) {
try { try {
return success(f.apply(this.value)); return new Success<>(f.apply(value));
} catch (Throwable e) { } catch (Throwable e) {
return err(e); return new Err<>(e);
} }
} }
@ -67,14 +66,6 @@ class Success<T> implements Result<T> {
onSuccess.accept(value); onSuccess.accept(value);
} }
@Override
public Result<Maybe<T>> maybe(final Predicate<T> predicate) {
if (predicate.test(value)) {
return success(Maybe.just(value));
}
return success(Maybe.nothing());
}
@Override @Override
public T orElseThrow() { public T orElseThrow() {
return value; return value;
@ -101,11 +92,24 @@ class Success<T> implements Result<T> {
return this; return this;
} }
@Override
public void onSuccess(final Consumer<T> successConsumer) {
successConsumer.accept(value);
}
@Override @Override
public void onError(final Consumer<Throwable> errorConsumer) { public void onError(final Consumer<Throwable> errorConsumer) {
// do nothing - this is not an error // do nothing - this is not an error
} }
@Override
public <E extends Throwable> Result<T> onError(
final Class<E> errorClass,
final Consumer<E> consumer
) {
return this;
}
@Override @Override
public <R> Result<R> andThen(final Function<T, Callable<R>> f) { public <R> Result<R> andThen(final Function<T, Callable<R>> f) {
return result(f.apply(value)); return result(f.apply(value));
@ -116,6 +120,11 @@ class Success<T> implements Result<T> {
return f.apply(value).call(this); return f.apply(value).call(this);
} }
@Override
public ResultVoid thenWithV(final Function<T, WithResultContinuation<T>> f) {
return f.apply(value).call(this).toVoid();
}
@Override @Override
public Result<T> reduce(final Result<T> identity, final BinaryOperator<T> operator) { public Result<T> reduce(final Result<T> identity, final BinaryOperator<T> operator) {
return flatMap(a -> identity.flatMap(b -> result(() -> operator.apply(a, b)))); return flatMap(a -> identity.flatMap(b -> result(() -> operator.apply(a, b))));
@ -126,6 +135,11 @@ class Success<T> implements Result<T> {
return f.apply(value); return f.apply(value);
} }
@Override
public ResultVoid flatMapV(final Function<T, ResultVoid> f) {
return f.apply(value);
}
@Override @Override
public boolean equals(final Object other) { public boolean equals(final Object other) {
return other instanceof Success && Objects.equals(value, ((Success) other).value); return other instanceof Success && Objects.equals(value, ((Success) other).value);
@ -140,4 +154,9 @@ class Success<T> implements Result<T> {
public String toString() { public String toString() {
return String.format("Result.Success{value=%s}", value); return String.format("Result.Success{value=%s}", value);
} }
@Override
public ResultVoid toVoid() {
return SuccessVoid.getInstance();
}
} }

View file

@ -0,0 +1,91 @@
package net.kemitix.mon.result;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.function.Function;
@NoArgsConstructor(access = AccessLevel.PRIVATE)
@SuppressWarnings("PMD.AvoidDuplicateLiterals")
public class SuccessVoid implements ResultVoid {
private static final ResultVoid INSTANCE = new SuccessVoid();
/**
* Get the SuccessVoid instance.
*
* <p>The SuccessVoid, having no value, represents the state of success.</p>
*
* @return the SuccessVoid
*/
public static ResultVoid getInstance() {
return INSTANCE;
}
@Override
public boolean isError() {
return false;
}
@Override
public boolean isOkay() {
return true;
}
@Override
public void match(final Runnable onSuccess, final Consumer<Throwable> onError) {
onSuccess.run();
}
@Override
public ResultVoid recover(final Function<Throwable, ResultVoid> f) {
return this;
}
@Override
public void onSuccess(final Runnable runnable) {
runnable.run();
}
@Override
public void onError(final Consumer<Throwable> errorConsumer) {
// do nothing - this is not an error
}
@Override
public <E extends Throwable> ResultVoid onError(
final Class<E> errorClass,
final Consumer<E> consumer
) {
return this;
}
@Override
@SuppressWarnings("PMD.AvoidCatchingGenericException")
public ResultVoid andThen(final VoidCallable f) {
try {
f.call();
return this;
} catch (Exception e) {
return new ErrVoid(e);
}
}
@Override
public boolean equals(final Object other) {
return other instanceof SuccessVoid;
}
@Override
public int hashCode() {
return Objects.hash(SuccessVoid.class);
}
@Override
public String toString() {
return "Result.SuccessVoid{}";
}
}

View file

@ -0,0 +1,23 @@
package net.kemitix.mon.result;
/**
* Represents a function that accepts one argument and produces a result.
* This is a functional interface whose functional method is apply(Object).
*
* @param <T> the type of the input to the function
* @param <R> the type of the result of the function
* @param <E> the type of the exception that could be thrown
*/
@FunctionalInterface
public interface ThrowableFunction<T, R, E extends Throwable> {
/**
* Applies this function to the given argument.
*
* @param value the function argument
* @return the function result
* @throws E if the function fails
*/
R apply(T value) throws E;
}

View file

@ -0,0 +1,19 @@
package net.kemitix.mon.result;
/**
* A task that returns void and may throw an exception.
*
* <p>Implementors define a single method with no arguments called call.
* The VoidCallable interface is similar to {@link java.util.concurrent.Callable},
* but does not return a value.</p>
*/
@FunctionalInterface
public interface VoidCallable {
/**
* Executes and may throw an exception.
*
* @throws Exception if unable to complete
*/
@SuppressWarnings("PMD.SignatureDeclareThrowsException")
void call() throws Exception;
}

View file

@ -21,6 +21,8 @@
package net.kemitix.mon.result; package net.kemitix.mon.result;
import net.kemitix.mon.TypeReference;
/** /**
* A Callable-like interface for performing an action with a Result that, if there are no errors is returned as-is, but * A Callable-like interface for performing an action with a Result that, if there are no errors is returned as-is, but
* if there is an error then a new error Result is returned. * if there is an error then a new error Result is returned.
@ -50,7 +52,7 @@ public interface WithResultContinuation<T> {
try { try {
run(); run();
} catch (Throwable e) { } catch (Throwable e) {
return Result.error(e); return Result.error(TypeReference.create(), e);
} }
return currentResult; return currentResult;
} }

View file

@ -20,9 +20,28 @@
*/ */
/** /**
* An experiment in creating something similar to a Type-Alias in Java. * <h1>Result</h1>
* *
* <p>Ideas initially lifted from the Design with Types series at https://fsharpforfunandprofit.com/</p> * Allows handling error conditions without the need to {@code catch}
* exceptions.
*
* <p>When a {@link Result} is returned from a method, it will be in one of two
* states: {@link Success} or {@link Err}. The {@code Success} state will
* contain a value from the method. The {@code Err} state will contain a
* {@link java.lang.Throwable} detailing the reason for the failure.</p>
*
* <p>Methods returning a {@code Result} should not throw any exceptions.</p>
*
* <p>{@code Result} is a Monad.</p>
*
* <h2>Static Constructors:</h2>
* <ul>
* <li>{@link Result#ok()}</li>
* <li>{@link Result#ok(Object)}</li>
* <li>{@link Result#of(Callable)}</li>
* <li>{@link Result#ofVoid(VoidCallable)}</li>
* <li>{@link Result#error(Throwable)}</li>
* </ul>
* *
* @author Paul Campbell (pcampbell@kemitix.net) * @author Paul Campbell (pcampbell@kemitix.net)
*/ */

View file

@ -114,11 +114,17 @@ class MaybeTest implements WithAssertions {
} }
@Test @Test
void fromOptional() { void mapFromOptional() {
assertThat(Optional.of(1).map(Maybe::just).orElseGet(Maybe::nothing)).isEqualTo(just(1)); assertThat(Optional.of(1).map(Maybe::just).orElseGet(Maybe::nothing)).isEqualTo(just(1));
assertThat(Optional.empty().map(Maybe::just).orElseGet(Maybe::nothing)).isEqualTo(nothing()); assertThat(Optional.empty().map(Maybe::just).orElseGet(Maybe::nothing)).isEqualTo(nothing());
} }
@Test
void fromOptional() {
assertThat(Maybe.fromOptional(Optional.of(1))).isEqualTo(just(1));
assertThat(Maybe.fromOptional(Optional.empty())).isEqualTo(nothing());
}
@Test @Test
void peek() { void peek() {
final AtomicInteger ref = new AtomicInteger(0); final AtomicInteger ref = new AtomicInteger(0);

File diff suppressed because it is too large Load diff