diff --git a/README.md b/README.md
index abd9cb0..905e9e8 100644
--- a/README.md
+++ b/README.md
@@ -1,19 +1,87 @@
[![Java CI with Gradle](https://github.com/xyzsd/dichotomy/actions/workflows/gradle.yml/badge.svg)](https://github.com/xyzsd/dichotomy/actions/workflows/gradle.yml)
# dichotomy
-Either and Result monadic types for Java.
-Includes specialized types Try and Maybe.
+Sealed monads for Java.
-All types are sealed, and can be used in switch statements and
+Generally these types are used to return one of two values, such as success or failure.
+
+All types are sealed (Sum types), and can be used in `switch` expressions and with
pattern matching.
-## UPDATE (18-Dec-2023):
- * Substantially restructured and improved for the pending 1.0 version
- * Markedly improved Try type, added Maybe type
- * Tests near-complete
- * Improved documentation
- * still needs: a nice intro (with synopsis and illustrative examples)
+### `Either`:
+An general immutable type that can only be *either* one of two types.
+The types are called `Left` and `Right`. By convention, the Left type
+indicates failure, while the Right type indicates success.
+
+### `Result`:
+Similar to an `Either`, but with success/failure semantics more clearly defined.
+An `OK` Result indicates success, and an `Err` Result indicates failure. Failure
+types do not need to be Exceptions.
+
+ ```java
+
+Result result = Result.ofOK(3828) // returns an OK
+ .map(x -> x*10.0) // map to Result, after multiplying x 10
+ .match(System.out::println) // print "38280.0" to console
+ .matchErr(System.err::println); // ignored, as this is an OK
+
+switch(result) {
+ case OK ok -> System.out.println("value ok! value: "+ok.value());
+ case Err err -> System.err.println(err.value());
+}
+
+// JDK 21+
+switch(result) {
+ case OK(Double x) when x > 0 -> System.out.println("positive");
+ case OK(Double x) -> System.out.println("0 or negative");
+ case Err(String s) -> System.err.println(s);
+}
+
+// anotherResult here will be an Err
+Result anotherResult = Result.ofErr("Insufficient entropy")
+ .map(x -> x*10.0 ) // ignored, as this is an Err
+ .match(System.out::println) // ignored, as this is an Err
+ .matchErr(System.err::println); // "Insufficient entropy" printed to System.err
+```
+
+
+### `Try`:
+A specialized type of `Result`. A `Try` wraps a function or block; if
+successful, a `Success` Try is returned; otherwise, a `Failure` Try containing
+an Exception is returned. Intermediate operations which return Trys will also
+catch generated Exceptions.
+
+```java
+
+final Try result = Try.ofSuccess( 777 )
+ .map( i -> i * 1000 ) // results in a Try with a value of 777000
+ .exec( System.out::println ) // prints "777000"
+ .map( i -> i / 0 ) // the ArithmeticException is caught as a Try.Failure
+ .exec( System.out::println ); // does not exec() because we are a Failure
+
+// prints "ERROR: java.lang.ArithmeticException: / by zero"
+switch(result) {
+ case Success(Integer i) -> System.out.printf("Operation completed successfully. Value: %d\n", i);
+ case Failure(Throwable t) -> System.err.printf("ERROR: %s\n", t);
+}
+
+
+```
+
+### `Maybe`:
+Analogous to the JDK `Optional` type, but sealed so it may be used in `switch`
+statements and with pattern matching.
+
+## Updates (January 2024)
+ - [x] Refactored, now with substantial improvements
+ - [x] New Try class
+ - [x] New Maybe class (analogous to Optional)
+ - [x] Near-complete test coverage
+ - [x] Improved documentation
+ - [x] Targets JDK 21
+ - [ ] Examples (still in progress)
+ - [ ] Maven release
Download
--------
diff --git a/src/main/java/net/xyzsd/dichotomy/Result.java b/src/main/java/net/xyzsd/dichotomy/Result.java
index a29ecf3..2eaecfa 100644
--- a/src/main/java/net/xyzsd/dichotomy/Result.java
+++ b/src/main/java/net/xyzsd/dichotomy/Result.java
@@ -1182,13 +1182,10 @@ public boolean containsErr(@Nullable E errValue) {
@Override
public @NotNull V expect() throws RuntimeException {
- // TODO: when pattern-switch is out of preview, convert this code
- if (value instanceof RuntimeException e) {
- throw e;
- } else if (value instanceof Throwable t) {
- throw new NoSuchElementException( t );
- } else {
- throw new NoSuchElementException( String.valueOf( value ) );
+ switch(value) {
+ case RuntimeException e -> throw e;
+ case Throwable t -> throw new NoSuchElementException(t);
+ default -> throw new NoSuchElementException( String.valueOf( value ) );
}
}
@@ -1198,7 +1195,7 @@ public boolean containsErr(@Nullable E errValue) {
throw requireNonNull( exFn.apply( value ) );
}
- // For types where the value type is unchanged and exists, but the generic type of the value differs
+ // For types where the value type is unchanged and exists, but the generic type of the value differs,
// just cast and return. Types are erased so there is no need to create a new object.
// The Error stays the same; only the empty value signature changes
@SuppressWarnings("unchecked")
diff --git a/src/main/java/net/xyzsd/dichotomy/collectors/EitherCollectors.java b/src/main/java/net/xyzsd/dichotomy/collectors/EitherCollectors.java
index e0f6ccd..dc9df2c 100644
--- a/src/main/java/net/xyzsd/dichotomy/collectors/EitherCollectors.java
+++ b/src/main/java/net/xyzsd/dichotomy/collectors/EitherCollectors.java
@@ -83,13 +83,9 @@ public interface EitherCollectors {
static private void add(Accumulator listBox, Either either) {
- // TODO: use switch/case when we support JDK > 20
- if(either instanceof Either.Right right) {
- listBox.okList.add( right.value() );
- } else if(either instanceof Either.Left left) {
- listBox.errList.add( left.value() );
- } else {
- throw new IllegalStateException();
+ switch(either) {
+ case Either.Right(R r) -> listBox.okList.add( r );
+ case Either.Left(L l) -> listBox.errList.add( l );
}
}
diff --git a/src/main/java/net/xyzsd/dichotomy/collectors/ResultCollectors.java b/src/main/java/net/xyzsd/dichotomy/collectors/ResultCollectors.java
index 4ebd326..7926798 100644
--- a/src/main/java/net/xyzsd/dichotomy/collectors/ResultCollectors.java
+++ b/src/main/java/net/xyzsd/dichotomy/collectors/ResultCollectors.java
@@ -82,13 +82,9 @@ public interface ResultCollectors {
static private void add(Accumulator listBox, Result result) {
- // TODO: use switch/case when we support JDK > 20
- if(result instanceof Result.OK ok) {
- listBox.okList.add( ok.value() );
- } else if(result instanceof Result.Err err) {
- listBox.errList.add( err.value() );
- } else {
- throw new IllegalStateException();
+ switch(result) {
+ case Result.OK(OK v) -> listBox.okList.add( v );
+ case Result.Err(ERR e) -> listBox.errList.add( e );
}
}
diff --git a/src/main/java/net/xyzsd/dichotomy/trying/Try.java b/src/main/java/net/xyzsd/dichotomy/trying/Try.java
index 0f07e2b..8760948 100644
--- a/src/main/java/net/xyzsd/dichotomy/trying/Try.java
+++ b/src/main/java/net/xyzsd/dichotomy/trying/Try.java
@@ -2,10 +2,7 @@
import net.xyzsd.dichotomy.Empty;
import net.xyzsd.dichotomy.Result;
-import net.xyzsd.dichotomy.trying.function.ExBiFunction;
-import net.xyzsd.dichotomy.trying.function.ExConsumer;
-import net.xyzsd.dichotomy.trying.function.ExFunction;
-import net.xyzsd.dichotomy.trying.function.ExSupplier;
+import net.xyzsd.dichotomy.trying.function.*;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -45,8 +42,6 @@
* If a method which takes an {@link ExConsumer}, {@link ExSupplier}, or {@link ExFunction} returns {@code null},
* the {@code NullPointerException} will be caught and returned as a {@link Failure}.
*
- *
- *
* @param the Try type, held by Success values.
*/
public sealed interface Try permits Try.Failure, Try.Success {
@@ -55,8 +50,8 @@ public sealed interface Try permits Try.Failure, Try.Success {
* Create a Successful Try.
*
* @param value contained by the {@link Success}
+ * @param value type
* @return {@link Success} containing the above value
- * @param value type
*/
@NotNull
static Try ofSuccess(@NotNull T value) {
@@ -67,8 +62,8 @@ static Try ofSuccess(@NotNull T value) {
* Create an unsuccessful Try (Failure).
*
* @param failure contained by the {@link Failure}
+ * @param Success value type (always empty in this case).
* @return {@link Failure} containing the above value
- * @param Success value type (always empty in this case).
*/
@NotNull
static Try ofFailure(@NotNull Throwable failure) {
@@ -76,33 +71,33 @@ static Try ofFailure(@NotNull Throwable failure) {
}
- // todo: show an example just wrapping a block of code.
+
/**
* Invoke an {@link ExSupplier} to create a {@link Try}.
*
- * If the {@link ExSupplier} is successful, a {@link Success} will be returned.
- * If an exception is thrown by the {@link ExSupplier}, a {@link Failure} will be returned.
+ * If the {@link ExSupplier} is successful, a {@link Success} will be returned.
+ * If an exception is thrown by the {@link ExSupplier}, a {@link Failure} will be returned.
*
*
- * {@link ExSupplier}s can throw checked or unchecked Exceptions.
- * To use a {@link Supplier} (which can only throw RuntimeExceptions), use as follows:
- * {@snippet :
+ * {@link ExSupplier}s can throw checked or unchecked Exceptions.
+ * To use a {@link Supplier} (which can only throw RuntimeExceptions), use as follows:
+ * {@snippet :
* Try suppliedTry = Try.of( myStringSupplier::get );
* // or alternatively:
- * Try suppliedTry2 = Try.of( ExSupplier.from(myStringSupplier) );
- * }
+ * Try suppliedTry2 = Try.wrap( ExSupplier.from(myStringSupplier) );
+ *}
*
*
- * If the {@code ExSupplier} returns {@code null}, this method
- * will return a {@link Failure} containing a {@link NullPointerException}.
+ * If the {@code ExSupplier} returns {@code null}, this method
+ * will return a {@link Failure} containing a {@link NullPointerException}.
*
*
* @param xSupplier a Supplier which could potentially throw an Exception.
+ * @param non-exceptional type
* @return supplied type or exception wrapped in a Try
- * @param non-exceptional type
*/
@NotNull
- static Try of(@NotNull ExSupplier xSupplier) {
+ static Try wrap(@NotNull ExSupplier xSupplier) {
requireNonNull( xSupplier );
try {
// we still need to wrap in a requireNonNull() because we could create
@@ -114,30 +109,72 @@ static Try of(@NotNull ExSupplier xSupplier) {
}
+ /**
+ * Invoke an {@link ExRunnable} to create a {@link Try}.
+ *
+ * If the {@link ExRunnable} is successful, a {@link Success} will be returned.
+ * If an exception is thrown by the {@link ExRunnable}, a {@link Failure} will be returned.
+ *
+ *
+ * {@link ExRunnable}s can throw checked or unchecked Exceptions.
+ * To use a {@link Runnable} (which can only throw RuntimeExceptions), use as follows:
+ * {@snippet :
+ * Try firstTry = Try.of( myRunnable::run );
+ * // or alternatively:
+ * Try secondTry = Try.wrap( ExRunnable.from(myRunnable) );
+ *}
+ *
+ *
+ * This can also be used to wrap blocks of code that could throw exceptions:
+ * {@snippet :
+ * Try blockTry = Try.wrap( () -> {
+ * String in = Files.readString( Path.of("path-to-a-file") );
+ * if(in.startsWith( "begin:")) {
+ * Files.writeString( Path.of("path-to-another-file") );
+ * }
+ * });
+ *}
+ *
+ * @param runnable a Runnable which could potentially throw an Exception
+ * @return Empty Try or an exception wrapped in a Try
+ */
+ @NotNull
+ static Try wrap(@NotNull ExRunnable runnable) {
+ requireNonNull( runnable );
+ try {
+ runnable.run();
+ return Try.Success.of();
+ } catch (Throwable t) {
+ return Failure.of( t );
+ }
+ }
+
+
+
/**
* Try-with-resources for a single AutoCloseable.
*
- * The ExSupplier will supply the AutoCloseable resource.
- * The ExFunction will take the supplied resource and return a result.
- * The supplied resource will be automatically closed (as per try-with-resource semantics).
- * If no exceptions occur, the function result will be wrapped in a {@link Success}.
+ * The ExSupplier will supply the AutoCloseable resource.
+ * The ExFunction will take the supplied resource and return a result.
+ * The supplied resource will be automatically closed (as per try-with-resource semantics).
+ * If no exceptions occur, the function result will be wrapped in a {@link Success}.
*
*
- * A {@link Failure} will be returned, containing an Exception when:
- *
- * - An Exception occurs in the supplier (if so, the function will not be invoked)
- * - An Exception occurs in the function (if so, the resource will still be closed)
- * - An Exception occurs when the resource is closed
- *
- * If an Exception occurs in the function (but not the supplier) and during resource closing, the
- * resource-closing Exception will be added as a suppressed exception (unless disallowed) to the
- * Exception thrown during function execution.
+ * A {@link Failure} will be returned, containing an Exception when:
+ *
+ * - An Exception occurs in the supplier (if so, the function will not be invoked)
+ * - An Exception occurs in the function (if so, the resource will still be closed)
+ * - An Exception occurs when the resource is closed
+ *
+ * If an Exception occurs in the function (but not the supplier) and during resource closing, the
+ * resource-closing Exception will be added as a suppressed exception (unless disallowed) to the
+ * Exception thrown during function execution.
*
* @param supplier AutoCloseable resource supplier
- * @param fn ExFunction to process AutoCloseable resource
+ * @param fn ExFunction to process AutoCloseable resource
+ * @param ExFunction result
+ * @param AutoCloseable resource and ExFunction input
* @return ExFunction result wrapped in a Try
- * @param ExFunction result
- * @param AutoCloseable resource and ExFunction input
*/
static Try withResources(ExSupplier supplier, ExFunction fn) {
requireNonNull( supplier );
@@ -154,14 +191,13 @@ static Try withResources(ExSupplier supplie
* Two-supplier, Two-parameter try-with-resources.
* See {@link #withResources(ExSupplier, ExFunction)}.
*
- *
* @param supplier1 First AutoCloseable resource supplier
* @param supplier2 Second AutoCloseable resource supplier
- * @param biFn function which can use both AutoCloseable resources supplied
+ * @param biFn function which can use both AutoCloseable resources supplied
+ * @param biFunction result
+ * @param First AutoCloseable resource and ExBiFunction input
+ * @param Second AutoCloseable resource and ExBiFunction input
* @return biFn result
- * @param biFunction result
- * @param First AutoCloseable resource and ExBiFunction input
- * @param Second AutoCloseable resource and ExBiFunction input
*/
static Try withResources(ExSupplier supplier1,
ExSupplier supplier2,
@@ -180,12 +216,14 @@ static Try withReso
/**
* Determines if this is a Failure.
+ *
* @return {@code true} if this is a {@link Failure}.
*/
boolean isFailure();
/**
* Determines if this is a Success.
+ *
* @return {@code true} if this is a {@link Success}.
*/
boolean isSuccess();
@@ -199,7 +237,7 @@ static Try withReso
*
* @param fnSuccess the mapping function for {@link Success} values.
* @param fnFailure the mapping function for {@link Failure} values.
- * @param returned type, which can be different from the original type
+ * @param returned type, which can be different from the original type
* @return the {@link Try} produced from {@code fnSuccess} or {@code fnFailure}
* @throws NullPointerException if the called function returns {@code null}.
* @see #map(ExFunction)
@@ -208,8 +246,6 @@ static Try withReso
@NotNull ExFunction super Throwable, ? extends Try extends V2>> fnFailure);
-
-
/**
* Returns a value, produced from one of the appropriate mapping functions.
*
@@ -219,7 +255,7 @@ static Try withReso
*
* @param fnSuccess the mapping function for {@link Failure} values.
* @param fnFailure the mapping function for {@link Success} values.
- * @param returned type, which must be the same for both Failure and Success values.
+ * @param returned type, which must be the same for both Failure and Success values.
* @return the value produced from {@code fnSuccess} or {@code fnFailure}
* @throws NullPointerException if the called function returns {@code null}.
* @see #recover(Function)
@@ -258,8 +294,8 @@ static Try withReso
/**
* Executes the action iff this is an {@link Success} {@link Try}.
*
- * @return {@code this} if successful, otherwise returns a {@link Failure} containing the Exception.
* @param successConsumer Consumer of Success values
+ * @return {@code this} if successful, otherwise returns a {@link Failure} containing the Exception.
* @throws NullPointerException if successConsumer is {@code null}.
* @see #consume(Consumer)
* @see #consumeErr(Consumer)
@@ -299,7 +335,7 @@ static Try withReso
*
*
* @param fnSuccess the mapping function producing a new {@link Success} value.
- * @param returned type, which can be different from the original type
+ * @param returned type, which can be different from the original type
* @return a new {@link Success} produced by the mapping function, if applied.
*/
@NotNull Try map(@NotNull ExFunction super V, ? extends V2> fnSuccess);
@@ -332,7 +368,7 @@ static Try withReso
*
* @param fnSuccess the mapping function for {@link Success} values.
* @param fnFailure the mapping function for {@link Failure} values.
- * @param returned type, which can be different from the original type
+ * @param returned type, which can be different from the original type
* @return the {@link Try} produced from {@code okMapper} or {@code errMapper}, or a {@link Failure} if an Exception is caught.
* @see #map(ExFunction)
* @see #mapErr(ExFunction)
@@ -356,7 +392,7 @@ static Try withReso
*
*
* @param fnSuccess the mapping function that produces a new {@link Try}
- * @param returned type, which can be different from the original type
+ * @param returned type, which can be different from the original type
* @return a new {@link Try} produced by the mapping function, if applied
* @see #flatMapErr(ExFunction)
* @see #biFlatMap(ExFunction, ExFunction)
@@ -432,17 +468,16 @@ static Try withReso
@NotNull V orElseGet(@NotNull Supplier extends V> okSupplier);
-
/**
* If {@code this} is {@link Failure}, return it. Otherwise, return the next {@link Try} given.
* The next {@link Try} can have a different parameterized type.
*
* @param nextTry The {@link Try} to return.
- * @param type of the value, which can be different from the original type
+ * @param type of the value, which can be different from the original type
+ * @return this or the given Try
* @see #and(ExSupplier)
* @see #or(Try)
* @see #or(ExSupplier)
- * @return this or the given Try
*/
@NotNull Try and(@NotNull Try nextTry);
@@ -453,12 +488,12 @@ static Try withReso
* The next {@link Try} can have a different parameterized type.
*
* @param nextTrySupplier The supplier of a {@link Try} to return; only called if {@code this} is a {@link Success}.
- * @param type of the value, which can be different from the original type
+ * @param type of the value, which can be different from the original type
+ * @return this or the supplied Try
* @throws NullPointerException if the supplied {@link Try} is {@code null}.
* @see #and(Try)
* @see #or(Try)
* @see #or(ExSupplier)
- * @return this or the supplied Try
*/
default @NotNull Try and(@NotNull final ExSupplier> nextTrySupplier) {
requireNonNull( nextTrySupplier );
@@ -466,17 +501,15 @@ static Try withReso
}
-
-
/**
* If {@code this} is {@link Success}, return it.
* Otherwise, return the next {@link Try} given.
*
* @param nextTry The {@link Try} to return.
+ * @return this or the given Try
* @see #or(ExSupplier)
* @see #and(Try)
* @see #and(ExSupplier)
- * @return this or the given Try
*/
@NotNull Try or(@NotNull Try nextTry);
@@ -485,11 +518,11 @@ static Try withReso
* Otherwise, return the next {@link Try} supplied.
*
* @param nextTrySupplier The supplier of a {@link Try} to return; only called if {@code this} is a {@link Failure}.
+ * @return this or the supplied Try
* @throws NullPointerException if the supplier is called and returns {@code null}.
* @see #or(Try)
* @see #and(Try)
* @see #and(ExSupplier)
- * @return this or the supplied Try
*/
default @NotNull Try or(@NotNull ExSupplier> nextTrySupplier) {
requireNonNull( nextTrySupplier );
@@ -497,9 +530,6 @@ static Try withReso
}
-
-
-
/**
* Recover from an value; ignore the {@link Failure} value if present,
* and apply the mapping function to get an {@link Success}.
@@ -559,7 +589,6 @@ static Try withReso
* getOrThrow( (x) -> new IOException("This is my exception message"));
*}
*
- *
* @param exFn Exception (Throwable) mapping function
* @param Exception to throw; if not a (subclass of) RuntimeException, it must be rethrown or caught
* @return V
@@ -590,7 +619,6 @@ private static Try flatMapChecked(IN in, ExFunction super IN, ?
}
-
@SuppressWarnings("unchecked")
private static void sneakyThrow(Throwable e) throws E {
throw (E) e;
@@ -605,11 +633,12 @@ private static void sneakyThrow(Throwable e) throws E {
* A Successful Try.
*
* @param value Successful value
- * @param value type parameter
+ * @param value type parameter
*/
record Success(@NotNull T value) implements Try {
/**
* A Successful Try.
+ *
* @param value successful value.
*/
public Success {
@@ -618,6 +647,7 @@ record Success(@NotNull T value) implements Try {
/**
* Create a Successful empty Try.
+ *
* @return Successful Try with the {@link Empty} type.
*/
public static Success of() {
@@ -626,8 +656,9 @@ public static Success of() {
/**
* Create a Successful Try.
+ *
* @param value successful value.
- * @param type of value
+ * @param type of value
* @return Success containing the given value
*/
public static Success of(@NotNull final U value) {
@@ -775,7 +806,6 @@ public boolean contains(@Nullable T okValue) {
}
-
@Override
public @NotNull T recover(@NotNull Function super Throwable, ? extends T> fnFailureToSuccess) {
requireNonNull( fnFailureToSuccess );
@@ -813,6 +843,7 @@ record Failure(@NotNull Throwable err) implements Try {
/**
* An Unsuccessful Try. This will never permit 'fatal' exceptions (see documentation for {@link Try}).
+ *
* @param err Throwable
*/
public Failure {
@@ -822,9 +853,10 @@ record Failure(@NotNull Throwable err) implements Try {
/**
* Create an Unsuccessful Try. This will never permit 'fatal' exceptions (see documentation for {@link Try}).
- * @param t value value
- * @return {@code Try.Failure} containing the value value.
+ *
+ * @param t value value
* @param Success value type (always empty)
+ * @return {@code Try.Failure} containing the value value.
*/
public static Failure of(@NotNull Throwable t) {
return new Failure<>( t );
@@ -997,11 +1029,11 @@ private Failure coerce() {
* For Throwables which are truly fatal, we will not capture.
*/
private static void throwIfFatal(final Throwable t) {
- // TODO: add MatchException when we target JDK > 20
- // and restructure to a switch-case w/pattern
if (t instanceof VirtualMachineError || t instanceof LinkageError) {
- // Errors are similar to unchecked exceptions
+ // Errors are similar to unchecked (Runtime) exceptions
throw (Error) t;
+ } else if (t instanceof MatchException me) {
+ throw me; // a RuntimeException
} else if (t instanceof InterruptedException) {
sneakyThrow( t );
}
diff --git a/src/main/java/net/xyzsd/dichotomy/trying/function/ExRunnable.java b/src/main/java/net/xyzsd/dichotomy/trying/function/ExRunnable.java
new file mode 100644
index 0000000..5e5ce64
--- /dev/null
+++ b/src/main/java/net/xyzsd/dichotomy/trying/function/ExRunnable.java
@@ -0,0 +1,33 @@
+package net.xyzsd.dichotomy.trying.function;
+
+import org.jetbrains.annotations.NotNull;
+
+
+import static java.util.Objects.requireNonNull;
+
+/**
+ * An {@link ExRunnable} is equivalent to a {@link Runnable}, but the
+ * {@link ExRunnable#run()} method can throw a checked Exception.
+ *
+ */
+@FunctionalInterface
+public interface ExRunnable {
+
+ /**
+ * Execute the given operation.
+ * @throws Throwable Exception
+ */
+ void run() throws Throwable;
+
+ /**
+ * Convert a Runnable to an ExRunnable. The Consumer is only invoked when used.
+ *
+ * @param runnable the Runnable
+ * @return ExRunnable
+ */
+ @NotNull static ExRunnable from(@NotNull final Runnable runnable) {
+ requireNonNull( runnable );
+ return runnable::run;
+ }
+
+}
diff --git a/src/main/java/net/xyzsd/dichotomy/trying/function/package-info.java b/src/main/java/net/xyzsd/dichotomy/trying/function/package-info.java
index 3053f96..776bdc1 100644
--- a/src/main/java/net/xyzsd/dichotomy/trying/function/package-info.java
+++ b/src/main/java/net/xyzsd/dichotomy/trying/function/package-info.java
@@ -8,5 +8,10 @@
* These interfaces are primarily provided for use with {@link net.xyzsd.dichotomy.trying.Try}, to
* allow functional code to wrap and use exception-generating methods in a convenient manner.
*
+ *
+ * These interfaces need be used if Runtime exceptions are thrown; instead, standard JDK
+ * functional interfaces (e.g., {@code Runnable, Consumer, Supplier, Function, BiFunction})
+ * can be used instead.
+ *
*/
package net.xyzsd.dichotomy.trying.function;
\ No newline at end of file
diff --git a/src/test/java/net/xyzsd/dichotomy/ResultTest.java b/src/test/java/net/xyzsd/dichotomy/ResultTest.java
index 1593b76..5caeb9b 100644
--- a/src/test/java/net/xyzsd/dichotomy/ResultTest.java
+++ b/src/test/java/net/xyzsd/dichotomy/ResultTest.java
@@ -565,4 +565,45 @@ void monadicLaws() {
MTEST_ERR.flatMapErr( s -> hundredMore.apply( s ).flatMapErr( square ) )
);
}
+
+ @Test
+ void highlyContrivedAndNotVeryGoodExamples() {
+ // OK: first example
+ final Result firstResult = Result.ofOK( 3828 ) // returns an OK
+ .map( x -> x * 10.0 ) // map to float, after multiplying x 10
+ .match( System.out::println ) // print "38280.0" to console
+ .matchErr( System.err::println );// ignored, as this is an OK
+
+ // prints 'value ok!'
+ switch(firstResult) {
+ case OK ok -> System.out.println("value ok! value: "+ok.value());
+ case Err err -> System.err.println(err.value());
+ }
+
+ // prints 'positive'
+ switch(firstResult) {
+ case OK(Double x) when x > 0 -> System.out.println("positive");
+ case OK(Double x) -> System.out.println("0 or negative");
+ case Err(String s) -> System.err.println(s);
+ }
+
+ // Err: second example
+ Result errResult = Result.ofErr("Insufficient entropy")
+ .map(x -> x*10.0 ) // ignored, as this is an Err
+ .match(System.out::println) // ignored, as this is an Err
+ .matchErr(System.err::println); // "Insufficient entropy" printed to System.err
+
+ // prints 'ERROR: Insufficient entropy'
+ switch(errResult) {
+ case OK ok -> System.out.println("value ok! value: "+ok.value());
+ case Err err -> System.err.printf("ERROR: %s\n", err.value());
+ }
+
+ // prints 'ERROR: Insufficient entropy'
+ switch(errResult) {
+ case OK(Double x) when x > 0 -> System.out.println("positive");
+ case OK(Double x) -> System.out.println("0 or negative");
+ case Err(String s) -> System.err.printf("ERROR: %s\n", s);
+ }
+ }
}
\ No newline at end of file
diff --git a/src/test/java/net/xyzsd/dichotomy/trying/TryTest.java b/src/test/java/net/xyzsd/dichotomy/trying/TryTest.java
index f4abd7d..1c7a180 100644
--- a/src/test/java/net/xyzsd/dichotomy/trying/TryTest.java
+++ b/src/test/java/net/xyzsd/dichotomy/trying/TryTest.java
@@ -1,9 +1,11 @@
package net.xyzsd.dichotomy.trying;
-import net.xyzsd.dichotomy.Empty;
import net.xyzsd.dichotomy.TestUtils.SingleUseConsumer;
+import net.xyzsd.dichotomy.trying.Try.Failure;
+import net.xyzsd.dichotomy.trying.Try.Success;
import net.xyzsd.dichotomy.trying.function.ExConsumer;
import net.xyzsd.dichotomy.trying.function.ExFunction;
+import net.xyzsd.dichotomy.trying.function.ExRunnable;
import net.xyzsd.dichotomy.trying.function.ExSupplier;
import org.junit.jupiter.api.Test;
@@ -82,9 +84,9 @@ public void close() throws IOException {
private static void assertMessage(final String expectedText, Try> tried) {
requireNonNull( expectedText );
requireNonNull( tried );
- if (tried instanceof Try.Success>) {
+ if (tried instanceof Success>) {
throw new AssertionError( "Expected Failure, not " + tried );
- } else if (tried instanceof Try.Failure> failure) {
+ } else if (tried instanceof Failure> failure) {
if (!Objects.equals( expectedText, failure.err().getMessage() )) {
throw new AssertionError( String.format( "Expected '%s' Actual: '%s'",
expectedText,
@@ -101,9 +103,9 @@ private static void assertException(final Class excepti
requireNonNull( exceptionClass );
requireNonNull( tried );
- if (tried instanceof Try.Success>) {
+ if (tried instanceof Success>) {
throw new AssertionError( "Expected Failure, not " + tried );
- } else if (tried instanceof Try.Failure> failure) {
+ } else if (tried instanceof Failure> failure) {
if (!Objects.equals( exceptionClass, failure.err().getClass() )) {
throw new AssertionError( String.format( "Expected class '%s' Actual: '%s'",
exceptionClass.getClass(),
@@ -120,9 +122,9 @@ private static void assertSuppressed(final int nSuppressed, Try> tried) {
if (nSuppressed < 0) {throw new IllegalArgumentException();}
requireNonNull( tried );
- if (tried instanceof Try.Success>) {
+ if (tried instanceof Success>) {
throw new AssertionError( "Expected Failure, not " + tried );
- } else if (tried instanceof Try.Failure> failure) {
+ } else if (tried instanceof Failure> failure) {
if (nSuppressed != failure.err().getSuppressed().length) {
throw new AssertionError( String.format( "Expected %d suppressed exceptions; actual count: %d: %s",
nSuppressed,
@@ -136,7 +138,7 @@ private static void assertSuppressed(final int nSuppressed, Try> tried) {
}
private static void assertSuccessfulTry(String expectedValue, Try in) {
- if(in instanceof Try.Success success) {
+ if (in instanceof Success success) {
assertEquals( expectedValue, success.value() );
} else {
throw new AssertionError();
@@ -149,15 +151,15 @@ void ofSupplier() {
// test simple suppliers
{
// success
- final Try stn = Try.of( STRING_SUPPLIER_OK::get );
- assertEquals( Try.Success.class, stn.getClass() );
+ final Try stn = Try.wrap( STRING_SUPPLIER_OK::get );
+ assertEquals( Success.class, stn.getClass() );
assertTrue( stn.contains( SUPPLIED_STRING ) );
}
{
// fail
- final Try stn = Try.of( STRING_SUPPLIER_FAIL::get );
- assertEquals( Try.Failure.class, stn.getClass() );
+ final Try stn = Try.wrap( STRING_SUPPLIER_FAIL::get );
+ assertEquals( Failure.class, stn.getClass() );
assertFalse( stn.contains( SUPPLIED_STRING ) );
assertMessage( RE.getMessage(), stn );
assertSuppressed( 0, stn );
@@ -166,15 +168,15 @@ void ofSupplier() {
// checked suppliers
{
// success
- final Try stn = Try.of( EX_STRING_SUPPLIER_OK );
- assertEquals( Try.Success.class, stn.getClass() );
+ final Try stn = Try.wrap( EX_STRING_SUPPLIER_OK );
+ assertEquals( Success.class, stn.getClass() );
assertTrue( stn.contains( SUPPLIED_STRING ) );
}
{
// fail
- final Try stn = Try.of( EX_STRING_SUPPLIER_FAIL );
- assertEquals( Try.Failure.class, stn.getClass() );
+ final Try stn = Try.wrap( EX_STRING_SUPPLIER_FAIL );
+ assertEquals( Failure.class, stn.getClass() );
assertFalse( stn.contains( SUPPLIED_STRING ) );
assertMessage( IOE.getMessage(), stn );
assertSuppressed( 0, stn );
@@ -182,8 +184,8 @@ void ofSupplier() {
{
// fatal exceptions should be fatal
- assertThrows( LinkageError.class, () -> Try.of( FATAL_SUPPLIER_ERROR ) );
- assertThrows( InterruptedException.class, () -> Try.of( FATAL_SUPPLIER_EX ) );
+ assertThrows( LinkageError.class, () -> Try.wrap( FATAL_SUPPLIER_ERROR ) );
+ assertThrows( InterruptedException.class, () -> Try.wrap( FATAL_SUPPLIER_EX ) );
}
}
@@ -198,7 +200,7 @@ void withResources1() {
return x.toString();
} );
- assertSuccessfulTry("HelloWorld", helloTry);
+ assertSuccessfulTry( "HelloWorld", helloTry );
}
// supplier fail
@@ -261,7 +263,7 @@ void withResources2() {
return s1.toString() + s2;
} );
- assertSuccessfulTry("HelloWorld", helloTry);
+ assertSuccessfulTry( "HelloWorld", helloTry );
}
// function fail + both FAILWRITERs fail at close (suppressed)
@@ -284,14 +286,14 @@ void withResources2() {
@Test
void isFailure() {
- assertFalse( Try.Success.of( SUPPLIED_STRING ).isFailure() );
- assertTrue( Try.Failure.of( IOE ).isFailure() );
+ assertFalse( Success.of( SUPPLIED_STRING ).isFailure() );
+ assertTrue( Failure.of( IOE ).isFailure() );
}
@Test
void isSuccess() {
- assertTrue( Try.Success.of( SUPPLIED_STRING ).isSuccess() );
- assertFalse( Try.Failure.of( IOE ).isSuccess() );
+ assertTrue( Success.of( SUPPLIED_STRING ).isSuccess() );
+ assertFalse( Failure.of( IOE ).isSuccess() );
}
@@ -580,9 +582,9 @@ void ifPredicate() {
@Test
void contains() {
- assertTrue( Try.Success.of( SUPPLIED_STRING ).contains( "SuppliedString" ) );
- assertFalse( Try.Success.of( SUPPLIED_STRING ).contains( "randomstring#*@(#$" ) );
- assertFalse( Try.Failure.of( IOE ).contains( "SuppliedString" ) );
+ assertTrue( Success.of( SUPPLIED_STRING ).contains( "SuppliedString" ) );
+ assertFalse( Success.of( SUPPLIED_STRING ).contains( "randomstring#*@(#$" ) );
+ assertFalse( Failure.of( IOE ).contains( "SuppliedString" ) );
// null : allowed but always false.
assertFalse( TRY_SUCCESS.contains( null ) );
assertFalse( TRY_FAILURE.contains( null ) );
@@ -777,26 +779,47 @@ void getOrThrow() {
@Test
- void randomstuff() {
- int div = 1;
- final Try integerTry = Try.of(
- () -> {
- int i = 318794;
- i = i / Random.from( RandomGenerator.getDefault() ).nextInt();
- i = i / div;
- return i;
+ void wrap() {
+ // example:
+ Try.wrap( () -> {
+ int i = Random.from( RandomGenerator.getDefault() ).nextInt();
+ if (i < 0) {
+ throw new IOException( "oh no!" );
+ } else {
+ // ... do something
+ }
}
);
- final Try asdf = Try.of(
- () -> {
- int i = 318794;
- i = i / Random.from( RandomGenerator.getDefault() ).nextInt();
- i = i / div;
- return Empty.getInstance();
- }
+ // testing
+ assertThrows( NullPointerException.class, () -> Try.wrap( (ExRunnable) null ) );
+
+ assertEquals(
+ Try.ofFailure( IOE ),
+ Try.wrap( () -> { throw IOE; })
+ );
+
+ assertEquals(
+ Success.of(),
+ Try.wrap( () -> { for(int i=0; i<10; i++) { int j = 10+i; }})
);
- // so above would be an int or if /0 then a failure
}
+
+
+ @Test
+ void highlyContrivedAndNotVeryGoodExamples() {
+ final Try result = Try.ofSuccess( 777 )
+ .map( i -> i * 1000 )
+ .exec( System.out::println ) // prints "777000"
+ .map( i -> i / 0 ) // the ArithmeticException is caught as a Try.Failure
+ .exec( System.out::println );// does not exec() because we are a Failure
+
+ // prints "ERROR: java.lang.ArithmeticException: / by zero"
+ switch(result) {
+ case Success(Integer i) -> System.out.printf("Operation completed successfully. Value: %d\n", i);
+ case Failure(Throwable t) -> System.err.printf("ERROR: %s\n", t);
+ }
+ }
+
}
\ No newline at end of file