diff --git a/build.gradle.kts b/build.gradle.kts index f8e39aa..789becc 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -21,7 +21,7 @@ dependencies { testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.9.3") } -java { +java { toolchain { languageVersion.set(JavaLanguageVersion.of(21)) vendor.set(JvmVendorSpec.ADOPTIUM) @@ -37,10 +37,21 @@ tasks.getByName("test") { } + +tasks.withType().configureEach { + options.compilerArgs.add("-Xlint:preview") +} + + tasks.withType().configureEach { useJUnitPlatform() } +tasks.withType().configureEach { +} + + + publishing { publications { create("mavenJava") { diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index 7cbcc2e..3d10d33 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -1,3 +1,14 @@ +/** + * Module declaration for the net.xyzsd.dichotomy module. + * This module exports three packages: + * - net.xyzsd.dichotomy.trying.function + * - net.xyzsd.dichotomy + * - net.xyzsd.dichotomy.trying + * + * This module requires the org.jetbrains.annotations module in a static manner. + * + * + */ module net.xyzsd.dichotomy { requires static org.jetbrains.annotations; diff --git a/src/main/java/net/xyzsd/dichotomy/Either.java b/src/main/java/net/xyzsd/dichotomy/Either.java index acfc9ed..62c7df2 100644 --- a/src/main/java/net/xyzsd/dichotomy/Either.java +++ b/src/main/java/net/xyzsd/dichotomy/Either.java @@ -21,20 +21,28 @@ * {@code rather than an Either.} * *

- * (todo: better explanation and examples) + * Features: + *

    + *
  • {@link Left} and {@link Right} values cannot hold {@code null}. The {@link Empty} type could be used instead.
  • + *
  • Right-biased; methods not ending in {@code Left} operate on {@link Right} values. + * However, the {@code xxxLeft} methods are provided so that a {@link #swap}() is not required.
  • + *
  • Sealed; can be used in {@code switch} statements and deconstruction patterns.
  • + *
  • Separable; this file only depends on standard JDK classes.
  • + *
+ *

+ * For example: + * {@snippet : + * //** TODO ** include a succinct example + *} * *

- * (todo: explanation of right-bias) + * NOTE: For methods which accept or return type parameters {@code } or {@code }, + * the {@code } or {@code } parameters can be completely different types from {@code } or {@code }. * * @param The left-hand value (by convention, this is the failure/unsuccessful value) * @param The right-hand value (by convention, the success value) */ -// opinionated : no null for L or R; cannot use null (Void types not allowed; use 'None' instead) -// sealed: can use in switch() -// biased: right (typically error value in left) HOWEVER we do have xxxErr methods to operate on the left side -// separable: everythign is defined in a single file. - -public sealed interface Either permits Either.Left, Either.Right { +public sealed interface Either { /** * Create a Left {@link Either}. @@ -63,27 +71,13 @@ static Either ofRight(@NotNull R value) { * If the {@link Either} is {@link Left}, return the value as an {@link Optional}. * Otherwise, return an empty {@link Optional}. */ - @NotNull - default Optional left() { - return switch (this) { - case Left(L l) -> Optional.of( l ); - case Right(var __) -> Optional.empty(); - }; - } + @NotNull Optional left(); /** * If the {@link Either} is {@link Right}, return the value as an {@link Optional}. * Otherwise, return an empty {@link Optional}. */ - @NotNull - default Optional right() { - return switch (this) { - case Right(R r) -> Optional.of( r ); - case Left(var __) -> Optional.empty(); - }; - } - - + @NotNull Optional right(); /** @@ -95,19 +89,7 @@ default Optional right() { * @see #match(Consumer) * @see #matchLeft(Consumer) */ - @NotNull - default Either biMatch(@NotNull Consumer leftConsumer, - @NotNull Consumer rightConsumer) { - requireNonNull( leftConsumer ); - requireNonNull( rightConsumer ); - - switch (this) { - case Left(L l) -> leftConsumer.accept( l ); - case Right(R r) -> rightConsumer.accept( r ); - } - - return this; - } + @NotNull Either biMatch(@NotNull Consumer leftConsumer, @NotNull Consumer rightConsumer); /** @@ -123,17 +105,7 @@ default Either biMatch(@NotNull Consumer leftConsumer, * @see #map(Function) * @see #mapLeft(Function) */ - @NotNull - default Either biMap(@NotNull Function fnLeft, - @NotNull Function fnRight) { - requireNonNull( fnLeft ); - requireNonNull( fnRight ); - - return switch (this) { - case Left(L l) -> ofLeft( fnLeft.apply( l ) ); - case Right(R r) -> ofRight( fnRight.apply( r ) ); - }; - } + @NotNull Either biMap(@NotNull Function fnLeft, @NotNull Function fnRight); /** @@ -151,18 +123,7 @@ default Either biMap(@NotNull Function * @see #map(Function) * @see #mapLeft(Function) */ - @NotNull - @SuppressWarnings("unchecked") - default Either biFlatMap(@NotNull Function> fnLeft, - @NotNull Function> fnRight) { - requireNonNull( fnLeft ); - requireNonNull( fnRight ); - - return switch (this) { - case Left(L l) -> (Either) requireNonNull( fnLeft.apply( l ) ); - case Right(R r) -> (Either) requireNonNull( fnRight.apply( r ) ); - }; - } + @NotNull Either biFlatMap(@NotNull Function> fnLeft, @NotNull Function> fnRight); /** @@ -183,18 +144,7 @@ default Either biFlatMap(@NotNull Function T fold(@NotNull Function fnLeft, - @NotNull Function fnRight) { - requireNonNull( fnLeft ); - requireNonNull( fnRight ); - - return switch (this) { - case Left(L l) -> requireNonNull( fnLeft.apply( l ) ); - case Right(R r) -> requireNonNull( fnRight.apply( r ) ); - - }; - } + @NotNull T fold(@NotNull Function fnLeft, @NotNull Function fnRight); /** @@ -214,19 +164,7 @@ default T fold(@NotNull Function fnLeft, * @return an {@link Either} based on the algorithm described above. * @throws NullPointerException if the called mapping function returns {@code null}. */ - @NotNull - default Either filter(@NotNull Predicate predicate, - @NotNull Function mapper) { - requireNonNull( predicate ); - requireNonNull( mapper ); - - return switch (this) { - case Left(L l) -> this; - case Right(R r) -> predicate.test( r ) - ? this - : Either.ofLeft( mapper.apply( r ) ); // implicit null check - }; - } + @NotNull Either filter(@NotNull Predicate predicate, @NotNull Function mapper); /** @@ -243,10 +181,7 @@ case Right(R r) -> predicate.test( r ) * @throws NullPointerException if the result of the mapping function is {@code null}. * @see #recover(Function) */ - @NotNull - default L forfeit(@NotNull Function fn) { - return fold( Function.identity(), fn ); - } + @NotNull L forfeit(@NotNull Function fn); /** @@ -264,10 +199,7 @@ default L forfeit(@NotNull Function fn) { * @throws NullPointerException if the result of the mapping function is {@code null}. * @see #forfeit(Function) */ - @NotNull - default R recover(@NotNull Function fn) { - return fold( fn, Function.identity() ); - } + @NotNull R recover(@NotNull Function fn); /** @@ -277,13 +209,7 @@ default R recover(@NotNull Function fn) { * @return Stream * @see #streamLeft() */ - @NotNull - default Stream stream() { - return switch (this) { - case Left(L l) -> Stream.empty(); - case Right(R r) -> Stream.of( r ); - }; - } + @NotNull Stream stream(); /** * Return a {@link Stream}, containing either a single {@link Left} value, or an empty {@link Stream} @@ -291,13 +217,7 @@ default Stream stream() { * * @see #stream() */ - @NotNull - default Stream streamLeft() { - return switch (this) { - case Left(L l) -> Stream.of( l ); - case Right(R r) -> Stream.empty(); - }; - } + @NotNull Stream streamLeft(); /** * Determines if this {@link Right} {@link Either} contains the given value. @@ -307,16 +227,11 @@ default Stream streamLeft() { * * @param rVal value to compare * @return {@code true} iff this is a {@link Right} {@link Either} and the contained value equals {@code rVal} - * @see #matches + * @see #ifPredicate * @see #containsLeft - * @see #matchesLeft + * @see #ifPredicateLeft */ - default boolean contains(@Nullable R rVal) { - return switch (this) { - case Left(L l) -> false; - case Right(R r) -> r.equals( rVal ); - }; - } + boolean contains(@Nullable R rVal); /** * Determines if this {@link Left} {@link Either} contains the given value. @@ -326,16 +241,11 @@ default boolean contains(@Nullable R rVal) { * * @param lVal value to compare * @return {@code true} iff this is a {@link Left} {@link Either} and the contained value equals {@code lVal} - * @see #matchesLeft - * @see #matches + * @see #ifPredicateLeft + * @see #ifPredicate * @see #contains */ - default boolean containsLeft(@Nullable L lVal) { - return switch (this) { - case Left(L l) -> l.equals( lVal ); - case Right(R r) -> false; - }; - } + boolean containsLeft(@Nullable L lVal); /** * Determines if this {@link Right} {@link Either} matches the given {@link Predicate}. @@ -346,17 +256,10 @@ default boolean containsLeft(@Nullable L lVal) { * @param rp the {@link Predicate} to test * @return {@code true} iff this is a {@link Right} {@link Either} and the {@link Predicate} matches. * @see #contains - * @see #matchesLeft + * @see #ifPredicateLeft * @see #containsLeft */ - default boolean matches(@NotNull Predicate rp) { - requireNonNull( rp ); - - return switch (this) { - case Left(L l) -> false; - case Right(R r) -> rp.test( r ); - }; - } + boolean ifPredicate(@NotNull Predicate rp); /** * Determines if this {@link Left} {@link Either} matches the given {@link Predicate}. @@ -368,16 +271,9 @@ default boolean matches(@NotNull Predicate rp) { * @return {@code true} iff this is a {@link Left} {@link Either} and the {@link Predicate} matches. * @see #containsLeft * @see #contains - * @see #matches + * @see #ifPredicate */ - default boolean matchesLeft(@NotNull Predicate lp) { - requireNonNull( lp ); - - return switch (this) { - case Left(L l) -> lp.test( l ); - case Right(R r) -> false; - }; - } + boolean ifPredicateLeft(@NotNull Predicate lp); /** * If this is a {@link Right}, return a new {@link Right} value produced by the given mapping function. @@ -397,10 +293,7 @@ default boolean matchesLeft(@NotNull Predicate lp) { * @see #mapLeft(Function) * @see #biMap(Function, Function) */ - @NotNull - default Either map(@NotNull Function rightMapper) { - return biMap( Function.identity(), rightMapper ); - } + @NotNull Either map(@NotNull Function rightMapper); /** * If this is a {@link Right}, return the new {@link Either} supplied by the mapping function. @@ -420,17 +313,7 @@ default Either map(@NotNull Function rightM * @see #biFlatMap(Function, Function) * @see #flatMapLeft(Function) */ - @SuppressWarnings("unchecked") - @NotNull - default Either flatMap(@NotNull Function> rightMapper) { - requireNonNull( rightMapper ); - - return switch (this) { - case Left(L l) -> (Left) this; // coerce right - case Right(R r) -> (Either) requireNonNull( rightMapper.apply( r ) ); - }; - } + @NotNull Either flatMap(@NotNull Function> rightMapper); /** @@ -451,10 +334,8 @@ default Either flatMap(@NotNull Function Either mapLeft(@NotNull Function leftMapper) { - return biMap( leftMapper, Function.identity() ); - } + @NotNull Either mapLeft(@NotNull Function leftMapper); + /** * If this is a {@link Left}, return the new {@link Either} supplied by the mapping function. @@ -474,45 +355,42 @@ default Either mapLeft(@NotNull Function le * @see #biFlatMap(Function, Function) * @see #flatMap(Function) */ - @SuppressWarnings("unchecked") - @NotNull - default Either flatMapLeft(@NotNull Function> leftMapper) { - requireNonNull( leftMapper ); - - return switch (this) { - case Left(L l) -> (Either) requireNonNull( leftMapper.apply( l ) ); - case Right(R r) -> (Right) this; // coerce left - }; - } + @NotNull Either flatMapLeft(@NotNull Function> leftMapper); /** * Executes the action iff this is a {@link Left} {@link Either}. * * @return {@code this} + * @param leftConsumer the consumer function to be executed * @throws NullPointerException if the called action returns {@code null}. * @see #match(Consumer) * @see #biMatch(Consumer, Consumer) */ - @NotNull - default Either matchLeft(@NotNull Consumer leftConsumer) { - return biMatch( leftConsumer, (x) -> {} ); - } + @NotNull Either matchLeft(@NotNull Consumer leftConsumer); /** * Executes the action iff this is a {@link Right} {@link Either}. * * @return {@code this} + * @param rightConsumer the consumer function to be executed * @throws NullPointerException if the called action returns {@code null}. * @see #match(Consumer) * @see #biMatch(Consumer, Consumer) */ - @NotNull - default Either match(@NotNull Consumer rightConsumer) { - return biMatch( (x) -> {}, rightConsumer ); + @NotNull Either match(@NotNull Consumer rightConsumer); + + + /** + * Executes the given consumer if this is a {@link Right}. This is a terminal operation. + * + * @param rightConsumer the consumer function to be executed + */ + default void consume(@NotNull Consumer rightConsumer) { + match(rightConsumer); } + /** * If this {@link Either} is {@link Left}, return {@code rightAlternate}. * Otherwise, return {@code this} (a {@link Right} {@link Either}). @@ -523,15 +401,7 @@ default Either match(@NotNull Consumer rightConsumer) { * @see #orElseLeft * @see #orElseGetLeft */ - @NotNull - default R orElse(@NotNull R rightAlternate) { - requireNonNull( rightAlternate ); - - return switch (this) { - case Left(L l) -> rightAlternate; - case Right(R r) -> r; - }; - } + @NotNull R orElse(@NotNull R rightAlternate); /** * If this {@link Either} is {@link Right}, return {@code leftAlternate}. @@ -543,14 +413,7 @@ default R orElse(@NotNull R rightAlternate) { * @see #orElse * @see #orElseGet */ - @NotNull - default L orElseLeft(@NotNull L leftAlternate) { - requireNonNull( leftAlternate ); - return switch (this) { - case Left(L l) -> l; - case Right(R r) -> leftAlternate; - }; - } + @NotNull L orElseLeft(@NotNull L leftAlternate); /** * If this {@link Either} is {@link Left}, return the supplied {@link Right} {@link Either}. @@ -563,15 +426,7 @@ default L orElseLeft(@NotNull L leftAlternate) { * @see #orElseLeft * @see #orElseGetLeft */ - @NotNull - default R orElseGet(@NotNull Supplier rightSupplier) { - requireNonNull( rightSupplier ); - - return switch (this) { - case Left(L l) -> rightSupplier.get(); - case Right(R r) -> r; - }; - } + @NotNull R orElseGet(@NotNull Supplier rightSupplier); /** * If this {@link Either} is {@link Right}, return the supplied {@link Left} {@link Either}. @@ -584,15 +439,7 @@ default R orElseGet(@NotNull Supplier rightSupplier) { * @see #orElse * @see #orElseGet */ - @NotNull - default L orElseGetLeft(@NotNull Supplier leftSupplier) { - requireNonNull( leftSupplier ); - - return switch (this) { - case Left(L l) -> l; - case Right(R r) -> leftSupplier.get(); - }; - } + @NotNull L orElseGetLeft(@NotNull Supplier leftSupplier); /** @@ -604,15 +451,7 @@ default L orElseGetLeft(@NotNull Supplier leftSupplier) { * @see #or(Either) * @see #or(Supplier) */ - @NotNull - default Either and(@NotNull Either nextEither) { - requireNonNull( nextEither ); - - return switch (this) { - case Left(L l) -> ((Left) this).coerce(); - case Right(R r) -> nextEither; - }; - } + @NotNull Either and(@NotNull Either nextEither); /** * If {@code this} is {@link Left}, return it (without invoking the {@link Supplier}). @@ -626,15 +465,7 @@ default Either and(@NotNull Either nextEither) { * @see #or(Either) * @see #or(Supplier) */ - @NotNull - default Either and(@NotNull Supplier> nextEitherSupplier) { - requireNonNull( nextEitherSupplier ); - - return switch (this) { - case Left(L l) -> ((Left) this).coerce(); - case Right(R r) -> requireNonNull( nextEitherSupplier.get() ); - }; - } + @NotNull Either and(@NotNull Supplier> nextEitherSupplier); /** @@ -648,15 +479,7 @@ default Either and(@NotNull Supplier> nextEitherSuppli * @see #and(Either) * @see #and(Supplier) */ - @NotNull - default Either or(@NotNull Either nextEither) { - requireNonNull( nextEither ); - - return switch (this) { - case Left(L l) -> nextEither; - case Right(R r) -> ((Right) this).coerce(); - }; - } + @NotNull Either or(@NotNull Either nextEither); /** @@ -671,15 +494,7 @@ default Either or(@NotNull Either nextEither) { * @see #and(Either) * @see #and(Supplier) */ - @NotNull - default Either or(@NotNull Supplier> nextEitherSupplier) { - requireNonNull( nextEitherSupplier ); - - return switch (this) { - case Left(L l) -> requireNonNull( nextEitherSupplier.get() ); - case Right(R r) -> ((Right) this).coerce(); - }; - } + @NotNull Either or(@NotNull Supplier> nextEitherSupplier); /** @@ -696,15 +511,7 @@ default Either or(@NotNull Supplier> nextEitherSupplie * @throws X the supplied {@link Exception} * @throws NullPointerException if the called Supplier returns {@code null}. */ - @NotNull - default R getOrThrow(@NotNull Supplier supplier) throws X { - requireNonNull( supplier ); - - return switch (this) { - case Left(L l) -> throw supplier.get(); - case Right(R r) -> r; - }; - } + @NotNull R expect(@NotNull Supplier supplier) throws X; /** @@ -754,15 +561,7 @@ default R getOrThrow(@NotNull Supplier supplier) throws * @throws X the produced {@link Exception} * @throws NullPointerException if the called Function returns {@code null}. */ - @NotNull - default R getOrThrowWrapped(@NotNull Function exFn) throws X { - requireNonNull( exFn ); - - return switch (this) { - case Left(L l) -> throw exFn.apply( l ); - case Right(R r) -> r; - }; - } + @NotNull R getOrThrow(@NotNull Function exFn) throws X; /** @@ -774,27 +573,21 @@ default R getOrThrowWrapped(@NotNull Function exFn) * For example, the following are equivalent: *

* {@snippet : - * myEither.flatMapLeft(MyFunction::doit) == myEither.swap().flatMap(MyFunction::doit); + * myEither.flatMapLeft(MyFunction::doIt) == myEither.swap().flatMap(MyFunction::doIt); *} * * @return A new {@link Either} with left and right values swapped. */ - default - @NotNull Either swap() { - // optimized; could use biFlatMap( Either::ofRight, Either::ofLeft ); - return switch (this) { - case Left(L l) -> Either.ofRight( l ); - case Right(R r) -> Either.ofLeft( r ); - }; - } + + @NotNull Either swap(); /** * Implementation of a Left {@link Either}. * * @param value Left value. May not be {@code null}. - * @param parameter type of Left values. - * @param parameter type of Right values. + * @param parameter type of Left values. + * @param parameter type of Right values. */ record Left(@NotNull L value) implements Either { @@ -811,19 +604,317 @@ record Left(@NotNull L value) implements Either { * Get the value. Never null. */ @NotNull - public L get() { return value; } + public L get() {return value;} + + + /** + * {@inheritDoc} + */ + @Override + public @NotNull Optional left() { + return Optional.of( value ); + } + + /** + * {@inheritDoc} + */ + @Override + public @NotNull Optional right() { + return Optional.empty(); + } + + + /** + * {@inheritDoc} + */ + @Override + public @NotNull Either biMatch(@NotNull Consumer leftConsumer, @NotNull Consumer rightConsumer) { + requireNonNull( leftConsumer ); + requireNonNull( rightConsumer ); + leftConsumer.accept( value ); + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public @NotNull Either biMap(@NotNull Function fnLeft, @NotNull Function fnRight) { + requireNonNull( fnLeft ); + requireNonNull( fnRight ); + return Either.ofLeft( fnLeft.apply( value ) ); + } + + /** + * {@inheritDoc} + */ + @Override + @SuppressWarnings("unchecked") + public @NotNull Either biFlatMap(@NotNull Function> fnLeft, @NotNull Function> fnRight) { + requireNonNull( fnLeft ); + requireNonNull( fnRight ); + return (Either) requireNonNull( fnLeft.apply( value ) ); + } + + /** + * {@inheritDoc} + */ + @Override + public @NotNull T fold(@NotNull Function fnLeft, @NotNull Function fnRight) { + requireNonNull( fnLeft ); + requireNonNull( fnRight ); + return requireNonNull( fnLeft.apply( value ) ); + } + + + /** + * {@inheritDoc} + */ + @Override + public @NotNull Either filter(@NotNull Predicate predicate, @NotNull Function mapper) { + requireNonNull( predicate ); + requireNonNull( mapper ); + return this; + } + + + /** + * {@inheritDoc} + */ + @Override + public @NotNull Stream stream() { + return Stream.empty(); + } + + /** + * {@inheritDoc} + */ + @Override + public @NotNull Stream streamLeft() { + return Stream.of( value ); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean contains(@Nullable R rVal) { + return false; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean containsLeft(@Nullable L lVal) { + return value.equals( lVal ); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean ifPredicate(@NotNull Predicate rp) { + return false; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean ifPredicateLeft(@NotNull Predicate lp) { + return requireNonNull( lp ).test( value ); + } + + + /** + * {@inheritDoc} + */ + @SuppressWarnings("unchecked") + @Override + public @NotNull Either flatMapLeft(@NotNull Function> leftMapper) { + requireNonNull( leftMapper ); + return (Either) requireNonNull( leftMapper.apply( value ) ); + } + + /** + * {@inheritDoc} + */ + @Override + public @NotNull Either flatMap(@NotNull Function> rightMapper) { + requireNonNull( rightMapper ); + return coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public @NotNull R orElse(@NotNull R rightAlternate) { + requireNonNull( rightAlternate ); + return rightAlternate; + } + + /** + * {@inheritDoc} + */ + @Override + public @NotNull L orElseLeft(@NotNull L leftAlternate) { + requireNonNull( leftAlternate ); + return value; + } + + /** + * {@inheritDoc} + */ + @Override + public @NotNull R orElseGet(@NotNull Supplier rightSupplier) { + requireNonNull( rightSupplier ); + return requireNonNull( rightSupplier.get() ); + } + + /** + * {@inheritDoc} + */ + @Override + public @NotNull L orElseGetLeft(@NotNull Supplier leftSupplier) { + requireNonNull( leftSupplier ); + return value; + } + + /** + * {@inheritDoc} + */ + @Override + public @NotNull L forfeit(@NotNull Function fn) { + requireNonNull( fn ); + return value; + } + + /** + * {@inheritDoc} + */ + @Override + public @NotNull R recover(@NotNull Function fn) { + requireNonNull( fn ); + return requireNonNull( fn.apply( value ) ); + } + + /** + * {@inheritDoc} + */ + @Override + public @NotNull Either map(@NotNull Function rightMapper) { + requireNonNull( rightMapper ); + return coerce(); + } + + /** + * {@inheritDoc} + */ + @SuppressWarnings("unchecked") + @Override + public @NotNull Either mapLeft(@NotNull Function leftMapper) { + requireNonNull( leftMapper ); + return (Either) Either.ofLeft( leftMapper.apply( value ) ); + } + + /** + * {@inheritDoc} + */ + @Override + public @NotNull Either matchLeft(@NotNull Consumer leftConsumer) { + requireNonNull( leftConsumer ); + leftConsumer.accept( value ); + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public @NotNull Either match(@NotNull Consumer rightConsumer) { + requireNonNull( rightConsumer ); + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public @NotNull Either or(@NotNull Supplier> nextEitherSupplier) { + requireNonNull( nextEitherSupplier ); + return requireNonNull( nextEitherSupplier.get() ); + } + + /** + * {@inheritDoc} + */ + @Override + public @NotNull Either swap() { + return Either.ofRight( value ); + } + + /** + * {@inheritDoc} + */ + @Override + public @NotNull Either and(@NotNull Either nextEither) { + requireNonNull( nextEither ); + return coerce(); + } + + /** + * {@inheritDoc} + */ + // more efficient than implementation : does not invoke supplier + @Override + public @NotNull Either and(@NotNull Supplier> nextEitherSupplier) { + requireNonNull( nextEitherSupplier ); + return coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public @NotNull Either or(@NotNull Either nextEither) { + requireNonNull( nextEither ); + return nextEither; + } + + /** + * {@inheritDoc} + */ + @Override + public @NotNull R expect(@NotNull Supplier supplier) throws X { + requireNonNull( supplier ); + throw requireNonNull( supplier.get() ); + } + + + /** + * {@inheritDoc} + */ + @Override + public @NotNull R getOrThrow(@NotNull Function exFn) throws X { + requireNonNull( exFn ); + throw requireNonNull( exFn.apply( value ) ); + } + /** * Coerce the empty Right parameter to the new type. *

- * Does not create a new object. + * Does not create a new object. *

* - * @return coerced Left * @param new Right parameter - */ @SuppressWarnings("unchecked") + * @return coerced Left + */ + @SuppressWarnings("unchecked") @NotNull - public Either coerce() { + private Either coerce() { return (Either) this; } } @@ -849,21 +940,321 @@ record Right(@NotNull R value) implements Either { * Get the value. Never null. */ @NotNull - public R get() { return value; } + public R get() {return value;} + + + /** + * {@inheritDoc} + */ + @Override + public @NotNull Optional left() { + return Optional.empty(); + } + + /** + * {@inheritDoc} + */ + @Override + public @NotNull Optional right() { + return Optional.of( value ); + } + + + /** + * {@inheritDoc} + */ + @Override + public @NotNull Either biMatch(@NotNull Consumer leftConsumer, @NotNull Consumer rightConsumer) { + requireNonNull( leftConsumer ); + requireNonNull( rightConsumer ); + rightConsumer.accept( value ); + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public @NotNull Either biMap(@NotNull Function fnLeft, @NotNull Function fnRight) { + requireNonNull( fnLeft ); + requireNonNull( fnRight ); + return Either.ofRight( fnRight.apply( value ) ); + } + + /** + * {@inheritDoc} + */ + @Override + @SuppressWarnings("unchecked") + public @NotNull Either biFlatMap(@NotNull Function> fnLeft, @NotNull Function> fnRight) { + requireNonNull( fnLeft ); + requireNonNull( fnRight ); + return (Either) requireNonNull( fnRight.apply( value ) ); + } + + /** + * {@inheritDoc} + */ + @Override + public @NotNull T fold(@NotNull Function fnLeft, @NotNull Function fnRight) { + requireNonNull( fnLeft ); + requireNonNull( fnRight ); + return requireNonNull( fnRight.apply( value ) ); + } + + /** + * {@inheritDoc} + */ + @Override + public @NotNull Either filter(@NotNull Predicate predicate, @NotNull Function mapper) { + requireNonNull( predicate ); + requireNonNull( mapper ); + return predicate.test( value ) ? this : Either.ofLeft( mapper.apply( value ) ); // implicit null check + } + + + /** + * {@inheritDoc} + */ + @Override + public @NotNull Stream stream() { + return Stream.of( value ); + } + + /** + * {@inheritDoc} + */ + @Override + public @NotNull Stream streamLeft() { + return Stream.empty(); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean contains(@Nullable R rVal) { + return value.equals( rVal ); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean containsLeft(@Nullable L lVal) { + return false; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean ifPredicate(@NotNull Predicate rp) { + return requireNonNull( rp ).test( value ); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean ifPredicateLeft(@NotNull Predicate lp) { + requireNonNull( lp ); + return false; + } + + + /** + * {@inheritDoc} + */ + @Override + public @NotNull Either flatMapLeft(@NotNull Function> leftMapper) { + requireNonNull( leftMapper ); + return coerce(); + } + + @SuppressWarnings("unchecked") + /** + * {@inheritDoc} + */ + @Override + public @NotNull Either flatMap(@NotNull Function> rightMapper) { + requireNonNull( rightMapper ); + return (Either) requireNonNull( rightMapper.apply( value ) ); + } + + /** + * {@inheritDoc} + */ + @Override + public @NotNull R orElse(@NotNull R rightAlternate) { + requireNonNull( rightAlternate ); + return value; + } + + /** + * {@inheritDoc} + */ + @Override + public @NotNull L orElseLeft(@NotNull L leftAlternate) { + requireNonNull( leftAlternate ); + return leftAlternate; + } + + /** + * {@inheritDoc} + */ + @Override + public @NotNull R orElseGet(@NotNull Supplier rightSupplier) { + requireNonNull( rightSupplier ); + return value; + } + + /** + * {@inheritDoc} + */ + @Override + public @NotNull L orElseGetLeft(@NotNull Supplier leftSupplier) { + requireNonNull( leftSupplier ); + return requireNonNull( leftSupplier.get() ); + } + + /** + * {@inheritDoc} + */ + @Override + public @NotNull Either and(@NotNull Either nextEither) { + requireNonNull( nextEither ); + return nextEither; + } + + + /** + * {@inheritDoc} + */ + @Override + public @NotNull Either or(@NotNull Either nextEither) { + requireNonNull( nextEither ); + return coerce(); + } + + // more efficient than implementation : does not invoke supplier + + /** + * {@inheritDoc} + */ + @Override + public @NotNull Either or(@NotNull Supplier> nextEitherSupplier) { + requireNonNull( nextEitherSupplier ); + return coerce(); + } + + + /** + * {@inheritDoc} + */ + @Override + public @NotNull L forfeit(@NotNull Function fn) { + requireNonNull( fn ); + return requireNonNull( fn.apply( value ) ); + } + + /** + * {@inheritDoc} + */ + @Override + public @NotNull R recover(@NotNull Function fn) { + requireNonNull( fn ); + return value; + } + + /** + * {@inheritDoc} + */ + @Override + public @NotNull Either map(@NotNull Function rightMapper) { + requireNonNull( rightMapper ); + return Either.ofRight( rightMapper.apply( value ) ); + } + + /** + * {@inheritDoc} + */ + @Override + public @NotNull Either mapLeft(@NotNull Function leftMapper) { + requireNonNull( leftMapper ); + return coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public @NotNull Either matchLeft(@NotNull Consumer leftConsumer) { + requireNonNull( leftConsumer ); + // do nothing + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public @NotNull Either match(@NotNull Consumer rightConsumer) { + requireNonNull( rightConsumer ); + rightConsumer.accept( value ); + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public @NotNull Either and(@NotNull Supplier> nextEitherSupplier) { + requireNonNull( nextEitherSupplier ); + return requireNonNull( nextEitherSupplier.get() ); + } + + /** + * {@inheritDoc} + */ + @Override + public @NotNull Either swap() { + return ofLeft( value ); + } + + /** + * {@inheritDoc} + */ + @Override + @NotNull + public R expect(@NotNull Supplier supplier) throws X { + // supplier not invoked + requireNonNull( supplier ); + return value; + } + /** + * {@inheritDoc} + */ + @Override + public @NotNull R getOrThrow(@NotNull Function exFn) { + // function not invoked + requireNonNull( exFn ); + return value; + } /** * Coerce the empty Left parameter to the new type. *

- * Does not create a new object. + * Does not create a new object. *

* - * @return coerced Right * @param new Left parameter + * @return coerced Right */ @SuppressWarnings("unchecked") @NotNull - public Either coerce() { + private Either coerce() { return (Either) this; } } diff --git a/src/main/java/net/xyzsd/dichotomy/None.java b/src/main/java/net/xyzsd/dichotomy/Empty.java similarity index 52% rename from src/main/java/net/xyzsd/dichotomy/None.java rename to src/main/java/net/xyzsd/dichotomy/Empty.java index 9fbdec5..1f2e88b 100644 --- a/src/main/java/net/xyzsd/dichotomy/None.java +++ b/src/main/java/net/xyzsd/dichotomy/Empty.java @@ -7,7 +7,8 @@ * as it cannot be instantiated, and extensive null-checking is in place. *

*

- * Instead, the {@link None} type can be used. All {@link None} types are considered equivalent. + * Instead, the {@link Empty} type can be used. All {@link Empty} types are considered equivalent. *

*/ -public record None() {} +public record Empty() {} +// 'Empty' instead of None to differ from Maybe.None. 'Nil' could be a consideration. or maybe all caps to denote singleton ('NONE') diff --git a/src/main/java/net/xyzsd/dichotomy/Maybe.java b/src/main/java/net/xyzsd/dichotomy/Maybe.java index 2cf3f76..27230fe 100644 --- a/src/main/java/net/xyzsd/dichotomy/Maybe.java +++ b/src/main/java/net/xyzsd/dichotomy/Maybe.java @@ -14,186 +14,483 @@ import static java.util.Objects.requireNonNull; - -public sealed interface Maybe { - +/** + * A type which holds a value {@link Some}, or no value {@link None}. + *

+ * This is analogous to the standard {@link java.util.Optional} class, but this is a sealed + * type and can be used in {@code switch} statements and supports pattern-matching. + * + * @param Value type. + */ +public sealed interface Maybe { + + + /** + * Create a Maybe with the given non-null value. + * + * @param value value of this Maybe + * @param value type + * @return Maybe holding given value. + */ static Maybe of(@NotNull T value) { return new Some<>( value ); } + /** + * Create an empty {@link Maybe}. + * + * @param value type + * @return empty Maybe + */ static Maybe ofNone() { - return new None<>(); + return None.empty(); } + /** + * Given a potentially nullable value, create a {@link Maybe} holding the value + * or an empty {@link Maybe} if the value is null. + * + * @param value value of this Maybe + * @param value type + * @return Maybe + */ static Maybe ofNullable(@Nullable T value) { return (value == null) ? Maybe.ofNone() : Maybe.of( value ); } - + /** + * Perform an action depending on whether the {@link Maybe} is a {@link Some} or a {@link None}. + *

+ * This is analagous to {@link java.util.Optional#ifPresentOrElse(Consumer, Runnable)} + * + * @param someConsumer action performed if {@link Some} + * @param noneRunner action performed if {@link None} + * @return this + */ + @NotNull Maybe biMatch(@NotNull Consumer someConsumer, @NotNull Runnable noneRunner); + + /** + * Filter based on the given {@link Predicate}. If the Predicate is false, or this is a {@link None}, + * a {@link None} value is returned. + * + * @param predicate {@link Predicate} to test. + * @return this or {@link None} + */ + @NotNull Maybe filter(@NotNull Predicate predicate); + + /** + * Map a {@link Some} or {@link None} value to a given type. + * + * @param fnSome mapping function for Some values + * @param supNone supplier for None values + * @param return type of both functions + * @return result of one of the above mapping function or supplier. + */ + @NotNull U fold(@NotNull Function fnSome, @NotNull Supplier supNone); + + /** + * Stream the value contained. The stream will consist of at most a single element, and + * will contain no elements if it is a {@link None}. + * + * @return Stream + */ + @NotNull Stream stream(); + + /** + * Map {@link Some} types. No mapping is performed if the type is {@link None}. + * + * @param fnSome mapping function for {@link Some} values + * @param return type of function + * @return the result of the mapping function or a {@link None} + */ + // only maps if present + @NotNull Maybe map(@NotNull Function fnSome); + + /** + * If the {@link Maybe} is a {@link Some}, call the provided {@link Consumer}. Otherwise, do nothing. + * + * @param someConsumer act upon the given {@link Some} value + * @return this + */ + Maybe match(@NotNull Consumer someConsumer); + + /** + * If the {@link Maybe} is a {@link Some}, call the provided {@link Consumer}. Otherwise, do nothing. + *

+ * This is functionally equivalent to {@link #match(Consumer)} but does not return {@code this}, + * clearly marking it as a terminal operation. + * + * @param someConsumer act upon the given {@link Some} value + */ + void consume(@NotNull Consumer someConsumer); + + /** + * If a value is present ({@link Some}), return the result of applying the given mapping function to the value, + * otherwise returns a {@link None}. + *

+ * This does not wrap the returned function value in a new {@link Some} value, unlike {@link #map(Function)}. + * + * @param mapper mapper for {@link Some} values. + * @param type of {@link Maybe} returned. + * @return result of mapping, or {@link None} + */ + @NotNull Maybe flatMap(@NotNull Function> mapper); + + /** + * Terminal operation which tests a {@link Some} value against the given {@link Predicate}. + *

+ * This is functionally equivalent to: + * {@snippet : + * filter(Predicate).hasSome() + * } + * + * @param predicate {@link Predicate} to test + * @return result of predicate testing + */ + boolean matches(@NotNull Predicate predicate); + + /** + * Returns {@code true} if {@code value} equals the value contained in a {@link Some}. + * If {@code value} is {@code null}, returns {@code true} if {@link None}. + *

+ * This is analogous to {@link java.util.Optional#equals(Object)}. + * + * @param value value to evaluate + * @return true if provided value equals the contained value. + */ + boolean contains(@Nullable final T value); + + /** + * True if this is a {@link Some}. + * + * @return {@code true} if this is a {@link Some} type. + */ + boolean hasSome(); + + /** + * If this is a {@link Some}, return it. Otherwise, use the provided alternate value. + * + * @param alternate used if this is {@link None} + * @return value + */ + @NotNull T orElse(@NotNull T alternate); + + /** + * If this is a {@link Some}, return it. Otherwise, use the provided alternate value. + * The Supplier is only invoked if this is a {@link None}. + * + * @param supplier, used if this is {@link None} + * @return value + */ + @NotNull T orElse(@NotNull Supplier supplier); + + /** + * Get the next {@link Maybe}, but only if the current {@link Maybe} is a {@link Some}. + * Otherwise, do nothing. + * + * @param nextMaybe if this is not a {@link None} + * @param type of Maybe returned + */ + @NotNull Maybe and(@NotNull Maybe nextMaybe); + + /** + * Get the next {@link Maybe}, but only if the current {@link Maybe} is a {@link Some}. + * Otherwise, do nothing. + * + * @param nextMaybeSupplier invoked this is not a {@link None} + * @param type of Maybe returned + */ + @NotNull Maybe and(@NotNull Supplier> nextMaybeSupplier); + + /** + * If this is a {@link Some}, return it. Otherwise, return {@code nextMaybe}. + * + * @param nextMaybe (returned if this is a {@link None} + */ + @NotNull Maybe or(@NotNull Maybe nextMaybe); + + /** + * If this is a {@link Some}, return it. Otherwise, return the maybe via the provided {@link Supplier}. + * + * @param nextMaybeSupplier (invoked if this is a {@link None} + */ + @NotNull Maybe or(@NotNull Supplier> nextMaybeSupplier); + + /** + * Get the value, if this is a {@link Some}; otherwise, throw an exception. + * + * @return value + * @throws NoSuchElementException if this is a {@link None} + */ + @NotNull T expect(); + + /** + * Get the value, if this is a {@link Some}; otherwise, throw an exception via the provided {@link Supplier}. + * + * @return value + */ + @NotNull T getOrThrow(@NotNull Supplier supplier) throws X; + + /** + * The non-empty {@link Maybe} which holds a non-null value. + * + * @param value the value + * @param value type + */ record Some(@NotNull T value) implements Maybe { + + + /** + * Represents a Maybe that contains a non-null value. + */ public Some { requireNonNull( value ); } - } - record None() implements Maybe { - // todo : ensure all None<> are equivalent... + @Override + public @NotNull Maybe biMatch(@NotNull Consumer someConsumer, @NotNull Runnable noneRunner) { + requireNonNull( someConsumer ); + requireNonNull( noneRunner ); + someConsumer.accept( value ); + return this; + } - @SuppressWarnings("unchecked") - private None coerce() { - return (None) this; + @Override + public @NotNull Maybe filter(@NotNull Predicate predicate) { + requireNonNull( predicate ); + return predicate.test( value ) ? this : None.empty(); } - } + @Override + public @NotNull U fold(@NotNull Function fnSome, @NotNull Supplier supNone) { + requireNonNull( fnSome ); + requireNonNull( supNone ); + return requireNonNull( fnSome.apply( value ) ); + } - default @NotNull Maybe biMatch(@NotNull Consumer okConsumer, @NotNull Runnable errRunner) { - requireNonNull( okConsumer ); - requireNonNull( errRunner ); - switch (this) { - case Some(var v) -> okConsumer.accept( v ); - case None __ -> errRunner.run(); + @Override + public @NotNull Stream stream() { + return Stream.of( value ); } - return this; - } + @Override + public @NotNull Maybe map(@NotNull Function fnSome) { + requireNonNull( fnSome ); + return new Some<>( fnSome.apply( value ) ); + } - default @NotNull Maybe filter(@NotNull Predicate predicate, @NotNull Supplier mapper) { - requireNonNull( predicate ); - requireNonNull( mapper ); - return switch (this) { - case Some(var v) when predicate.test( v ) -> this; - default -> new Some<>( mapper.get() ); - }; - } + @Override + public Maybe match(@NotNull Consumer someConsumer) { + requireNonNull( someConsumer ); + someConsumer.accept( value ); + return this; + } + @Override + public void consume(@NotNull Consumer someConsumer) { + requireNonNull( someConsumer ); + someConsumer.accept( value ); + } - default @NotNull T fold(@NotNull Function fnOK, - @NotNull Supplier fnErr) { - requireNonNull( fnOK ); - requireNonNull( fnErr ); - return switch (this) { - case Some(var v) -> requireNonNull( fnOK.apply( v ) ); - case None __ -> requireNonNull( fnErr.get() ); - }; - } + @SuppressWarnings("unchecked") + @Override + public @NotNull Maybe flatMap(@NotNull Function> mapper) { + requireNonNull( mapper ); + return (Maybe) requireNonNull( mapper.apply( value ) ); + } + @Override + public boolean matches(@NotNull Predicate predicate) { + requireNonNull( predicate ); + return predicate.test( value ); + } - default @NotNull Stream stream() { - return switch (this) { - case Some(var v) -> Stream.of( v ); - case None __ -> Stream.empty(); - }; - } + @Override + public boolean contains(@Nullable T v) { + return Objects.equals( value, v ); + } + @Override + public boolean hasSome() { + return true; + } - default @NotNull Maybe map(@NotNull Function mapper) { - requireNonNull( mapper ); - return switch (this) { - case Some(var v) -> new Some<>( mapper.apply( v ) ); // implicit null check - case None v -> v.coerce(); - }; - } + @Override + public @NotNull T orElse(@NotNull T alternate) { + requireNonNull( value ); + return value; + } + @Override + public @NotNull T orElse(@NotNull Supplier supplier) { + requireNonNull( supplier ); + return value; + } + @SuppressWarnings("unchecked") + @Override + public @NotNull Maybe and(@NotNull Maybe nextMaybe) { + requireNonNull( nextMaybe ); + return (Maybe) nextMaybe; + } - @SuppressWarnings("unchecked") - default @NotNull Maybe flatMap(@NotNull Function> mapper) { - requireNonNull( mapper ); - return switch (this) { - case Some(var v) -> (Maybe) requireNonNull( mapper.apply( v ) ); - case None v -> v.coerce(); - }; - } + @SuppressWarnings("unchecked") + @Override + public @NotNull Maybe and(@NotNull Supplier> nextMaybeSupplier) { + requireNonNull( nextMaybeSupplier ); + return requireNonNull( (Maybe) nextMaybeSupplier.get() ); + } + @Override + public @NotNull Maybe or(@NotNull Maybe nextMaybe) { + requireNonNull( nextMaybe ); + return this; + } - default boolean matches(@NotNull Predicate predicate) { - requireNonNull( predicate ); - return switch (this) { - case Some(var v) -> predicate.test( v ); - case None __ -> false; - }; - } + @Override + public @NotNull Maybe or(@NotNull Supplier> nextMaybeSupplier) { + requireNonNull( nextMaybeSupplier ); + return this; + } + @Override + public @NotNull T expect() { + return value; + } - default boolean contains(@Nullable final V value) { - return switch (this) { - case Some(var v) -> Objects.equals( v, value ); - case None __ -> false; - }; + @Override + public @NotNull T getOrThrow(@NotNull Supplier supplier) throws X { + return value; + } } + /** + * The empty {@link Maybe}. + *

+ * This None has a type (unliked net.xyzsd.dichotomy.None), but the type is always coerced to the needed type. + * + * @param value type + */ + record None() implements Maybe { + private static final None _NONE_INSTANCE = new None<>(); - default @NotNull V orElse(@NotNull V alternate) { - requireNonNull( alternate ); - return switch (this) { - case Some(var v) -> v; - case None __ -> alternate; - }; - } + @SuppressWarnings("unchecked") + private static None empty() { + return (None) _NONE_INSTANCE; + } - default @NotNull V orElseGet(@NotNull Supplier supplier) { - requireNonNull( supplier ); - return switch (this) { - case Some(var v) -> v; - case None __ -> requireNonNull( supplier.get() ); - }; - } + @Override + public @NotNull Maybe biMatch(@NotNull Consumer someConsumer, @NotNull Runnable noneRunner) { + requireNonNull( someConsumer ); + requireNonNull( noneRunner ); + noneRunner.run(); + return this; + } + @Override + public @NotNull Maybe filter(@NotNull Predicate predicate) { + requireNonNull( predicate ); + return None.empty(); + } - @SuppressWarnings("unchecked") - default @NotNull Maybe and(@NotNull Maybe nextSome) { - requireNonNull( nextSome ); - return switch (this) { - case Some __ -> (Maybe) nextSome; - case None v -> v.coerce(); - }; - } + @Override + public @NotNull U fold(@NotNull Function fnSome, @NotNull Supplier supNone) { + requireNonNull( fnSome ); + requireNonNull( supNone ); + return requireNonNull( supNone.get() ); + } + @Override + public @NotNull Stream stream() { + return Stream.empty(); + } - @SuppressWarnings("unchecked") - default @NotNull Maybe and(@NotNull Supplier> nextSomeSupplier) { - requireNonNull( nextSomeSupplier ); - return switch (this) { - case Some __ -> requireNonNull( (Maybe) nextSomeSupplier.get() ); - case None v -> v.coerce(); - }; - } + @Override + public @NotNull Maybe map(@NotNull Function fnSome) { + requireNonNull( fnSome ); + return empty(); + } + @Override + public Maybe match(@NotNull Consumer someConsumer) { + requireNonNull( someConsumer ); + return this; // do nothing + } - default @NotNull Maybe or(@NotNull Maybe nextSome) { - requireNonNull( nextSome ); - return switch (this) { - case Some __ -> this; - case None v -> nextSome; - }; - } + @Override + public void consume(@NotNull Consumer someConsumer) { + requireNonNull( someConsumer ); + // do nothing + } + @Override + public @NotNull Maybe flatMap(@NotNull Function> someConsumer) { + requireNonNull( someConsumer ); + return empty(); + } - default @NotNull Maybe or(@NotNull Supplier> nextSomeSupplier) { - requireNonNull( nextSomeSupplier ); - return switch (this) { - case Some __ -> this; - case None v -> requireNonNull( nextSomeSupplier.get() ); - }; - } + @Override + public boolean matches(@NotNull Predicate predicate) { + requireNonNull( predicate ); + return false; + } + @Override + public boolean contains(@Nullable T value) { + return (value == null); + } - default @NotNull V expect() { - return switch (this) { - case Some(var v) -> v; - case None v -> throw new NoSuchElementException(); - }; - } + @Override + public boolean hasSome() { + return false; + } + + @Override + public @NotNull T orElse(@NotNull T alternate) { + requireNonNull( alternate ); + return alternate; + } + + @Override + public @NotNull T orElse(@NotNull Supplier supplier) { + requireNonNull( supplier ); + return requireNonNull( supplier.get() ); + } + + @Override + public @NotNull Maybe and(@NotNull Maybe nextMaybe) { + requireNonNull( nextMaybe ); + return empty(); + } + @Override + public @NotNull Maybe and(@NotNull Supplier> nextMaybeSupplier) { + requireNonNull( nextMaybeSupplier ); + return empty(); + } - @NotNull - default V orThrow(@NotNull Supplier supplier) throws X { - requireNonNull( supplier ); - return switch (this) { - case Some(var v) -> v; - case None v -> throw requireNonNull( supplier.get() ); - }; + @Override + public @NotNull Maybe or(@NotNull Maybe nextMaybe) { + requireNonNull( nextMaybe ); + return nextMaybe; + } + + @Override + public @NotNull Maybe or(@NotNull Supplier> nextMaybeSupplier) { + requireNonNull( nextMaybeSupplier ); + return requireNonNull( nextMaybeSupplier.get() ); + } + + @Override + public @NotNull T expect() throws NoSuchElementException { + throw new NoSuchElementException(); + } + + @Override + public @NotNull T getOrThrow(@NotNull Supplier supplier) throws X { + throw requireNonNull( supplier.get() ); + } } } diff --git a/src/main/java/net/xyzsd/dichotomy/Result.java b/src/main/java/net/xyzsd/dichotomy/Result.java index 27f5bbb..9b168a1 100644 --- a/src/main/java/net/xyzsd/dichotomy/Result.java +++ b/src/main/java/net/xyzsd/dichotomy/Result.java @@ -1,5 +1,6 @@ package net.xyzsd.dichotomy; +import net.xyzsd.dichotomy.trying.Try; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -18,40 +19,53 @@ /** * A {@code Result} type. *

- * A {@code Result} is typically the return value of a method, and can be one of two values, - * each of which can have its own non-null type + * A {@code Result} is typically the return value of a method, and can be only one of two values, + * each of which can have its own non-null type. *

    *
  • OK: Success
  • *
  • Err: Failure (not required to be an {@link Exception})
  • *
- * If there is no associated type, use the {@link None} type rather than {@link Void}. + * If there is no associated type, use the {@link Empty} type rather than {@link Void}. *

* For example: - *

- * {@code
- *      Result myMethod(String input) {
+ * {@snippet :
+ *
+ *      Result myMethod(String input) {
  *           if ("hello".equals(input) ) {
  *              return Result.ofOK("Hello!");
  *           }
- *           return Result.ofErr(new IllegalArgumentException(input));
+ *           return Result.ofErr(666);
  *      }
  *
  *      ...
  *
- *      Result result = myMethod("hello");
+ *      Result result = myMethod("hello");
  *
  *      switch(result) {
  *          case OK success -> System.out.println(success.get());
- *          case Err err -> throw err.get();
+ *          case Err err -> throw new IllegalArgumentException("ERROR CODE: "+err.get());
  *      }
+ *}
+ * Or with patterns, using the above {@code Result}:
+ * {@snippet :
  *
- *
- *
- * }
- * 
+ * switch(result) { + * case OK(String s) -> System.out.printf("Success! %s\n", s); + * case Err(int i) -> System.err.printf("ERROR: %d",i); + * } + *} + *

+ * NOTE: For methods which accept or return type parameters {@code } or {@code }, + * {@code } or {@code } parameters can be completely different types from {@code } or {@code }. *

* While {@link Result}s can be used with switch() statements (particularly the pattern {@code switch} syntax), * they can be used in functional code by mapping, flatMapping (joining), folds, etc. + *

+ * Unlike {@link Either}s, {@link Result}s have a right-bias. Therefore, methods not ending in {@code Err} will + * operate on the {@link OK} value. + *

+ * If the {@link Err} type is an {@link Exception}, then consider using a {@link Try}, which is a specialized + * {@link Result}, and can wrap methods producing checked or unchecked {@link Exception}s. * * @param The Success type. * @param THe Failure (error) type. Does not have to be an Exception. @@ -59,71 +73,6 @@ public sealed interface Result { - record OK(@NotNull V value) implements Result { - - - /** - * Create an OK with the given non-null value. - * - * @param value OK Value - */ - public OK { - requireNonNull( value, "OK: value cannot be null!" ); - } - - /** - * Get the value V - * - * @return unwrapped value V - */ - public @NotNull V get() { - return value; - } - - // For types where the error 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 value stays the same, only the empty error signature changes - @SuppressWarnings("unchecked") - @NotNull - private Result coerce() { - return (Result) this; - } - } - - - record Err(@NotNull E error) implements Result { - - /** - * Create an OK with the given non-null value. - * - * @param error OK Value - */ - public Err { - requireNonNull( error, "Err: error cannot be null!" ); - } - - - /** - * Get the Err E - * - * @return unwrapped Err E - */ - public @NotNull E get() { - return error; - } - - - // For types where the error 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") - @NotNull - private Result coerce() { - return (Result) this; - } - } - - /** * Create an OK (Success) Result for the given non-null value. * @@ -140,15 +89,15 @@ static Result ofOK(@NotNull V value) { /** * Create an OK (Success) Result for the given empty value. *

- * All empty values use the {@link None} type. + * All empty values use the {@link Empty} type. *

* * @param Error value * @return OK result containing the given value. */ @NotNull - static Result ofOK() { - return new OK<>( new None() ); + static Result ofOK() { + return new OK<>( new Empty() ); } /** @@ -173,8 +122,8 @@ static Result ofOK() { * @return A result containing {@code OK} or {@code Err}. */ @NotNull - static Result ofNullable(@Nullable V value) { - return (value == null) ? ofErr( new None() ) : ofOK( value ); + static Result ofNullable(@Nullable V value) { + return (value == null) ? ofErr( new Empty() ) : ofOK( value ); } /** @@ -202,8 +151,8 @@ static Result ofNullable(@Nullable V value) { */ @SuppressWarnings("OptionalUsedAsFieldOrParameterType") @NotNull - static Result from(@NotNull Optional opt) { - return opt.>map( Result::ofOK ).orElseGet( () -> Result.ofErr( new None() ) ); + static Result from(@NotNull Optional opt) { + return opt.>map( Result::ofOK ).orElseGet( () -> Result.ofErr( new Empty() ) ); } /** @@ -219,33 +168,19 @@ static Result ofErr(@NotNull E error) { return new Err<>( error ); } - /** * Get the {@link OK} value V as an {@link Optional}. * * @return return the {@link OK} value if present; otherwise, return an empty {@link Optional}. */ - @NotNull - default Optional ok() { - if(this instanceof OK(V v)) { - return Optional.of(v); - } - return Optional.empty(); - } + @NotNull Optional ok(); /** * Get the {@link Err} value E as an {@link Optional}. * * @return return {@link Err} value if present; otherwise, return an empty {@link Optional}. */ - @NotNull - default Optional err() { - if(this instanceof Err(E e)) { - return Optional.of(e); - } - return Optional.empty(); - } - + @NotNull Optional err(); /** * Create a new {@link Result} with values transposed. @@ -254,13 +189,7 @@ default Optional err() { * * @return A new {@link Result} with {@link OK} and {@link Err} values swapped. */ - default @NotNull Result swap() { - return switch (this) { - case OK(V v) -> ofErr( v ); - case Err(E e) -> ofOK( e ); - }; - } - + @NotNull Result swap(); /** * Executes the action for the {@link OK} or {@link Err} depending upon @@ -271,19 +200,7 @@ default Optional err() { * @see #match(Consumer) * @see #matchErr(Consumer) */ - @NotNull - default Result biMatch(@NotNull Consumer okConsumer, @NotNull Consumer errConsumer) { - requireNonNull( okConsumer ); - requireNonNull( errConsumer ); - - switch (this) { - case OK(V v) -> okConsumer.accept( v ); - case Err(E e) -> errConsumer.accept( e ); - } - - return this; - } - + @NotNull Result biMatch(@NotNull Consumer okConsumer, @NotNull Consumer errConsumer); /** * Returns a new {@link Result}, the value of which is determined by the appropriate mapping function. @@ -298,18 +215,7 @@ default Result biMatch(@NotNull Consumer okConsumer, @NotNull C * @see #map(Function) * @see #mapErr(Function) */ - @NotNull - default Result biMap(@NotNull Function okMapper, - @NotNull Function errMapper) { - requireNonNull( okMapper ); - requireNonNull( errMapper ); - - return switch (this) { - case OK(V v) -> new OK<>( okMapper.apply( v ) ); - case Err(E e) -> new Err<>( errMapper.apply( e ) ); - }; - } - + @NotNull Result biMap(@NotNull Function okMapper, @NotNull Function errMapper); /** * Returns a {@link Result}, produced from one of the appropriate mapping functions. @@ -324,20 +230,7 @@ default Result biMap(@NotNull Function * @see #map(Function) * @see #mapErr(Function) (Function) */ - @SuppressWarnings("unchecked") - @NotNull - default Result biFlatMap(@NotNull Function> fnOK, - @NotNull Function> fnErr) { - - requireNonNull( fnOK ); - requireNonNull( fnErr ); - - return (Result) switch (this) { - case OK(V v) -> requireNonNull( fnOK.apply( v ) ); - case Err(E e) -> requireNonNull( fnErr.apply( e ) ); - }; - } - + @NotNull Result biFlatMap(@NotNull Function> fnOK, @NotNull Function> fnErr); /** * Returns a value, produced from one of the appropriate mapping functions. @@ -356,17 +249,7 @@ default Result biFlatMap(@NotNull Function T fold(@NotNull Function fnOK, @NotNull Function fnErr) { - requireNonNull( fnOK ); - requireNonNull( fnErr ); - - return switch (this) { - case OK(V v) -> requireNonNull( fnOK.apply( v ) ); - case Err(E e) -> requireNonNull( fnErr.apply( e ) ); - }; - } - + @NotNull T fold(@NotNull Function fnOK, @NotNull Function fnErr); /** * Return a {@link Stream}, containing either a single {@link OK} value, or an empty {@link Stream} @@ -374,13 +257,7 @@ default T fold(@NotNull Function fnOK, @NotNull Func * * @see #streamErr() */ - @NotNull - default Stream stream() { - return switch (this) { - case OK(V v) -> Stream.of( v ); - case Err(E e) -> Stream.empty(); - }; - } + @NotNull Stream stream(); /** * Filter a {@link Result}. @@ -399,23 +276,7 @@ default Stream stream() { * @return a {@link Result} based on the algorithm described above. * @throws NullPointerException if the called mapping function returns {@code null}. */ - @NotNull - default Result filter(@NotNull Predicate predicate, - @NotNull Function mapper) { - requireNonNull( predicate ); - requireNonNull( mapper ); - return switch (this) { - case OK ok -> { - if (predicate.test( ok.get() )) { - yield ok; - } else { - yield new Err<>( mapper.apply( ok.get() ) ); - } - } - case Err __ -> this; - }; - } - + @NotNull Result filter(@NotNull Predicate predicate, @NotNull Function mapper); /** * Executes the action iff this is an {@link OK} {@link Result}. @@ -425,13 +286,16 @@ default Result filter(@NotNull Predicate predicate, * @see #match(Consumer) * @see #biMatch(Consumer, Consumer) */ - @NotNull - default Result match(@NotNull Consumer okConsumer) { - requireNonNull( okConsumer ); - if (this instanceof OK(V v)) { - okConsumer.accept( v ); - } - return this; + @NotNull Result match(@NotNull Consumer okConsumer); + + + /** + * Executes the given consumer if this is a {@link OK}. This is a terminal operation. + * + * @param okConsumer the consumer function to be executed + */ + default void consume(@NotNull Consumer okConsumer) { + match(okConsumer); } /** @@ -452,15 +316,7 @@ default Result match(@NotNull Consumer okConsumer) { * @see #mapErr(Function) * @see #biMap(Function, Function) */ - @NotNull - default Result map(@NotNull Function okMapper) { - requireNonNull( okMapper ); - - return switch (this) { - case OK(V v) -> new OK<>( okMapper.apply( v ) ); - case Err err -> err.coerce(); - }; - } + @NotNull Result map(@NotNull Function okMapper); /** * If this is an {@link OK}, return the new {@link Result} supplied by the mapping function. @@ -480,15 +336,7 @@ default Result map(@NotNull Function okMapp * @see #biFlatMap(Function, Function) * @see #flatMapErr(Function) */ - @NotNull - default Result flatMap(@NotNull Function> okMapper) { - requireNonNull( okMapper ); - - return (Result) switch (this) { - case OK(V v) -> requireNonNull( okMapper.apply( v ) ); - case Err err -> err.coerce(); - }; - } + @NotNull Result flatMap(@NotNull Function> okMapper); /** * Determines if this {@link OK} {@link Result} matches the given {@link Predicate}. @@ -499,16 +347,10 @@ default Result flatMap(@NotNull Function okPredicate) { - requireNonNull( okPredicate ); - return switch (this) { - case OK(V v) -> okPredicate.test( v ); - case Err __ -> false; - }; - } + boolean ifPredicate(@NotNull Predicate okPredicate); /** * Determines if this {@link OK} {@link Result} contains the given value. @@ -518,17 +360,11 @@ default boolean matches(@NotNull Predicate okPredicate) { * * @param okValue value to compare * @return {@code true} iff this is an {@link OK} {@link Result} and the contained value equals {@code okValue} - * @see #matches(Predicate) + * @see #ifPredicate(Predicate) * @see #containsErr(Object) - * @see #matchesErr(Predicate) - */ - default boolean contains(@Nullable V okValue) { - requireNonNull( okValue ); - return switch (this) { - case OK(V v) -> Objects.equals( v, okValue ); - case Err __ -> false; - }; - } + * @see #ifPredicateErr(Predicate) + */ + boolean contains(@Nullable V okValue); /** * If this {@link Result} is {@link Err}, return {@code rightAlternate}. @@ -536,18 +372,11 @@ default boolean contains(@Nullable V okValue) { * * @param okAlternate alternate {@link OK} {@link Result} * @return {@code this}, or {@code okAlternate} if {@code this} is {@link Err} - * @see #orElseGet(Supplier) + * @see #orElse(Supplier) * @see #orElseErr(Object) - * @see #orElseGetErr(Supplier) + * @see #orElseErr(Supplier) */ - @NotNull - default V orElse(@NotNull V okAlternate) { - requireNonNull( okAlternate ); - return switch (this) { - case OK(V v) -> v; - case Err __ -> okAlternate; - }; - } + @NotNull V orElse(@NotNull V okAlternate); /** * If this {@link Result} is {@link Err}, return the supplied {@link OK} {@link Result}. @@ -558,16 +387,9 @@ default V orElse(@NotNull V okAlternate) { * @return {@code this}, or the supplied {@link OK} {@link Result} if {@code this} is {@link Err} * @see #orElse(Object) * @see #orElseErr(Object) - * @see #orElseGetErr(Supplier) + * @see #orElseErr(Supplier) */ - @NotNull - default V orElseGet(@NotNull Supplier okSupplier) { - requireNonNull( okSupplier ); - return switch (this) { - case OK(V v) -> v; - case Err __ -> requireNonNull( okSupplier.get() ); - }; - } + @NotNull V orElse(@NotNull Supplier okSupplier); /** * Recover from an error; ignore the {@link Err} value if present, @@ -584,17 +406,7 @@ default V orElseGet(@NotNull Supplier okSupplier) { * @throws NullPointerException if the result of the mapping function is {@code null}. * @see #forfeit(Function) */ - @NotNull - default V recover(@NotNull Function fnE2V) { - // equivalent to: return fold( Function.identity(), fnE2V ); - requireNonNull( fnE2V ); - - return switch (this) { - case OK(V v) -> v; - case Err(E e) -> requireNonNull( fnE2V.apply( e ) ); - }; - } - + @NotNull V recover(@NotNull Function fnE2V); /** * Return a {@link Stream}, containing either a single {@link Err} value, or an empty {@link Stream} @@ -602,13 +414,7 @@ default V recover(@NotNull Function fnE2V) { * * @see #stream() */ - @NotNull - default Stream streamErr() { - return switch (this) { - case OK(V v) -> Stream.empty(); - case Err(E e) -> Stream.of( e ); - }; - } + @NotNull Stream streamErr(); /** * Executes the action iff this is an {@link Err} {@link Result}. @@ -618,16 +424,7 @@ default Stream streamErr() { * @see #match(Consumer) * @see #biMatch(Consumer, Consumer) */ - @NotNull - default Result matchErr(@NotNull Consumer errConsumer) { - // equivalent to: biMatch( (x) -> {}, errConsumer ); - requireNonNull( errConsumer ); - if (this instanceof Err(E e)) { - errConsumer.accept( e ); - } - return this; - } - + @NotNull Result matchErr(@NotNull Consumer errConsumer); /** * If this is an {@link Err}, return a new {@link Err} value produced by the given mapping function. @@ -637,8 +434,23 @@ default Result matchErr(@NotNull Consumer errConsumer) { * {@link Err} values. *

*

- * This is equivalent to {@code map( leftMapper, Function.identity() )}. - *

+ * This is equivalent to: + * {@snippet : + * // given: + * Result result = Result<>.of(5); + * Function mapper = (i) -> 10.0d * i; + * + * // newResult : 50.0d + * Result newResult = result.mapErr(mapper); + * + * // equivalent + * newResult = either.map(mapper, Function.identity()); + * + * // also equivalent: + * newResult = result.swap() // Result<Integer,String> + * .map(mapper) // Result<Double,String> + * .swap(); // Result<String,Double> + *} * * @param errMapper the mapping function producing a new {@link Err} value. * @return a new {@link Err} produced by the mapping function if this is {@link Err}; @@ -647,14 +459,7 @@ default Result matchErr(@NotNull Consumer errConsumer) { * @see #map(Function) * @see #biMap(Function, Function) */ - @NotNull - default Result mapErr(@NotNull Function errMapper) { - requireNonNull( errMapper ); - return switch (this) { - case OK ok -> ok.coerce(); - case Err(E e) -> new Err<>( errMapper.apply( e ) ); - }; - } + @NotNull Result mapErr(@NotNull Function errMapper); /** * If this is an {@link Err}, return the new {@link Result} supplied by the mapping function. @@ -674,15 +479,7 @@ default Result mapErr(@NotNull Function err * @see #biFlatMap(Function, Function) * @see #flatMap(Function) */ - @NotNull - default Result flatMapErr(@NotNull Function> errMapper) { - requireNonNull( errMapper ); - return switch (this) { - case OK ok -> ok.coerce(); - case Err(E e) -> (Result) requireNonNull( errMapper.apply( e ) ); - }; - } - + @NotNull Result flatMapErr(@NotNull Function> errMapper); /** * Determines if this {@link Err} {@link Result} matches the given {@link Predicate}. @@ -694,15 +491,9 @@ default Result flatMapErr(@NotNull Function errPredicate) { - requireNonNull( errPredicate ); - return switch (this) { - case OK __ -> false; - case Err(E e) -> errPredicate.test( e ); - }; - } + * @see #ifPredicate(Predicate) + */ + boolean ifPredicateErr(@NotNull Predicate errPredicate); /** * Determines if this {@link Err} {@link Result} contains the given value. @@ -712,17 +503,11 @@ default boolean matchesErr(@NotNull Predicate errPredicate) { * * @param errValue value to compare * @return {@code true} iff this is an {@link Err} {@link Result} and the contained value equals {@code errValue} - * @see #matchesErr - * @see #matches + * @see #ifPredicateErr + * @see #ifPredicate * @see #contains */ - default boolean containsErr(@NotNull E errValue) { - requireNonNull( errValue ); - return switch (this) { - case OK __ -> false; - case Err(E e) -> Objects.equals( e, errValue ); - }; - } + boolean containsErr(@Nullable E errValue); /** * If this {@link Result} is an {@link OK}, return {@code errAlternate}. @@ -732,16 +517,9 @@ default boolean containsErr(@NotNull E errValue) { * @return {@code this}, or {@code leftAlternate} if {@code this} is an {@link OK} * @see #orElseErr(Object) * @see #orElse(Object) - * @see #orElseGet(Supplier) + * @see #orElse(Supplier) */ - @NotNull - default E orElseErr(@NotNull E errAlternate) { - requireNonNull( errAlternate ); - return switch (this) { - case OK __ -> errAlternate; - case Err(E e) -> e; - }; - } + @NotNull E orElseErr(@NotNull E errAlternate); /** * If this {@link Result} is an {@link OK}, return the supplied {@link Err} {@link Result}. @@ -752,16 +530,9 @@ default E orElseErr(@NotNull E errAlternate) { * @return {@code this}, or the supplied {@link Err} {@link Result} if {@code this} is {@link OK} * @see #orElse(Object) * @see #orElseErr(Object) - * @see #orElseGetErr(Supplier) + * @see #orElseErr(Supplier) */ - @NotNull - default E orElseGetErr(@NotNull Supplier errSupplier) { - requireNonNull( errSupplier ); - return switch (this) { - case OK __ -> requireNonNull( errSupplier.get() ); - case Err(E e) -> e; - }; - } + @NotNull E orElseErr(@NotNull Supplier errSupplier); /** * Forfeit (ignore) the {@link OK} value if present, and apply the mapping function to get an {@link Err}. @@ -777,16 +548,7 @@ default E orElseGetErr(@NotNull Supplier errSupplier) { * @throws NullPointerException if the result of the mapping function is {@code null}. * @see #recover(Function) */ - @NotNull - default E forfeit(@NotNull Function fnV2E) { - // equivalent to: fold( fn, Function.identity() ); - requireNonNull( fnV2E ); - return switch (this) { - case OK(V v) -> requireNonNull( fnV2E.apply( v ) ); - case Err(E e) -> e; - }; - } - + @NotNull E forfeit(@NotNull Function fnV2E); /** * If {@code this} is {@link Err}, return it. Otherwise, return the next {@link Result} given. @@ -797,14 +559,7 @@ default E forfeit(@NotNull Function fnV2E) { * @see #or(Result) * @see #or(Supplier) */ - @NotNull - default Result and(@NotNull Result nextResult) { - requireNonNull( nextResult ); - return switch (this) { - case OK __ -> nextResult; - case Err err -> err.coerce(); - }; - } + @NotNull Result and(@NotNull Result nextResult); /** * If {@code this} is {@link Err}, return it (without invoking the {@link Supplier}). @@ -817,14 +572,7 @@ default Result and(@NotNull Result nextResult) { * @see #or(Result) * @see #or(Supplier) */ - @NotNull - default Result and(@NotNull Supplier> nextResultSupplier) { - requireNonNull( nextResultSupplier ); - return switch (this) { - case OK __ -> requireNonNull( nextResultSupplier.get() ); - case Err err -> err.coerce(); - }; - } + @NotNull Result and(@NotNull Supplier> nextResultSupplier); /** * If {@code this} is {@link OK}, return it. @@ -836,14 +584,7 @@ default Result and(@NotNull Supplier> nextResultSuppli * @see #and(Result) * @see #and(Supplier) */ - @NotNull - default Result or(@NotNull Result nextResult) { - requireNonNull( nextResult ); - return switch (this) { - case OK ok -> ok.coerce(); - case Err __ -> nextResult; - }; - } + @NotNull Result or(@NotNull Result nextResult); /** * If {@code this} is {@link OK}, return it (without invoking the {@link Supplier}). @@ -856,15 +597,7 @@ default Result or(@NotNull Result nextResult) { * @see #and(Result) * @see #and(Supplier) */ - @NotNull - default Result or(@NotNull Supplier> nextResultSupplier) { - requireNonNull( nextResultSupplier ); - return switch (this) { - case OK ok -> ok.coerce(); - case Err __ -> requireNonNull( nextResultSupplier.get() ); - }; - } - + @NotNull Result or(@NotNull Supplier> nextResultSupplier); /** * Expect success (an {@link OK} value), otherwise throw a runtime Exception. @@ -874,7 +607,7 @@ default Result or(@NotNull Supplier> nextResultSupplie *
    *
  • If {@link Err} extends {@link RuntimeException}, * the RuntimeException is thrown as-is, without wrapping into a {@link NoSuchElementException}
  • - *
  • If {@link Err} extends {@link Throwable} (but not a subclass of {@link RuntimeException}) + *
  • If {@link Err} extends {@link Exception} (but not a subclass of {@link RuntimeException}) * it is wrapped in a {@link NoSuchElementException} and then thrown
  • *
  • If {@link Err} is any other type, * it is converted to a {@link String} using {@code String.valueOf()}, @@ -884,26 +617,13 @@ default Result or(@NotNull Supplier> nextResultSupplie * @return Value (if {@link OK}) or throws a {@link RuntimeException} (if {@link Err}) * @throws RuntimeException as detailed above. */ - @NotNull - default V expect() throws RuntimeException { - return switch (this) { - case OK(V v) -> v; - case Err(E error) -> { - switch (error) { - case RuntimeException e -> throw e; - case Throwable t -> throw new NoSuchElementException( t ); - default -> throw new NoSuchElementException( String.valueOf( error ) ); - } - } - }; - } - + @NotNull V expect() throws RuntimeException; /** - * Throw the given supplied {@link Exception} (or more precisely, {@link Throwable}). + * Throw the given supplied {@link Exception} *

    * This method can wrap an {@link Err} type if that {@link Err} type is allowed by the constructor. - * For example, an {@code IOException::new} could wrap a {@code Throwable} or {@code String} type. + * For example, an {@code IOException::new} could wrap an {@code Exception} or {@code String} type. *

    *
          * {@code
    @@ -943,43 +663,517 @@ case Err(E error) -> {
          * 

    * * @param exFn Exception producing function - * @param Throwable (Exception) created by {@code exFn} + * @param Exception created by {@code exFn} * @return Value V - * @throws X Throwable - * @see #orThrow(Supplier) + * @throws X Exception + * @see #getOrThrow(Supplier) * @see #expect() */ - @NotNull - default V orThrowWrapped(@NotNull Function exFn) throws X { - requireNonNull( exFn ); - return switch (this) { - case OK(V v) -> v; - case Err(E e) -> throw requireNonNull( exFn.apply( e ) ); - }; - } - + @NotNull V getOrThrowMapped(@NotNull Function exFn) throws X; /** - * Throw the given supplied {@link Exception} (or more precisely, {@link Throwable}). + * Throw the given supplied {@link Exception} *

    * This method takes a {@link Supplier}. In many cases, the exception message or fields can be customized - * more easily with {@link #orThrowWrapped(Function)}. + * more easily with {@link #getOrThrowMapped(Function)}. *

    * * @param supplier Exception supplier - * @param Throwable (Exception) created by the supplier + * @param Exception created by the supplier * @return Value V - * @throws X Throwable - * @see #orThrowWrapped(Function) + * @throws X Exception + * @see #getOrThrowMapped(Function) * @see #expect() */ - @NotNull - default V orThrow(@NotNull Supplier supplier) throws X { - requireNonNull( supplier ); - return switch (this) { - case OK(V v) -> v; - case Err(E e) -> throw requireNonNull( supplier.get() ); - }; + @NotNull V getOrThrow(@NotNull Supplier supplier) throws X; + + /** + * Success Result. + * + * @param value OK value + * @param OK type + * @param Error type + */ + record OK(@NotNull V value) implements Result { + + + /** + * Create an OK with the given non-null value. + * + * @param value OK Value + */ + public OK { + requireNonNull( value, "OK: value cannot be null!" ); + } + + /** + * Get the value V + * + * @return unwrapped value V + */ + public @NotNull V get() { + return value; + } + + + @Override + public @NotNull Optional ok() { + return Optional.of( value ); + } + + @Override + public @NotNull Optional err() { + return Optional.empty(); + } + + @Override + public @NotNull Result swap() { + return new Err<>( value ); + } + + @Override + public @NotNull Result biMatch(@NotNull Consumer okConsumer, @NotNull Consumer errConsumer) { + requireNonNull( okConsumer ); + requireNonNull( errConsumer ); + okConsumer.accept( value ); + return this; + } + + @Override + public @NotNull Result biMap(@NotNull Function okMapper, @NotNull Function errMapper) { + requireNonNull( okMapper ); + requireNonNull( errMapper ); + return new OK<>( okMapper.apply( value ) ); + } + + + @SuppressWarnings("unchecked") + @Override + public @NotNull Result biFlatMap(@NotNull Function> okMapper, @NotNull Function> errMapper) { + requireNonNull( okMapper ); + requireNonNull( errMapper ); + return (Result) requireNonNull( okMapper.apply( value ) ); + } + + @Override + public @NotNull T fold(@NotNull Function fnOK, @NotNull Function fnErr) { + requireNonNull( fnOK ); + requireNonNull( fnErr ); + return requireNonNull( fnOK.apply( value ) ); + } + + @Override + public @NotNull Stream stream() { + return Stream.of( value ); + } + + @Override + public @NotNull Result filter(@NotNull Predicate predicate, @NotNull Function mapper) { + requireNonNull( predicate ); + requireNonNull( mapper ); + if (predicate.test( value )) { + return this; + } + return new Err<>( mapper.apply( value ) ); + } + + @Override + public @NotNull Result match(@NotNull Consumer okConsumer) { + requireNonNull( okConsumer ); + okConsumer.accept( value ); + return this; + } + + @Override + public @NotNull Result map(@NotNull Function okMapper) { + requireNonNull( okMapper ); + return new OK<>( okMapper.apply( value ) ); + } + + @SuppressWarnings("unchecked") + @Override + public @NotNull Result flatMap(@NotNull Function> okMapper) { + requireNonNull( okMapper ); + return (Result) requireNonNull( okMapper.apply( value ) ); + } + + @Override + public boolean ifPredicate(@NotNull Predicate okPredicate) { + requireNonNull( okPredicate ); + return okPredicate.test( value ); + } + + @Override + public boolean contains(@Nullable V okValue) { + return Objects.equals( value, okValue ); + } + + @Override + public @NotNull V orElse(@NotNull V okAlternate) { + requireNonNull( okAlternate ); + return value; + } + + @Override + public @NotNull V orElse(@NotNull Supplier okSupplier) { + requireNonNull( okSupplier ); + return value; + } + + @Override + public @NotNull V recover(@NotNull Function fnE2V) { + requireNonNull( fnE2V ); + return value; + } + + @Override + public @NotNull Stream streamErr() { + return Stream.empty(); + } + + @Override + public @NotNull Result matchErr(@NotNull Consumer errConsumer) { + requireNonNull( errConsumer ); + return this; + } + + @Override + public @NotNull Result mapErr(@NotNull Function errMapper) { + requireNonNull( errMapper ); + return coerce(); + } + + @Override + public @NotNull Result flatMapErr(@NotNull Function> errMapper) { + requireNonNull( errMapper ); + return coerce(); + } + + @Override + public boolean ifPredicateErr(@NotNull Predicate errPredicate) { + requireNonNull( errPredicate ); + return false; + } + + @Override + public boolean containsErr(@Nullable E errValue) { + requireNonNull( errValue ); + return false; + } + + @Override + public @NotNull E orElseErr(@NotNull E errAlternate) { + requireNonNull( errAlternate ); + return errAlternate; + } + + @Override + public @NotNull E orElseErr(@NotNull Supplier errSupplier) { + requireNonNull( errSupplier ); + return requireNonNull( errSupplier.get() ); + } + + @Override + public @NotNull E forfeit(@NotNull Function fnV2E) { + requireNonNull( fnV2E ); + return requireNonNull( fnV2E.apply( value ) ); + } + + @Override + public @NotNull Result and(@NotNull Result nextResult) { + requireNonNull( nextResult ); + return nextResult; + } + + @Override + public @NotNull Result and(@NotNull Supplier> nextResultSupplier) { + requireNonNull( nextResultSupplier ); + return requireNonNull( nextResultSupplier.get() ); + } + + @Override + public @NotNull Result or(@NotNull Result nextResult) { + requireNonNull( nextResult ); + return coerce(); + } + + @Override + public @NotNull Result or(@NotNull Supplier> nextResultSupplier) { + requireNonNull( nextResultSupplier ); + return coerce(); + } + + @Override + public @NotNull V expect() throws RuntimeException { + return value; + } + + @Override + public @NotNull V getOrThrowMapped(@NotNull Function exFn) { + return value; + } + + + @Override + public @NotNull V getOrThrow(@NotNull Supplier supplier) { + return value; + } + + // For types where the error 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 value stays the same, only the empty error signature changes + @SuppressWarnings("unchecked") + @NotNull + private Result coerce() { + return (Result) this; + } + } + + /** + * Error Result. + * + * @param error Error value + * @param OK type + * @param Error type + */ + record Err(@NotNull E error) implements Result { + + /** + * Create an OK with the given non-null value. + * + * @param error OK Value + */ + public Err { + requireNonNull( error, "Err: error cannot be null!" ); + } + + + /** + * Get the Err E + * + * @return unwrapped Err E + */ + public @NotNull E get() { + return error; + } + + + @Override + public @NotNull Optional ok() { + return Optional.empty(); + } + + @Override + public @NotNull Optional err() { + return Optional.of( error ); + } + + @Override + public @NotNull Result swap() { + return new OK<>( error ); + } + + @Override + public @NotNull Result biMatch(@NotNull Consumer okConsumer, @NotNull Consumer errConsumer) { + requireNonNull( okConsumer ); + requireNonNull( errConsumer ); + errConsumer.accept( error ); + return this; + } + + @Override + public @NotNull Result biMap(@NotNull Function okMapper, @NotNull Function errMapper) { + requireNonNull( okMapper ); + requireNonNull( errMapper ); + return new Err<>( errMapper.apply( error ) ); + } + + @SuppressWarnings("unchecked") + @Override + public @NotNull Result biFlatMap(@NotNull Function> okMapper, @NotNull Function> errMapper) { + requireNonNull( okMapper ); + requireNonNull( errMapper ); + return (Result) requireNonNull( errMapper.apply( error ) ); + } + + @Override + public @NotNull T fold(@NotNull Function fnOK, @NotNull Function fnErr) { + requireNonNull( fnOK ); + requireNonNull( fnErr ); + return requireNonNull( fnErr.apply( error ) ); + } + + @Override + public @NotNull Stream stream() { + return Stream.empty(); + } + + @Override + public @NotNull Result filter(@NotNull Predicate predicate, @NotNull Function mapper) { + requireNonNull( predicate ); + requireNonNull( mapper ); + return this; + } + + @Override + public @NotNull Result match(@NotNull Consumer okConsumer) { + requireNonNull( okConsumer ); + return this; + } + + @Override + public @NotNull Result map(@NotNull Function okMapper) { + requireNonNull( okMapper ); + return coerce(); + } + + @Override + public @NotNull Result flatMap(@NotNull Function> okMapper) { + requireNonNull( okMapper ); + return coerce(); + } + + @Override + public boolean ifPredicate(@NotNull Predicate okPredicate) { + requireNonNull( okPredicate ); + return false; + } + + @Override + public boolean contains(@Nullable V okValue) { + requireNonNull( okValue ); + return false; + } + + @Override + public @NotNull V orElse(@NotNull V okAlternate) { + requireNonNull( okAlternate ); + return okAlternate; + } + + @Override + public @NotNull V orElse(@NotNull Supplier okSupplier) { + requireNonNull( okSupplier ); + return requireNonNull( okSupplier.get() ); + } + + @Override + public @NotNull V recover(@NotNull Function fnE2V) { + requireNonNull( fnE2V ); + return requireNonNull( fnE2V.apply( error ) ); + } + + @Override + public @NotNull Stream streamErr() { + return Stream.of( error ); + } + + @Override + public @NotNull Result matchErr(@NotNull Consumer errConsumer) { + requireNonNull( errConsumer ); + errConsumer.accept( error ); + return this; + } + + @Override + public @NotNull Result mapErr(@NotNull Function errMapper) { + requireNonNull( errMapper ); + return new Err<>( errMapper.apply( error ) ); + } + + @SuppressWarnings("unchecked") + @Override + public @NotNull Result flatMapErr(@NotNull Function> errMapper) { + requireNonNull( errMapper ); + return (Result) requireNonNull( errMapper.apply( error ) ); + } + + @Override + public boolean ifPredicateErr(@NotNull Predicate errPredicate) { + requireNonNull( errPredicate ); + return errPredicate.test( error ); + } + + @Override + public boolean containsErr(@Nullable E errValue) { + requireNonNull( errValue ); + return Objects.equals( error, errValue ); + } + + @Override + public @NotNull E orElseErr(@NotNull E errAlternate) { + requireNonNull( errAlternate ); + return error; + } + + @Override + public @NotNull E orElseErr(@NotNull Supplier errSupplier) { + requireNonNull( errSupplier ); + return error; + } + + @Override + public @NotNull E forfeit(@NotNull Function fnV2E) { + requireNonNull( fnV2E ); + return error; + } + + @Override + public @NotNull Result and(@NotNull Result nextResult) { + requireNonNull( nextResult ); + return coerce(); + } + + @Override + public @NotNull Result and(@NotNull Supplier> nextResultSupplier) { + requireNonNull( nextResultSupplier ); + return coerce(); + } + + @Override + public @NotNull Result or(@NotNull Result nextResult) { + requireNonNull( nextResult ); + return nextResult; + } + + @Override + public @NotNull Result or(@NotNull Supplier> nextResultSupplier) { + requireNonNull( nextResultSupplier ); + return requireNonNull( nextResultSupplier.get() ); + } + + @Override + public @NotNull V expect() throws RuntimeException { + // TODO: when pattern-switch is out of preview, convert this code + if(error instanceof RuntimeException e) { + throw e; + } else if(error instanceof Throwable t) { + throw new NoSuchElementException( t ); + } else { + throw new NoSuchElementException( String.valueOf( error ) ); + } + } + + @Override + public @NotNull V getOrThrowMapped(@NotNull Function exFn) throws X { + requireNonNull( exFn ); + throw requireNonNull( exFn.apply( error ) ); + } + + @Override + public @NotNull V getOrThrow(@NotNull Supplier supplier) throws X { + requireNonNull( supplier ); + throw requireNonNull( supplier.get() ); + } + + // For types where the error 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") + @NotNull + private Result coerce() { + return (Result) this; + } } diff --git a/src/main/java/net/xyzsd/dichotomy/gatherers/Gatherer.java b/src/main/java/net/xyzsd/dichotomy/gatherers/Gatherer.java deleted file mode 100644 index e96fc66..0000000 --- a/src/main/java/net/xyzsd/dichotomy/gatherers/Gatherer.java +++ /dev/null @@ -1,12 +0,0 @@ -package net.xyzsd.dichotomy.gatherers; - -public class Gatherer { - - - /* - - possible implementation of JDK22 gatherers for Eithers/Results/etc - - */ - -} diff --git a/src/main/java/net/xyzsd/dichotomy/gatherers/package-info.java b/src/main/java/net/xyzsd/dichotomy/gatherers/package-info.java new file mode 100644 index 0000000..800defa --- /dev/null +++ b/src/main/java/net/xyzsd/dichotomy/gatherers/package-info.java @@ -0,0 +1,4 @@ +/** + * Future gathering (ahem) place for JDK22-compatible Gatherers. + */ +package net.xyzsd.dichotomy.gatherers; \ No newline at end of file diff --git a/src/main/java/net/xyzsd/dichotomy/package-info.java b/src/main/java/net/xyzsd/dichotomy/package-info.java index 3280550..fe6a35b 100644 --- a/src/main/java/net/xyzsd/dichotomy/package-info.java +++ b/src/main/java/net/xyzsd/dichotomy/package-info.java @@ -1,4 +1,5 @@ /** - * This package contains the monadic types and the {@link java.lang.Void}-alternative type {@link net.xyzsd.dichotomy.None}. + * Contains the primary Monads ({@link net.xyzsd.dichotomy.Either}, {@link net.xyzsd.dichotomy.Result}, and {@link net.xyzsd.dichotomy.Maybe}) + * and the {@link java.lang.Void}-alternative type {@link net.xyzsd.dichotomy.Empty}. */ package net.xyzsd.dichotomy; \ No newline at end of file diff --git a/src/main/java/net/xyzsd/dichotomy/trying/Try.java b/src/main/java/net/xyzsd/dichotomy/trying/Try.java new file mode 100644 index 0000000..d1f136d --- /dev/null +++ b/src/main/java/net/xyzsd/dichotomy/trying/Try.java @@ -0,0 +1,1169 @@ +package net.xyzsd.dichotomy.trying; + + +import net.xyzsd.dichotomy.Empty; +import net.xyzsd.dichotomy.Result; +import net.xyzsd.dichotomy.trying.function.ExFunction; +import net.xyzsd.dichotomy.trying.function.ExSupplier; +import net.xyzsd.dichotomy.trying.function.SpecExSupplier; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Objects; +import java.util.Optional; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.function.Supplier; +import java.util.stream.Stream; + +import static java.util.Objects.requireNonNull; + +/** + * A {@link Result}-like monad specialized to handle both successful and failed operations, but where + * failed operations are always {@link Exception}s. + * + * + * @param The type of the value contained in the Try. + * @param The type of the exception that can occur in the Try. + */ +public sealed interface Try { + + + /** + * Create an {@link OK} (Success) {@link Try} for the given non-null value. + * + * @param value value for success + * @param Value type + * @param Error Exception type + * @return {@link OK} {@link Try} containing the given value. + */ + @NotNull + static Try ofOK(@NotNull V value) { + return new OK<>( value ); + } + + /** + * Create an empty {@link OK} (Success) {@link Try}. + *

    + * All empty values use the {@link Empty} type. + *

    + * + * @param Error value + * @return {@link OK} {@link Try} containing the given value. + */ + @NotNull + static Try ofOK() { + return new OK<>( new Empty() ); + } + + /** + * Create an {@link Err} (Failure) {@link Try} for the given non-null value. + * + * @param error Error + * @param Value type + * @param Error Exception type + * @return {@link Err} containing the given error Exception. + */ + @NotNull + static Try ofErr(@NotNull E error) { + return new Err<>( error ); + } + + /** + * Creates a Try object by executing a supplied function and handling exceptions. + * + * @param supplier the supplier function that provides the result + * @param exClass the class of exception to be handled + * @param the type of result + * @param the type of exception + * @return a Try object representing the result or exception + * @throws NullPointerException if supplier is null + * @throws RuntimeException if an exception not assignable to exClass occurs + */ + @NotNull + static Try of(@NotNull Supplier supplier, @NotNull Class exClass) { + requireNonNull( supplier ); + + try { + return new OK<>( supplier.get() ); + } catch (Throwable t) { + if (exClass.isInstance( t )) { + return new Err<>( exClass.cast( t ) ); + } + throw t; + } + } + + /** + * Creates a Try object based on the result of the given supplier. + * + * @param supplier the supplier that provides the value + * @param the type of the value + * @return a Try object representing the result of the supplier + * @throws NullPointerException if the supplier is null + */ + // ANY runtime exception. standard Suppliers Can't throw checked exceptions (no throws clause defined), so, not included + @NotNull + static Try of(@NotNull Supplier supplier) { + requireNonNull( supplier ); + + try { + return new OK<>( supplier.get() ); + } catch (RuntimeException ex) { + return new Err<>( ex ); + } + } + + + + /** + * Get a {@link Try} from an {@link ExSupplier}. + *

    + * The {@link ExSupplier} may throw a checked or unchecked {@link Exception}. + * The result of the function, or {@link Exception} will be contained in the {@link Try}. + *

    + * + * @param exSupplier Supplier to invoke + * @param type of result supplied + * @return A {@link Try} containing the supplied value or an {@link Exception} + * @see #from(ExSupplier) + * @see #of(Supplier) + * @see #from(Supplier) + */ + @NotNull + static Try of(@NotNull ExSupplier exSupplier) { + requireNonNull( exSupplier ); + + try { + return new Try.OK<>( exSupplier.get() ); + } catch (Exception ex) { + return new Try.Err<>( ex ); + } + } + + /** + * Converts an {@link Supplier} which may throw {@link RuntimeException}s to a {@link Supplier} of {@link Try}s. + * + * @param supplier Supplier to invoke + * @param type of result supplied + * @return A {@link Try} containing the supplied value or a {@link RuntimeException} + * @see #of(Supplier) + * @see #of(ExSupplier) + * @see #from(ExSupplier) + */ + @NotNull + static Supplier> from(@NotNull Supplier supplier) { + requireNonNull( supplier ); + return () -> of( supplier ); + } + + /** + * Converts an {@link ExSupplier} to a {@link Supplier} of {@link Try}s. + *

    + * The {@link ExSupplier} may throw a checked or unchecked {@link Exception}, + * which will be wrapped in an {@link Err} {@link Try}. + *

    + * + * @param exSupplier Supplier to invoke + * @param type of result supplied + * @return A {@link Try} containing the supplied value or an {@link Exception} + */ + @NotNull + static Supplier> from(@NotNull ExSupplier exSupplier) { + requireNonNull( exSupplier ); + return () -> of( exSupplier ); + } + + /** + * Converts a {@link SpecExSupplier} to a {@link Supplier} of {@link Try}s. + *

    + * The returned {@link Supplier} will only wrap {@link Exception}s of the given class + * parameter {@code } (and subclasses); all other {@link RuntimeException}s will be thrown. + *

    + * + * @param exSupplier {@link SpecExSupplier} to wrap + * @param value wrapped by the given {@link Try}. + * @param {@link RuntimeException} wrapped by the given {@link Try}. + * @return A {@link Supplier} of {@link Try}s containing the supplied value or the specified + * {@link RuntimeException}{@code } + * @see #of(SpecExSupplier, Class) + * @see #from(ExSupplier) + * @see #from(Supplier) + */ + @NotNull + static Supplier> from(@NotNull SpecExSupplier exSupplier, final Class exClass) { + requireNonNull( exSupplier ); + return () -> of( exSupplier, exClass ); + } + + /** + * Execute a {@link SpecExSupplier} to a supply a {@link Try}. + *

    + * The returned {@link Try} will contain the supplied value or the {@link RuntimeException} of the given class + * parameter {@code } (and subclasses); all other {@link RuntimeException}s will be thrown. + *

    + * + * @param exSupplier {@link SpecExSupplier} to wrap + * @param supplied value wrapped by a {@link Try}. + * @param {@link RuntimeException} type wrapped by a {@link Try}. + * @return A {@link Try} containing the supplied value or the specified {@link RuntimeException} + * @see #from(SpecExSupplier, Class) + */ + @NotNull + static Try of(@NotNull SpecExSupplier exSupplier, final Class exClass) { + requireNonNull( exSupplier ); + requireNonNull( exClass ); + + try { + return new Try.OK<>( exSupplier.get() ); + } catch (RuntimeException ex) { + if (exClass.isAssignableFrom( ex.getClass() )) { + return new Try.Err<>( exClass.cast( ex ) ); + } + throw ex; + } + } + + /** + * Apply the given {@link ExFunction}, returning a {@link Try} which wraps either the return value + * of the {@link ExFunction} or an {@link Exception}. + * + * @param in value applied + * @param exFn function, which may throw an exception + * @param function input + * @param function output + * @return A {@link Try} wrapping either the output value or an {@link Exception}. + * @see #from(ExFunction) + */ + @NotNull + static Try of(@Nullable T in, @NotNull final ExFunction exFn) { + requireNonNull( exFn ); + return fnToTry( in, exFn ); + } + + /** + * Given an {@link Exception}-producing function ({@link ExFunction}), returns a {@link Function} which + * returns a {@link Try} wrapping the function output or {@link Exception}. This is lazy; the function + * is not evaluated. + * + * @param exFn {@link ExFunction} which may generate an exception + * @param function input + * @param function output + * @return {@link Function} returning {@link Try}s which wrap the output or {@link Exception} + * @see #of(Object, ExFunction) + */ + @NotNull + static Function> from(@NotNull final ExFunction exFn) { + requireNonNull( exFn ); + return (in) -> fnToTry( in, exFn ); + } + + private static Try fnToTry(@Nullable T in, @NotNull final ExFunction exFn) { + try { + return new Try.OK<>( exFn.apply( in ) ); + } catch (Exception ex) { + return new Try.Err<>( ex ); + } + } + + /** + * Get the {@link Result.OK} value V as an {@link Optional}. + * + * @return return the {@link Result.OK} value if present; otherwise, return an empty {@link Optional}. + */ + @NotNull Optional ok(); + + /** + * Get the {@link Result.Err} value E as an {@link Optional}. + * + * @return return {@link Result.Err} value if present; otherwise, return an empty {@link Optional}. + */ + @NotNull Optional err(); + + + ///////////////////////////////////////////////// + // Interface methods + ///////////////////////////////////////////////// + + /** + * Executes the action for the {@link OK} or {@link Err} depending upon + * the value of this {@link Try}. + * + * @return {@code this} + * @throws NullPointerException if the called action returns {@code null}. + * @see #match(Consumer) + * @see #matchErr(Consumer) + */ + @NotNull Try biMatch(@NotNull Consumer okConsumer, @NotNull Consumer errConsumer); + + /** + * Returns a new {@link Try}, the value of which is determined by the appropriate mapping function. + *

    + * The returned {@link Try} (which may be {@link OK} or {@link Err}) can have different types. + *

    + * + * @param okMapper the mapping function for {@link OK} values. + * @param errMapper the mapping function for {@link Err} values. + * @return the {@link Try} produced from {@code okMapper} or {@code errMapper} + * @throws NullPointerException if the called function returns {@code null}. + * @see #map(Function) + * @see #mapErr(Function) + */ + @NotNull Try biMap(@NotNull Function okMapper, @NotNull Function errMapper); + + /** + * Returns a {@link Try}, produced from one of the appropriate mapping functions. + *

    + * The produced {@link Try} (which may be {@link Err} or {@link OK}) can have different types. + *

    + * + * @param fnOK the mapping function for {@link OK} values. + * @param fnErr the mapping function for {@link Err} values. + * @return the {@link Try} produced from {@code fnOK} or {@code fnErr} + * @throws NullPointerException if the called function returns {@code null}. + * @see #map(Function) + * @see #mapErr(Function) (Function) + */ + @NotNull Try biFlatMap(@NotNull Function> fnOK, @NotNull Function> fnErr); + + /** + * Returns a value, produced from one of the appropriate mapping functions. + *

    + * The produced value can have any type (except {@link Void}) but mapping functions for + * both {@link Err} and {@link OK} types must produce the same value type. + *

    + *

    + * If no value is to be returned, use {@link #biMatch(Consumer, Consumer)} instead. + *

    + * + * @param fnOK the mapping function for {@link Err} values. + * @param fnErr the mapping function for {@link OK} values. + * @return the value produced from {@code fnOK} or {@code fnErr} + * @throws NullPointerException if the called function returns {@code null}. + * @see #recover(Function) + * @see #forfeit(Function) + */ + @NotNull T fold(@NotNull Function fnOK, @NotNull Function fnErr); + + /** + * Return a {@link Stream}, containing either a single {@link OK} value, or an empty {@link Stream} + * if this is an {@link Err} value. + * + * @see #streamErr() + */ + @NotNull Stream stream(); + + /** + * Filter a {@link Try}. + *

    + * If this {@link Try} is {@link Err}, return {@link Err} ({@code this}). + * The {@code Predicate} is not tested, and the mapper {@code Function} is not executed. + *

    + *

    + * If this {@link Try} is {@link OK}, return {@link OK} ({@code this}) if the {@code Predicate} matches. + * If the {@code Predicate} fails to match, return an {@link Err} {@link Try} produced by applying the + * mapping function to the current {@link Try} ({@code this}). + *

    + * + * @param predicate the predicate used to test {@link OK} values. + * @param mapper the mapping function for {@link OK} values that do not match the predicate. + * @return a {@link Try} based on the algorithm described above. + * @throws NullPointerException if the called mapping function returns {@code null}. + */ + @NotNull Try filter(@NotNull Predicate predicate, @NotNull Function mapper); + + /** + * Executes the action iff this is an {@link OK} {@link Try}. + * + * @return {@code this} + * @throws NullPointerException if the called action returns {@code null}. + * @see #matchErr(Consumer) + * @see #biMatch(Consumer, Consumer) + */ + @NotNull Try match(@NotNull Consumer okConsumer); + + + /** + * Executes the given consumer if this is a {@link OK}. This is a terminal operation. + * + * @param okConsumer the consumer function to be executed + */ + default void consume(@NotNull Consumer okConsumer) { + match(okConsumer); + } + + /** + * If this is an {@link OK}, return a new {@link OK} value produced by the given mapping function. + * Otherwise, return the {@link Err} value. + *

    + * The type of the produced {@link OK} can be different. The mapping function is only invoked for + * {@link OK} values. + *

    + *

    + * This is equivalent to {@code map( Function.identity(), rightMapper )}. + *

    + * + * @param okMapper the mapping function producing a new {@link OK} value. + * @return a new {@link OK} produced by the mapping function if this is {@link OK}; + * otherwise, returns an {@link Err}. + * @throws NullPointerException if the Try of the mapping function is {@code null} + * @see #mapErr(Function) + * @see #biMap(Function, Function) + */ + @NotNull Try map(@NotNull Function okMapper); + + /** + * If this is an {@link OK}, return the new {@link Try} supplied by the mapping function. + * Note that while the {@link Err} type must remain the same, the {@link OK} type returned + * can be different. + *

    + * This is also known as {@code join()} in other implementations. + *

    + *

    + * No mapping is performed if this is an {@link Err}, and the mapping function is not invoked. + *

    + * + * @param okMapper the mapping function that produces a new {@link Try} + * @return a new {@link OK} produced by the mapping function if this is {@link OK}; + * otherwise, returns an {@link Err}. + * @throws NullPointerException if the Try of the mapping function is {@code null} + * @see #biFlatMap(Function, Function) + * @see #flatMapErr(Function) + */ + @NotNull Try flatMap(@NotNull Function> okMapper); + + /** + * Determines if this {@link OK} {@link Try} matches the given {@link Predicate}. + *

    + * The {@link Predicate} is not invoked if this is an {@link Err} {@link Try} + *

    + * + * @param okPredicate the {@link Predicate} to test + * @return {@code true} iff this is an {@link OK} {@link Try} and the {@link Predicate} matches. + * @see #contains(Object) + * @see #ifPredicateErr(Predicate) + * @see #containsErr(Exception) + */ + boolean ifPredicate(@NotNull Predicate okPredicate); + + /** + * Determines if this {@link OK} {@link Try} contains the given value. + *

    + * This will always return {@code false} for {@code null} values. + *

    + * + * @param okValue value to compare + * @return {@code true} iff this is an {@link OK} {@link Try} and the contained value equals {@code okValue} + * @see #ifPredicate(Predicate) + * @see #containsErr(Exception) + * @see #ifPredicateErr(Predicate) + */ + boolean contains(@Nullable V okValue); + + /** + * If this {@link Try} is {@link Err}, return {@code rightAlternate}. + * Otherwise, return {@code this} (an {@link OK} {@link Try}). + * + * @param okAlternate alternate {@link OK} {@link Try} + * @return {@code this}, or {@code okAlternate} if {@code this} is {@link Err} + * @see #orElse(Supplier) + * @see #orElseErr(Exception) + * @see #orElseErr(Supplier) + */ + @NotNull V orElse(@NotNull V okAlternate); + + /** + * If this {@link Try} is {@link Err}, return the supplied {@link OK} {@link Try}. + * Otherwise, return {@code this} (an {@link OK} {@link Try}) without + * invoking the {@link Supplier}. + * + * @param okSupplier supplier of {@link OK} {@link Try}s + * @return {@code this}, or the supplied {@link OK} {@link Try} if {@code this} is {@link Err} + * @see #orElse(Object) + * @see #orElseErr(Exception) + * @see #orElseErr(Supplier) + */ + @NotNull V orElse(@NotNull Supplier okSupplier); + + /** + * Recover from an error; ignore the {@link Err} value if present, + * and apply the mapping function to get an {@link OK}. + *

    + * If this is an {@link OK}, return it without applying the mapping function. + *

    + *

    + * This method is equivalent in alternative implementations to {@code orElseMap()}. + *

    + * + * @param fnE2V {@link Function} that produces an {@link OK} value. + * @return A {@link OK} value, either the current {@link OK} if present, or the produced {@link OK} if not. + * @throws NullPointerException if the Try of the mapping function is {@code null}. + * @see #forfeit(Function) + */ + @NotNull V recover(@NotNull Function fnE2V); + + /** + * Return a {@link Stream}, containing either a single {@link Err} value, or an empty {@link Stream} + * if this is an {@link OK} value. + * + * @see #stream() + */ + @NotNull Stream streamErr(); + + /** + * Executes the action iff this is an {@link Err} {@link Try}. + * + * @return {@code this} + * @throws NullPointerException if the called action returns {@code null}. + * @see #match(Consumer) + * @see #biMatch(Consumer, Consumer) + */ + @NotNull Try matchErr(@NotNull Consumer errConsumer); + + /** + * If this is an {@link Err}, return a new {@link Err} value produced by the given mapping function. + * Otherwise, return the {@link OK} value. + *

    + * The type of the produced {@link Err} can be different. The mapping function is only invoked for + * {@link Err} values. + *

    + *

    + * This is equivalent to {@code map( leftMapper, Function.identity() )}. + *

    + * + * @param errMapper the mapping function producing a new {@link Err} value. + * @return a new {@link Err} produced by the mapping function if this is {@link Err}; + * otherwise, returns an {@link OK}. + * @throws NullPointerException if the Try of the mapping function is {@code null} + * @see #map(Function) + * @see #biMap(Function, Function) + */ + @NotNull Try mapErr(@NotNull Function errMapper); + + /** + * If this is an {@link Err}, return the new {@link Try} supplied by the mapping function. + * Note that while the {@link OK} type must remain the same, the {@link Err} type returned + * can be different. + *

    + * This is also known as a left-{@code join()} in other implementations. + *

    + *

    + * No mapping is performed if this is an {@link OK}, and the mapping function is not invoked. + *

    + * + * @param errMapper the mapping function that produces a new {@link Try} + * @return a new {@link Err} produced by the mapping function if this is {@link Err}; + * otherwise, returns an {@link OK}. + * @throws NullPointerException if the Try of the mapping function is {@code null} + * @see #biFlatMap(Function, Function) + * @see #flatMap(Function) + */ + @NotNull Try flatMapErr(@NotNull Function> errMapper); + + /** + * Determines if this {@link Err} {@link Try} matches the given {@link Predicate}. + *

    + * The {@link Predicate} is not invoked if this is an {@link OK} {@link Try} + *

    + * + * @param errPredicate the {@link Predicate} to test + * @return {@code true} iff this is an {@link Err} {@link Try} and the {@link Predicate} matches. + * @see #containsErr(Exception) (Object) + * @see #contains(Object) + * @see #ifPredicate(Predicate) + */ + boolean ifPredicateErr(@NotNull Predicate errPredicate); + + /** + * Determines if this {@link Err} {@link Try} contains the given value. + *

    + * This will always return {@code false} for {@code null} values. + *

    + * + * @param errValue value to compare + * @return {@code true} iff this is an {@link Err} {@link Try} and the contained value equals {@code errValue} + * @see #ifPredicateErr + * @see #ifPredicate + * @see #contains + */ + boolean containsErr(@NotNull E errValue); + + /** + * If this {@link Try} is an {@link OK}, return {@code errAlternate}. + * Otherwise, return {@code this} (an {@link Err} {@link Try}). + * + * @param errAlternate alternate {@link Err} {@link Try} + * @return {@code this}, or {@code leftAlternate} if {@code this} is an {@link OK} + * @see #orElse(Object) + * @see #orElse(Supplier) + */ + @NotNull E orElseErr(@NotNull E errAlternate); + + /** + * If this {@link Try} is an {@link OK}, return the supplied {@link Err} {@link Try}. + * Otherwise, return {@code this} (an {@link Err} {@link Try}) without + * invoking the {@link Supplier}. + * + * @param errSupplier supplier of {@link Err} {@link Try}s + * @return {@code this}, or the supplied {@link Err} {@link Try} if {@code this} is {@link OK} + * @see #orElse(Object) + * @see #orElseErr(Supplier) + */ + @NotNull E orElseErr(@NotNull Supplier errSupplier); + + /** + * Forfeit (ignore) the {@link OK} value if present, and apply the mapping function to get an {@link Err}. + *

    + * If this is an {@link Err}, return it without applying the mapping function. + *

    + *

    + * This method is equivalent in alternative implementations to {@code orElseMapErr()}. + *

    + * + * @param fnV2E {@link Function} that produces an {@link Err} value. + * @return A {@link Err} value, either the current {@link Err} if present, or the produced {@link Err} if not. + * @throws NullPointerException if the Try of the mapping function is {@code null}. + * @see #recover(Function) + */ + @NotNull E forfeit(@NotNull Function fnV2E); + + /** + * If {@code this} is {@link Err}, return it. Otherwise, return the next {@link Try} given. + * The next {@link Try} can have a different parameterized {@link OK} type. + * + * @param nextTry The {@link Try} to return. + * @see #and(Supplier) + * @see #or(Try) + * @see #or(Supplier) + */ + @NotNull Try and(@NotNull Try nextTry); + + /** + * If {@code this} is {@link Err}, return it (without invoking the {@link Supplier}). + * Otherwise, return the next {@link Try} supplied. + * The next {@link Try} can have a different parameterized {@link OK} type. + * + * @param nextTrySupplier The supplier of a {@link Try} to return; only called if {@code this} is {@link OK}. + * @throws NullPointerException if the supplied {@link Try} is {@code null}. + * @see #and(Try) + * @see #or(Try) + * @see #or(Supplier) + */ + @NotNull Try and(@NotNull Supplier> nextTrySupplier); + + /** + * If {@code this} is {@link OK}, return it. + * Otherwise, return the next {@link Try} given. + * The next {@link Try} can have a different parameterized {@link Err} type. + * + * @param nextTry The {@link Try} to return. + * @see #or(Supplier) + * @see #and(Try) + * @see #and(Supplier) + */ + @NotNull Try or(@NotNull Try nextTry); + + /** + * If {@code this} is {@link OK}, return it (without invoking the {@link Supplier}). + * Otherwise, return the next {@link Try} supplied. + * The next {@link Try} can have a different parameterized {@link Err} type. + * + * @param nextTrySupplier The supplier of a {@link Try} to return; only called if {@code this} is {@link Err}. + * @throws NullPointerException if the supplier is called and returns {@code null}. + * @see #or(Try) + * @see #and(Try) + * @see #and(Supplier) + */ + @NotNull Try or(@NotNull Supplier> nextTrySupplier); + + /** + * Expect success (an {@link OK} value), otherwise throw a runtime Exception. + *

    + * This always will throw for {@link Err}. If {@link Err} is a checked exception, this will + * throw a checked Exception. + *

    + * This is similar to {@code getOrThrow()}; a value is expected or an exception is thrown. + * + * @return Value (if {@link OK})) + * @throws E ({@link Exception} held by {@link Err}). + * @see #getOrThrow(Function) + */ + @NotNull V expect() throws E; + + /** + * Throw the given supplied {@link Exception} (or more precisely, {@link Throwable}). + *

    + * This method can wrap an {@link Err} type if that {@link Err} type is allowed by the constructor. + * This can also convert Exceptions; e.g., using an {@link java.io.UncheckedIOException} to wrap + * an {@link java.io.IOException} via {@code orThrow(UncheckedIOException::new)}. + *

    + * + * @param exFn Exception producing function + * @param Throwable (Exception) created by {@code exFn} + * @return Value V + * @throws X Throwable + * @see #expect() + */ + @NotNull V getOrThrow(@NotNull Function exFn) throws X; + + /** + * Represents a successful result with a value of type V. + * Implements the Try interface. + * + * @param the type of the value in the result + * @param the type of the exception that may occur + */ + record OK(V value) implements Try { + /** + * Checks if the given value is non-null. + * + * @param value the value to be checked + * @throws NullPointerException if the value is null + */ + public OK { + requireNonNull( value, "OK: cannot be null!" ); + } + + /** + * Get the OK value V + * + * @return V + */ + public @NotNull V get() { + return value; + } + + + @Override + public @NotNull Optional ok() { + return Optional.of( value ); + } + + @Override + public @NotNull Optional err() { + return Optional.empty(); + } + + @Override + public @NotNull Try biMatch(@NotNull Consumer okConsumer, @NotNull Consumer errConsumer) { + requireNonNull( okConsumer ); + requireNonNull( errConsumer ); + okConsumer.accept( value ); + return this; + } + + @Override + public @NotNull Try biMap(@NotNull Function okMapper, @NotNull Function errMapper) { + requireNonNull( okMapper ); + requireNonNull( errMapper ); + return new OK<>( okMapper.apply( value ) ); + } + + @SuppressWarnings("unchecked") + @Override + public @NotNull Try biFlatMap(@NotNull Function> okMapper, @NotNull Function> errMapper) { + requireNonNull( okMapper ); + requireNonNull( errMapper ); + return (Try) requireNonNull( okMapper.apply( value )); + } + + @Override + public @NotNull T fold(@NotNull Function fnOK, @NotNull Function fnErr) { + requireNonNull( fnOK ); + requireNonNull( fnErr ); + return requireNonNull( fnOK.apply( value ) ); + } + + @Override + public @NotNull Stream stream() { + return Stream.of( value ); + } + + + @Override + public @NotNull Try filter(@NotNull Predicate predicate, @NotNull Function mapper) { + requireNonNull( predicate ); + requireNonNull( mapper ); + if (predicate.test( value )) { + return this; + } + return new Err<>( mapper.apply( value ) ); + } + + @Override + public @NotNull Try match(@NotNull Consumer okConsumer) { + requireNonNull( okConsumer ); + okConsumer.accept( value ); + return this; + } + + @Override + public @NotNull Try map(@NotNull Function okMapper) { + requireNonNull( okMapper ); + return new OK<>( okMapper.apply( value ) ); + } + + @SuppressWarnings("unchecked") + @Override + public @NotNull Try flatMap(@NotNull Function> okMapper) { + requireNonNull( okMapper ); + return (Try) requireNonNull( okMapper.apply( value ) ); + } + + @Override + public boolean ifPredicate(@NotNull Predicate okPredicate) { + requireNonNull( okPredicate ); + return okPredicate.test( value ); + } + + @Override + public boolean contains(@Nullable V okValue) { + return Objects.equals( value, okValue ); + } + + + @Override + public @NotNull V orElse(@NotNull V okAlternate) { + requireNonNull( okAlternate ); + return value; + } + + @Override + public @NotNull V orElse(@NotNull Supplier okSupplier) { + requireNonNull( okSupplier ); + return value; + } + + @Override + public @NotNull V recover(@NotNull Function fnE2V) { + requireNonNull( fnE2V ); + return value; + } + + @Override + public @NotNull Stream streamErr() { + return Stream.empty(); + } + + + @Override + public @NotNull Try matchErr(@NotNull Consumer errConsumer) { + requireNonNull( errConsumer ); + return this; + } + + @Override + public @NotNull Try mapErr(@NotNull Function errMapper) { + requireNonNull( errMapper ); + return coerce(); + } + + @Override + public @NotNull Try flatMapErr(@NotNull Function> errMapper) { + requireNonNull( errMapper ); + return coerce(); + } + + @Override + public boolean ifPredicateErr(@NotNull Predicate errPredicate) { + requireNonNull( errPredicate ); + return false; + } + + @Override + public boolean containsErr(@NotNull E errValue) { + requireNonNull( errValue ); + return false; + } + + @Override + public @NotNull E orElseErr(@NotNull E errAlternate) { + requireNonNull( errAlternate ); + return errAlternate; + } + + @Override + public @NotNull E orElseErr(@NotNull Supplier errSupplier) { + requireNonNull( errSupplier ); + return requireNonNull( errSupplier.get() ); + } + + @Override + public @NotNull E forfeit(@NotNull Function fnV2E) { + requireNonNull( fnV2E ); + return requireNonNull( fnV2E.apply( value ) ); + } + + @Override + public @NotNull Try and(@NotNull Try nextTry) { + requireNonNull( nextTry ); + return nextTry; + } + + @Override + public @NotNull Try and(@NotNull Supplier> nextTrySupplier) { + requireNonNull( nextTrySupplier ); + return requireNonNull( nextTrySupplier.get() ); + } + + @Override + public @NotNull Try or(@NotNull Try nextTry) { + requireNonNull( nextTry ); + return coerce(); + } + + @Override + public @NotNull Try or(@NotNull Supplier> nextTrySupplier) { + requireNonNull( nextTrySupplier ); + return coerce(); + } + + @Override + public @NotNull V expect() throws E { + return value; + } + + @Override + public @NotNull V getOrThrow(@NotNull Function exFn) throws X { + return value; + } + + // coerce empty exception to new type + @SuppressWarnings("unchecked") + private OK coerce() { + return (OK) this; + } + + } + + + /** + * The Err class represents the error result of a Try. + * It holds an exception object. + * + * @param the type of the success value + * @param the type of the exception + */ + record Err(E error) implements Try { + /** + * Checks if the specified error is not null. + * + * @param error the error object to check + * @throws NullPointerException if the error is null + */ + public Err { + requireNonNull( error, "Err: cannot be null!" ); + } + + /** + * Get the Err value E + * + * @return E + */ + public @NotNull E get() { + return error; + } + + + @Override + public @NotNull Optional ok() { + return Optional.empty(); + } + + @Override + public @NotNull Optional err() { + return Optional.of( error ); + } + + + @Override + public @NotNull Try biMatch(@NotNull Consumer okConsumer, @NotNull Consumer errConsumer) { + requireNonNull( okConsumer ); + requireNonNull( errConsumer ); + errConsumer.accept( error ); + return this; + } + + @Override + public @NotNull Try biMap(@NotNull Function okMapper, @NotNull Function errMapper) { + requireNonNull( okMapper ); + requireNonNull( errMapper ); + return new Err<>( errMapper.apply( error ) ); + } + + @SuppressWarnings("unchecked") + @Override + public @NotNull Try biFlatMap(@NotNull Function> okMapper, @NotNull Function> errMapper) { + requireNonNull( okMapper ); + requireNonNull( errMapper ); + return (Try) requireNonNull( errMapper.apply( error ) ); + } + + @Override + public @NotNull T fold(@NotNull Function fnOK, @NotNull Function fnErr) { + requireNonNull( fnOK ); + requireNonNull( fnErr ); + return requireNonNull( fnErr.apply( error ) ); + } + + @Override + public @NotNull Stream stream() { + return Stream.empty(); + } + + @Override + public @NotNull Try filter(@NotNull Predicate predicate, @NotNull Function mapper) { + requireNonNull( predicate ); + requireNonNull( mapper ); + return this; + } + + @Override + public @NotNull Try match(@NotNull Consumer okConsumer) { + requireNonNull( okConsumer ); + return this; + } + + @Override + public @NotNull Try map(@NotNull Function okMapper) { + requireNonNull( okMapper ); + return coerce(); + } + + @Override + public @NotNull Try flatMap(@NotNull Function> okMapper) { + requireNonNull( okMapper ); + return coerce(); + } + + @Override + public boolean ifPredicate(@NotNull Predicate okPredicate) { + requireNonNull( okPredicate ); + return false; + } + + @Override + public boolean contains(@Nullable V okValue) { + requireNonNull( okValue ); + return false; + } + + @Override + public @NotNull V orElse(@NotNull V okAlternate) { + requireNonNull( okAlternate ); + return okAlternate; + } + + @Override + public @NotNull V orElse(@NotNull Supplier okSupplier) { + requireNonNull( okSupplier ); + return requireNonNull( okSupplier.get() ); + } + + @Override + public @NotNull V recover(@NotNull Function fnE2V) { + requireNonNull( fnE2V ); + return requireNonNull( fnE2V.apply( error ) ); + } + + @Override + public @NotNull Stream streamErr() { + return Stream.of( error ); + } + + @Override + public @NotNull Try matchErr(@NotNull Consumer errConsumer) { + requireNonNull( errConsumer ); + errConsumer.accept( error ); + return this; + } + + @Override + public @NotNull Try mapErr(@NotNull Function errMapper) { + requireNonNull( errMapper ); + return new Err<>( errMapper.apply( error ) ); + } + + @SuppressWarnings("unchecked") + @Override + public @NotNull Try flatMapErr(@NotNull Function> errMapper) { + requireNonNull( errMapper ); + return (Try) requireNonNull( errMapper.apply( error ) ); + } + + @Override + public boolean ifPredicateErr(@NotNull Predicate errPredicate) { + requireNonNull( errPredicate ); + return errPredicate.test( error ); + } + + @Override + public boolean containsErr(@Nullable E errValue) { + requireNonNull( errValue ); + return Objects.equals( error, errValue ); + } + + @Override + public @NotNull E orElseErr(@NotNull E errAlternate) { + requireNonNull( errAlternate ); + return error; + } + + @Override + public @NotNull E orElseErr(@NotNull Supplier errSupplier) { + requireNonNull( errSupplier ); + return error; + } + + @Override + public @NotNull E forfeit(@NotNull Function fnV2E) { + requireNonNull( fnV2E ); + return error; + } + + @Override + public @NotNull Try and(@NotNull Try nextResult) { + requireNonNull( nextResult ); + return coerce(); + } + + @Override + public @NotNull Try and(@NotNull Supplier> nextResultSupplier) { + requireNonNull( nextResultSupplier ); + return coerce(); + } + + @Override + public @NotNull Try or(@NotNull Try nextResult) { + requireNonNull( nextResult ); + return nextResult; + } + + @Override + public @NotNull Try or(@NotNull Supplier> nextResultSupplier) { + requireNonNull( nextResultSupplier ); + return requireNonNull( nextResultSupplier.get() ); + } + + + @Override + public @NotNull V expect() throws E { + throw error; + } + + @Override + public @NotNull V getOrThrow(@NotNull Function exFn) throws X { + requireNonNull( exFn ); + throw requireNonNull( exFn.apply( error ) ); + } + + // coerce empty value to new type + @SuppressWarnings("unchecked") + private Err coerce() { + return (Err) this; + } + } + + +} diff --git a/src/main/java/net/xyzsd/dichotomy/trying/Try2.java b/src/main/java/net/xyzsd/dichotomy/trying/Try2.java deleted file mode 100644 index 31570e9..0000000 --- a/src/main/java/net/xyzsd/dichotomy/trying/Try2.java +++ /dev/null @@ -1,972 +0,0 @@ -package net.xyzsd.dichotomy.trying; - - -import net.xyzsd.dichotomy.None; -import net.xyzsd.dichotomy.trying.function.ExFunction; -import net.xyzsd.dichotomy.trying.function.ExSupplier; -import net.xyzsd.dichotomy.trying.function.SpecExSupplier; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.Objects; -import java.util.function.Consumer; -import java.util.function.Function; -import java.util.function.Predicate; -import java.util.function.Supplier; -import java.util.stream.Stream; - -import static java.util.Objects.requireNonNull; - -/* - todo: need common 'none' class with Result, and ofOK() without argument to create a null - - Try2 as exception - NOTE: cannot have a swap() in this case - - TODO: ... consider renaming - ? XResult? ExResult ? ResultX - - */ -public sealed interface Try2 { - - - - record OK(V value) implements Try2 { - public OK { - requireNonNull( value, "OK: cannot be null!" ); - } - - /** - * Get the OK value V - * - * @return V - */ - public @NotNull V get() { - return value; - } - - // coerce empty exception to new type - @SuppressWarnings("unchecked") - private OK coerceErr() { - return (OK) this; - } - - } - - record Err(E error) implements Try2 { - public Err { - requireNonNull( error, "Err: cannot be null!" ); - } - - /** - * Get the Err value E - * - * @return E - */ - public @NotNull E get() { - return error; - } - - // coerce empty OK value to new type - @SuppressWarnings("unchecked") - private Err coerceOK() { - return (Err) this; - } - - } - - - /** - * Create an {@link OK} (Success) {@link Try2} for the given non-null value. - * - * @param value value for success - * @param Value type - * @param Error Exception type - * @return {@link OK} {@link Try2} containing the given value. - */ - @NotNull - static Try2 ofOK(@NotNull V value) { - return new OK<>( value ); - } - - /** - * Create an empty {@link OK} (Success) {@link Try2}. - *

    - * All empty values use the {@link None} type. - *

    - * - * @param Error value - * @return {@link OK} {@link Try2} containing the given value. - */ - @NotNull - static Try2 ofOK() { - return new OK( new None() ); - } - - /** - * Create an {@link Err} (Failure) {@link Try2} for the given non-null value. - * - * @param error Error - * @param Value type - * @param Error Exception type - * @return {@link Err} containing the given error Exception. - */ - @NotNull - static Try2 ofErr(@NotNull E error) { - return new Err<>( error ); - } - - - - // TODO: document and test all this - - - @NotNull - static Try2 of(@NotNull Supplier supplier, @NotNull Class exClass) { - requireNonNull( supplier ); - - try { - return new OK<>( supplier.get() ); - } catch (Throwable t) { - if (exClass.isInstance( t )) { - return new Err(exClass.cast( t ) ); - } - throw t; - } - } - - - // ANY runtime exception. standard Suppliers Can't throw checked exceptions (no throws clause defined), so, not included - @NotNull - static Try2 of(@NotNull Supplier supplier) { - requireNonNull( supplier ); - - try { - return new OK<>( supplier.get() ); - } catch (RuntimeException ex) { - return new Err<>( ex ); - } - } - - - - - - /** - * Get a {@link Try2} from an {@link ExSupplier}. - *

    - * The {@link ExSupplier} may throw a checked or unchecked {@link Exception}. - * The result of the function, or {@link Exception} will be contained in the {@link Try2}. - *

    - * - * @param exSupplier Supplier to invoke - * @param type of result supplied - * @return A {@link Try2} containing the supplied value or an {@link Exception} - * @see #from(ExSupplier) - * @see #of(Supplier) - * @see #from(Supplier) - */ - @NotNull - public static Try2 of(@NotNull ExSupplier exSupplier) { - requireNonNull( exSupplier ); - - try { - return new Try2.OK<>( exSupplier.get() ); - } catch (Exception ex) { - return new Try2.Err<>( ex ); - } - } - - /** - * Converts an {@link Supplier} which may throw {@link RuntimeException}s to a {@link Supplier} of {@link Try2}s. - * - * @param supplier Supplier to invoke - * @param type of result supplied - * @return A {@link Try2} containing the supplied value or a {@link RuntimeException} - * @see #of(Supplier) - * @see #of(ExSupplier) - * @see #from(ExSupplier) - */ - @NotNull - public static Supplier> from(@NotNull Supplier supplier) { - requireNonNull( supplier ); - return () -> of( supplier ); - } - - - /** - * Converts an {@link ExSupplier} to a {@link Supplier} of {@link Try2}s. - *

    - * The {@link ExSupplier} may throw a checked or unchecked {@link Exception}, - * which will be wrapped in an {@link Err} {@link Try2}. - *

    - * - * @param exSupplier Supplier to invoke - * @param type of result supplied - * @return A {@link Try2} containing the supplied value or an {@link Exception} - */ - @NotNull - public static Supplier> from(@NotNull ExSupplier exSupplier) { - requireNonNull( exSupplier ); - return () -> of( exSupplier ); - } - - - /** - * Converts a {@link SpecExSupplier} to a {@link Supplier} of {@link Try2}s. - *

    - * The returned {@link Supplier} will only wrap {@link Exception}s of the given class - * parameter {@code } (and subclasses); all other {@link RuntimeException}s will be thrown. - *

    - * - * @param exSupplier {@link SpecExSupplier} to wrap - * @param value wrapped by the given {@link Try2}. - * @param {@link RuntimeException} wrapped by the given {@link Try2}. - * @return A {@link Supplier} of {@link Try2}s containing the supplied value or the specified - * {@link RuntimeException}{@code } - * @see #of(SpecExSupplier, Class) - * @see #from(ExSupplier) - * @see #from(Supplier) - */ - @NotNull - public static Supplier> from(@NotNull SpecExSupplier exSupplier, final Class exClass) { - requireNonNull( exSupplier ); - return () -> of( exSupplier, exClass ); - } - - - /** - * Execute a {@link SpecExSupplier} to a supply a {@link Try2}. - *

    - * The returned {@link Try2} will contain the supplied value or the {@link RuntimeException} of the given class - * parameter {@code } (and subclasses); all other {@link RuntimeException}s will be thrown. - *

    - * - * @param exSupplier {@link SpecExSupplier} to wrap - * @param supplied value wrapped by a {@link Try2}. - * @param {@link RuntimeException} type wrapped by a {@link Try2}. - * @return A {@link Try2} containing the supplied value or the specified {@link RuntimeException} - * @see #from(SpecExSupplier, Class) - */ - @NotNull - public static Try2 of(@NotNull SpecExSupplier exSupplier, final Class exClass) { - requireNonNull( exSupplier ); - requireNonNull( exClass ); - - try { - return new Try2.OK<>( exSupplier.get() ); - } catch (RuntimeException ex) { - if (exClass.isAssignableFrom( ex.getClass() )) { - return new Try2.Err<>( exClass.cast( ex ) ); - } - throw ex; - } - } - - - /** - * Apply the given {@link ExFunction}, returning a {@link Try2} which wraps either the return value - * of the {@link ExFunction} or an {@link Exception}. - * - * @param in value applied - * @param exFn function, which may throw an exception - * @return A {@link Try2} wrapping either the output value or an {@link Exception}. - * @param function input - * @param function output - * - * @see #from(ExFunction) - */ - @NotNull - public static Try2 of(@Nullable T in, @NotNull final ExFunction exFn) { - requireNonNull( exFn ); - return fnToResult( in, exFn ); - } - - /** - * Given an {@link Exception}-producing function ({@link ExFunction}), returns a {@link Function} which - * returns a {@link Try2} wrapping the function output or {@link Exception}. This is lazy; the function - * is not evaluated. - * - * @param exFn {@link ExFunction} which may generate an exception - * @return {@link Function} returning {@link Try2}s which wrap the output or {@link Exception} - * @param function input - * @param function output - * - * @see #of(Object, ExFunction) - */ - @NotNull - public static Function> from(@NotNull final ExFunction exFn) { - requireNonNull( exFn ); - return (in) -> fnToResult( in, exFn ); - } - - - - private static Try2 fnToResult(@Nullable T in, @NotNull final ExFunction exFn) { - try { - return new Try2.OK<>( exFn.apply( in ) ); - } catch (Exception ex) { - return new Try2.Err<>( ex ); - } - } - - - - - - - - - - - - - - /** - * Executes the action for the {@link OK} or {@link Err} depending upon - * the value of this {@link Try2}. - * - * @return {@code this} - * @throws NullPointerException if the called action returns {@code null}. - * @see #match(Consumer) - * @see #matchErr(Consumer) - */ - @NotNull - default Try2 biMatch(@NotNull Consumer okConsumer, @NotNull Consumer errConsumer) { - requireNonNull( okConsumer ); - requireNonNull( errConsumer ); - - switch (this) { - case OK(V v) -> okConsumer.accept( v ); - case Err(E e) -> errConsumer.accept( e ); - } - - return this; - } - - - /** - * Returns a new {@link Try2}, the value of which is determined by the appropriate mapping function. - *

    - * The returned {@link Try2} (which may be {@link OK} or {@link Err}) can have different types. - *

    - * - * @param okMapper the mapping function for {@link OK} values. - * @param errMapper the mapping function for {@link Err} values. - * @return the {@link Try2} produced from {@code okMapper} or {@code errMapper} - * @throws NullPointerException if the called function returns {@code null}. - * @see #map(Function) - * @see #mapErr(Function) - */ - @NotNull - default Try2 biMap(@NotNull Function okMapper, - @NotNull Function errMapper) { - requireNonNull( okMapper ); - requireNonNull( errMapper ); - - return switch (this) { - case OK(V v) -> new OK<>( okMapper.apply( v ) ); - case Err(E e) -> new Err<>( errMapper.apply( e ) ); - }; - } - - - /** - * Returns a {@link Try2}, produced from one of the appropriate mapping functions. - *

    - * The produced {@link Try2} (which may be {@link Err} or {@link OK}) can have different types. - *

    - * - * @param fnOK the mapping function for {@link OK} values. - * @param fnErr the mapping function for {@link Err} values. - * @return the {@link Try2} produced from {@code fnOK} or {@code fnErr} - * @throws NullPointerException if the called function returns {@code null}. - * @see #map(Function) - * @see #mapErr(Function) (Function) - */ - @SuppressWarnings("unchecked") - @NotNull - default Try2 biFlatMap(@NotNull Function> fnOK, - @NotNull Function> fnErr) { - requireNonNull( fnOK ); - requireNonNull( fnErr ); - - return (Try2) switch (this) { - case OK(V v) -> requireNonNull( fnOK.apply( v ) ); - case Err(E e) -> requireNonNull( fnErr.apply( e ) ); - }; - } - - - /** - * Returns a value, produced from one of the appropriate mapping functions. - *

    - * The produced value can have any type (except {@link Void}) but mapping functions for - * both {@link Err} and {@link OK} types must produce the same value type. - *

    - *

    - * If no value is to be returned, use {@link #biMatch(Consumer, Consumer)} instead. - *

    - * - * @param fnOK the mapping function for {@link Err} values. - * @param fnErr the mapping function for {@link OK} values. - * @return the value produced from {@code fnOK} or {@code fnErr} - * @throws NullPointerException if the called function returns {@code null}. - * @see #recover(Function) - * @see #forfeit(Function) - */ - @NotNull - default T fold(@NotNull Function fnOK, @NotNull Function fnErr) { - requireNonNull( fnOK ); - requireNonNull( fnErr ); - - return switch (this) { - case OK(V v) -> requireNonNull( fnOK.apply( v ) ); - case Err(E e) -> requireNonNull( fnErr.apply( e ) ); - }; - } - - - /** - * Return a {@link Stream}, containing either a single {@link OK} value, or an empty {@link Stream} - * if this is an {@link Err} value. - * - * @see #streamErr() - */ - @NotNull - default Stream stream() { - return switch (this) { - case OK(V v) -> Stream.of( v ); - case Err(E e) -> Stream.empty(); - }; - } - - /** - * Filter a {@link Try2}. - *

    - * If this {@link Try2} is {@link Err}, return {@link Err} ({@code this}). - * The {@code Predicate} is not tested, and the mapper {@code Function} is not executed. - *

    - *

    - * If this {@link Try2} is {@link OK}, return {@link OK} ({@code this}) if the {@code Predicate} matches. - * If the {@code Predicate} fails to match, return an {@link Err} {@link Try2} produced by applying the - * mapping function to the current {@link Try2} ({@code this}). - *

    - * - * @param predicate the predicate used to test {@link OK} values. - * @param mapper the mapping function for {@link OK} values that do not match the predicate. - * @return a {@link Try2} based on the algorithm described above. - * @throws NullPointerException if the called mapping function returns {@code null}. - */ - @NotNull - default Try2 filter(@NotNull Predicate predicate, - @NotNull Function mapper) { - requireNonNull( predicate ); - requireNonNull( mapper ); - return switch (this) { - case OK ok -> { - if(predicate.test( ok.get() )) { - yield ok; - } else { - yield new Err<>( mapper.apply( ok.get() ) ); - } - } - case Err __ -> this; - }; - } - - - /** - * Executes the action iff this is an {@link OK} {@link Try2}. - * - * @return {@code this} - * @throws NullPointerException if the called action returns {@code null}. - * @see #matchErr(Consumer) - * @see #biMatch(Consumer, Consumer) - */ - @NotNull - default Try2 match(@NotNull Consumer okConsumer) { - requireNonNull( okConsumer ); - if (this instanceof OK(V v)) { - okConsumer.accept( v ); - } - return this; - } - - /** - * If this is an {@link OK}, return a new {@link OK} value produced by the given mapping function. - * Otherwise, return the {@link Err} value. - *

    - * The type of the produced {@link OK} can be different. The mapping function is only invoked for - * {@link OK} values. - *

    - *

    - * This is equivalent to {@code map( Function.identity(), rightMapper )}. - *

    - * - * @param okMapper the mapping function producing a new {@link OK} value. - * @return a new {@link OK} produced by the mapping function if this is {@link OK}; - * otherwise, returns an {@link Err}. - * @throws NullPointerException if the Try2 of the mapping function is {@code null} - * @see #mapErr(Function) - * @see #biMap(Function, Function) - */ - @NotNull - default Try2 map(@NotNull Function okMapper) { - requireNonNull( okMapper ); - - return switch (this) { - case OK(V v) -> new OK<>( okMapper.apply( v ) ); - case Err err -> err.coerceOK(); - }; - } - - /** - * If this is an {@link OK}, return the new {@link Try2} supplied by the mapping function. - * Note that while the {@link Err} type must remain the same, the {@link OK} type returned - * can be different. - *

    - * This is also known as {@code join()} in other implementations. - *

    - *

    - * No mapping is performed if this is an {@link Err}, and the mapping function is not invoked. - *

    - * - * @param okMapper the mapping function that produces a new {@link Try2} - * @return a new {@link OK} produced by the mapping function if this is {@link OK}; - * otherwise, returns an {@link Err}. - * @throws NullPointerException if the Try2 of the mapping function is {@code null} - * @see #biFlatMap(Function, Function) - * @see #flatMapErr(Function) - */ - @SuppressWarnings("unchecked") - @NotNull - default Try2 flatMap(@NotNull Function> okMapper) { - requireNonNull( okMapper ); - - return (Try2) switch (this) { - case OK(V v) -> requireNonNull( okMapper.apply( v ) ); - case Err err -> err.coerceOK(); - }; - } - - /** - * Determines if this {@link OK} {@link Try2} matches the given {@link Predicate}. - *

    - * The {@link Predicate} is not invoked if this is an {@link Err} {@link Try2} - *

    - * - * @param okPredicate the {@link Predicate} to test - * @return {@code true} iff this is an {@link OK} {@link Try2} and the {@link Predicate} matches. - * @see #contains(Object) - * @see #matchesErr(Predicate) - * @see #containsErr(Exception) - */ - default boolean matches(@NotNull Predicate okPredicate) { - requireNonNull( okPredicate ); - return switch (this) { - case OK(V v) -> okPredicate.test( v ); - case Err __ -> false; - }; - } - - /** - * Determines if this {@link OK} {@link Try2} contains the given value. - *

    - * This will always return {@code false} for {@code null} values. - *

    - * - * @param okValue value to compare - * @return {@code true} iff this is an {@link OK} {@link Try2} and the contained value equals {@code okValue} - * @see #matches(Predicate) - * @see #containsErr(Exception) - * @see #matchesErr(Predicate) - */ - default boolean contains(@Nullable V okValue) { - requireNonNull( okValue ); - return switch (this) { - case OK(V v) -> Objects.equals( v, okValue ); - case Err __ -> false; - }; - } - - /** - * If this {@link Try2} is {@link Err}, return {@code rightAlternate}. - * Otherwise, return {@code this} (an {@link OK} {@link Try2}). - * - * @param okAlternate alternate {@link OK} {@link Try2} - * @return {@code this}, or {@code okAlternate} if {@code this} is {@link Err} - * @see #orElseGet(Supplier) - * @see #orElseErr(Exception) - * @see #orElseGetErr(Supplier) - */ - @NotNull - default V orElse(@NotNull V okAlternate) { - requireNonNull( okAlternate ); - return switch (this) { - case OK(V v) -> v; - case Err __ -> okAlternate; - }; - } - - /** - * If this {@link Try2} is {@link Err}, return the supplied {@link OK} {@link Try2}. - * Otherwise, return {@code this} (an {@link OK} {@link Try2}) without - * invoking the {@link Supplier}. - * - * @param okSupplier supplier of {@link OK} {@link Try2}s - * @return {@code this}, or the supplied {@link OK} {@link Try2} if {@code this} is {@link Err} - * @see #orElse(Object) - * @see #orElseErr(Exception) - * @see #orElseGetErr(Supplier) - */ - @NotNull - default V orElseGet(@NotNull Supplier okSupplier) { - requireNonNull( okSupplier ); - return switch (this) { - case OK(V v) -> v; - case Err __ -> requireNonNull( okSupplier.get() ); - }; - } - - /** - * Recover from an error; ignore the {@link Err} value if present, - * and apply the mapping function to get an {@link OK}. - *

    - * If this is an {@link OK}, return it without applying the mapping function. - *

    - *

    - * This method is equivalent in alternative implementations to {@code orElseMap()}. - *

    - * - * @param fnE2V {@link Function} that produces an {@link OK} value. - * @return A {@link OK} value, either the current {@link OK} if present, or the produced {@link OK} if not. - * @throws NullPointerException if the Try2 of the mapping function is {@code null}. - * @see #forfeit(Function) - */ - @NotNull - default V recover(@NotNull Function fnE2V) { - requireNonNull( fnE2V ); - - return switch (this) { - case OK(V v) -> v; - case Err(E e) -> requireNonNull( fnE2V.apply( e ) ); - }; - } - - - /** - * Return a {@link Stream}, containing either a single {@link Err} value, or an empty {@link Stream} - * if this is an {@link OK} value. - * - * @see #stream() - */ - @NotNull - default Stream streamErr() { - return switch (this) { - case OK(V v) -> Stream.empty(); - case Err(E e) -> Stream.of( e ); - }; - } - - /** - * Executes the action iff this is an {@link Err} {@link Try2}. - * - * @return {@code this} - * @throws NullPointerException if the called action returns {@code null}. - * @see #match(Consumer) - * @see #biMatch(Consumer, Consumer) - */ - @NotNull - default Try2 matchErr(@NotNull Consumer errConsumer) { - requireNonNull( errConsumer ); - if (this instanceof Err(E e)) { - errConsumer.accept( e ); - } - return this; - } - - - /** - * If this is an {@link Err}, return a new {@link Err} value produced by the given mapping function. - * Otherwise, return the {@link OK} value. - *

    - * The type of the produced {@link Err} can be different. The mapping function is only invoked for - * {@link Err} values. - *

    - *

    - * This is equivalent to {@code map( leftMapper, Function.identity() )}. - *

    - * - * @param errMapper the mapping function producing a new {@link Err} value. - * @return a new {@link Err} produced by the mapping function if this is {@link Err}; - * otherwise, returns an {@link OK}. - * @throws NullPointerException if the Try2 of the mapping function is {@code null} - * @see #map(Function) - * @see #biMap(Function, Function) - */ - @NotNull - default Try2 mapErr(@NotNull Function errMapper) { - requireNonNull( errMapper ); - return switch (this) { - case OK ok -> ok.coerceErr(); - case Err(E e) -> new Err<>( errMapper.apply( e ) ); - }; - } - - /** - * If this is an {@link Err}, return the new {@link Try2} supplied by the mapping function. - * Note that while the {@link OK} type must remain the same, the {@link Err} type returned - * can be different. - *

    - * This is also known as a left-{@code join()} in other implementations. - *

    - *

    - * No mapping is performed if this is an {@link OK}, and the mapping function is not invoked. - *

    - * - * @param errMapper the mapping function that produces a new {@link Try2} - * @return a new {@link Err} produced by the mapping function if this is {@link Err}; - * otherwise, returns an {@link OK}. - * @throws NullPointerException if the Try2 of the mapping function is {@code null} - * @see #biFlatMap(Function, Function) - * @see #flatMap(Function) - */ - @SuppressWarnings("unchecked") - @NotNull - default Try2 flatMapErr(@NotNull Function> errMapper) { - requireNonNull( errMapper ); - return switch (this) { - case OK ok -> ok.coerceErr(); - case Err(E e) -> (Try2) requireNonNull( errMapper.apply( e ) ); - }; - } - - - /** - * Determines if this {@link Err} {@link Try2} matches the given {@link Predicate}. - *

    - * The {@link Predicate} is not invoked if this is an {@link OK} {@link Try2} - *

    - * - * @param errPredicate the {@link Predicate} to test - * @return {@code true} iff this is an {@link Err} {@link Try2} and the {@link Predicate} matches. - * @see #containsErr(Exception) (Object) - * @see #contains(Object) - * @see #matches(Predicate) - */ - default boolean matchesErr(@NotNull Predicate errPredicate) { - requireNonNull( errPredicate ); - return switch (this) { - case OK __ -> false; - case Err(E e) -> errPredicate.test( e ); - }; - } - - /** - * Determines if this {@link Err} {@link Try2} contains the given value. - *

    - * This will always return {@code false} for {@code null} values. - *

    - * - * @param errValue value to compare - * @return {@code true} iff this is an {@link Err} {@link Try2} and the contained value equals {@code errValue} - * @see #matchesErr - * @see #matches - * @see #contains - */ - default boolean containsErr(@NotNull E errValue) { - requireNonNull( errValue ); - return switch (this) { - case OK __ -> false; - case Err(E e) -> Objects.equals( e, errValue ); - }; - } - - /** - * If this {@link Try2} is an {@link OK}, return {@code errAlternate}. - * Otherwise, return {@code this} (an {@link Err} {@link Try2}). - * - * @param errAlternate alternate {@link Err} {@link Try2} - * @return {@code this}, or {@code leftAlternate} if {@code this} is an {@link OK} - * @see #orElse(Object) - * @see #orElseGet(Supplier) - */ - @NotNull - default E orElseErr(@NotNull E errAlternate) { - requireNonNull( errAlternate ); - return switch (this) { - case OK __ -> errAlternate; - case Err(E e) -> e; - }; - } - - /** - * If this {@link Try2} is an {@link OK}, return the supplied {@link Err} {@link Try2}. - * Otherwise, return {@code this} (an {@link Err} {@link Try2}) without - * invoking the {@link Supplier}. - * - * @param errSupplier supplier of {@link Err} {@link Try2}s - * @return {@code this}, or the supplied {@link Err} {@link Try2} if {@code this} is {@link OK} - * @see #orElse(Object) - * @see #orElseGetErr(Supplier) - */ - @NotNull - default E orElseGetErr(@NotNull Supplier errSupplier) { - requireNonNull( errSupplier ); - return switch (this) { - case OK __ -> requireNonNull( errSupplier.get() ); - case Err(E e) -> e; - }; - } - - /** - * Forfeit (ignore) the {@link OK} value if present, and apply the mapping function to get an {@link Err}. - *

    - * If this is an {@link Err}, return it without applying the mapping function. - *

    - *

    - * This method is equivalent in alternative implementations to {@code orElseMapErr()}. - *

    - * - * @param fnV2E {@link Function} that produces an {@link Err} value. - * @return A {@link Err} value, either the current {@link Err} if present, or the produced {@link Err} if not. - * @throws NullPointerException if the Try2 of the mapping function is {@code null}. - * @see #recover(Function) - */ - @NotNull - default E forfeit(@NotNull Function fnV2E) { - requireNonNull( fnV2E ); - return switch (this) { - case OK(V v) -> requireNonNull( fnV2E.apply( v ) ); - case Err(E e) -> e; - }; - } - - - /** - * If {@code this} is {@link Err}, return it. Otherwise, return the next {@link Try2} given. - * The next {@link Try2} can have a different parameterized {@link OK} type. - * - * @param nextTry2 The {@link Try2} to return. - * @see #and(Supplier) - * @see #or(Try2) - * @see #or(Supplier) - */ - @NotNull - default Try2 and(@NotNull Try2 nextTry2) { - requireNonNull( nextTry2 ); - return switch (this) { - case OK __ -> nextTry2; - case Err err -> err.coerceOK(); - }; - } - - /** - * If {@code this} is {@link Err}, return it (without invoking the {@link Supplier}). - * Otherwise, return the next {@link Try2} supplied. - * The next {@link Try2} can have a different parameterized {@link OK} type. - * - * @param nextTry2Supplier The supplier of a {@link Try2} to return; only called if {@code this} is {@link OK}. - * @throws NullPointerException if the supplied {@link Try2} is {@code null}. - * @see #and(Try2) - * @see #or(Try2) - * @see #or(Supplier) - */ - @NotNull - default Try2 and(@NotNull Supplier> nextTry2Supplier) { - requireNonNull( nextTry2Supplier ); - return switch (this) { - case OK __ -> requireNonNull( nextTry2Supplier.get() ); - case Err err -> err.coerceOK(); - }; - } - - /** - * If {@code this} is {@link OK}, return it. - * Otherwise, return the next {@link Try2} given. - * The next {@link Try2} can have a different parameterized {@link Err} type. - * - * @param nextTry2 The {@link Try2} to return. - * @see #or(Supplier) - * @see #and(Try2) - * @see #and(Supplier) - */ - @NotNull - default Try2 or(@NotNull Try2 nextTry2) { - requireNonNull( nextTry2 ); - return switch (this) { - case OK ok -> ok.coerceErr(); - case Err __ -> nextTry2; - }; - } - - /** - * If {@code this} is {@link OK}, return it (without invoking the {@link Supplier}). - * Otherwise, return the next {@link Try2} supplied. - * The next {@link Try2} can have a different parameterized {@link Err} type. - * - * @param nextTry2Supplier The supplier of a {@link Try2} to return; only called if {@code this} is {@link Err}. - * @throws NullPointerException if the supplier is called and returns {@code null}. - * @see #or(Try2) - * @see #and(Try2) - * @see #and(Supplier) - */ - @NotNull - default Try2 or(@NotNull Supplier> nextTry2Supplier) { - requireNonNull( nextTry2Supplier ); - return switch (this) { - case OK ok -> ok.coerceErr(); - case Err __ -> requireNonNull( nextTry2Supplier.get() ); - }; - } - - - /** - * Expect success (an {@link OK} value), otherwise throw a runtime Exception. - *

    - * This always will throw for {@link Err}. If {@link Err} is a checked exception, this will - * throw a checked Exception. - *

    - * This is equivalent to {@code expect()}; a value is expected or an exception is thrown. - * - * @return Value (if {@link OK})) - * @throws E ({@link Exception} held by {@link Err}). - * @see #orThrow(Function) - */ - @NotNull - default V orThrow() throws E { - // this method is NOT called 'expect()' because the error argument is always an exception, so it can be thrown - return switch (this) { - case OK(V v) -> v; - case Err(E e) -> throw e; - }; - } - - - /** - * Throw the given supplied {@link Exception} (or more precisely, {@link Throwable}). - *

    - * This method can wrap an {@link Err} type if that {@link Err} type is allowed by the constructor. - * This can also convert Exceptions; e.g., using an {@link java.io.UncheckedIOException} to wrap - * an {@link java.io.IOException} via {@code orThrow(UncheckedIOException::new)}. - *

    - * - * @param exFn Exception producing function - * @param Throwable (Exception) created by {@code exFn} - * @return Value V - * @throws X Throwable - * @see #orThrow() - */ - @NotNull - default V orThrow(@NotNull Function exFn) throws X { - requireNonNull( exFn ); - return switch (this) { - case OK(V v) -> v; - case Err(E e) -> throw requireNonNull( exFn.apply( e ) ); - }; - } - - - -} diff --git a/src/main/java/net/xyzsd/dichotomy/trying/function/ExConsumer.java b/src/main/java/net/xyzsd/dichotomy/trying/function/ExConsumer.java index 8d8cda5..2f32bdf 100644 --- a/src/main/java/net/xyzsd/dichotomy/trying/function/ExConsumer.java +++ b/src/main/java/net/xyzsd/dichotomy/trying/function/ExConsumer.java @@ -19,7 +19,7 @@ public interface ExConsumer { /** * Performs this operation on the given argument. * @param t input - * @throws Exception + * @throws Exception exception */ void accept(@Nullable T t) throws Exception; diff --git a/src/main/java/net/xyzsd/dichotomy/trying/function/ExFunction.java b/src/main/java/net/xyzsd/dichotomy/trying/function/ExFunction.java index c5a6b34..6f6cc13 100644 --- a/src/main/java/net/xyzsd/dichotomy/trying/function/ExFunction.java +++ b/src/main/java/net/xyzsd/dichotomy/trying/function/ExFunction.java @@ -40,6 +40,15 @@ public interface ExFunction { // with the wrapped exception as the cause (or as suppressed) + + /** + * Composes this ExFunction with a regular Function. + * + * @param before the Function to be composed with + * @param the input type of the composed Function + * @return the composed ExFunction + * @throws NullPointerException if the before Function is null + */ // compose with a regular Function @NotNull default ExFunction compose(@NotNull Function before) { @@ -69,6 +78,16 @@ default ExFunction andThen(@NotNull Function a + + /** + * Composes an ExFunction with another ExFunction, applying the before function first + * and then applying this function to the result. + * + * @param the type of the input to the before function + * @param before the ExFunction to apply before this function + * @return a composed ExFunction that applies the before function followed by this function + * @throws NullPointerException if before is null + */ @NotNull default ExFunction composeEx(@NotNull ExFunction before) { requireNonNull(before); diff --git a/src/main/java/net/xyzsd/dichotomy/trying/function/ExSupplier.java b/src/main/java/net/xyzsd/dichotomy/trying/function/ExSupplier.java index 0993d09..d81c027 100644 --- a/src/main/java/net/xyzsd/dichotomy/trying/function/ExSupplier.java +++ b/src/main/java/net/xyzsd/dichotomy/trying/function/ExSupplier.java @@ -10,7 +10,11 @@ @FunctionalInterface public interface ExSupplier { - /** Get a Result or throw an Exception */ + /** + * Get a type T or throw an Exception + * @return T object of supplied type + * @throws Exception exception on failure + */ @NotNull T get() throws Exception; } diff --git a/src/main/java/net/xyzsd/dichotomy/trying/function/SpecExFunction.java b/src/main/java/net/xyzsd/dichotomy/trying/function/SpecExFunction.java index f3e7122..b35cb0e 100644 --- a/src/main/java/net/xyzsd/dichotomy/trying/function/SpecExFunction.java +++ b/src/main/java/net/xyzsd/dichotomy/trying/function/SpecExFunction.java @@ -7,22 +7,39 @@ import static java.util.Objects.requireNonNull; -// DOES NOT compose exceptions ... that is rather difficult +/** + * A functional interface for a special exception-throwing function that takes an argument of type {@code T} and returns a result of type {@code R}. + * The function can throw an exception of type {@code X}. + * + * @param the type of the input to the function + * @param the type of the result of the function + * @param the type of the exception that can be thrown by the function + */ @FunctionalInterface public interface SpecExFunction { + // DOES NOT compose exceptions ... that is rather difficult + /** - * Applies this function to the given argument. + * Applies the function to the given argument. * - * @param t the function argument - * @return the function result + * @param t the argument to apply the function to + * @return the result of applying the function to the argument + * @throws Exception if an exception occurs while applying the function */ @NotNull R apply(T t) throws Exception; - // compose with a regular Function + /** + * Composes this SpecExFunction with a regular Function. + * + * @param the type of input to the before function + * @param before the function to compose with + * @return a new SpecExFunction that is the composition of this SpecExFunction and the before function + * @throws NullPointerException if before is null + */ @NotNull default SpecExFunction compose(@NotNull Function before) { requireNonNull( before ); @@ -31,15 +48,11 @@ default SpecExFunction compose(@NotNull Function - * Note that the {@code after} operation will not be performed if the - * first operation throws an exception. - *

    + * Composes this SpecExFunction with a regular Function. * - * @param after the operation to perform after this operation - * @return a composed {@code ExFunction} that performs this operation followed by the {@code after} operation. + * @param the type of input to the {@code after} function + * @param after the function to compose with + * @return a new SpecExFunction that is the composition of this SpecExFunction and the {@code after} function * @throws NullPointerException if {@code after} is null */ @NotNull @@ -49,6 +62,16 @@ default SpecExFunction andThen(@NotNull Function + * Note: Exceptions are not composed. + * + * @param before the SpecExFunction to compose with + * @param the type of input to the before SpecExFunction + * @return a new SpecExFunction that is the composition of this SpecExFunction and the before SpecExFunction + * @throws NullPointerException if before is null + */ @NotNull default SpecExFunction composeEx(@NotNull SpecExFunction before) { requireNonNull( before ); @@ -57,17 +80,14 @@ default SpecExFunction composeEx(@NotNull SpecExFunction - * Note that the {@code after} operation will not be performed if the - * first operation throws an exception. - *

    + * Note: Exceptions are not composed. * - * @param after the operation to perform after this operation - * @return a composed {@code ExFunction} that performs this operation followed by the {@code after} operation. - * @throws NullPointerException if {@code after} is null - * @see #andThen(Function) + * @param the type of output from the after SpecExFunction + * @param after the SpecExFunction to compose with + * @return a new SpecExFunction that is the composition of this SpecExFunction and the after SpecExFunction + * @throws NullPointerException if after is null */ @NotNull default SpecExFunction andThenEx(@NotNull SpecExFunction after) { @@ -79,6 +99,7 @@ default SpecExFunction andThenEx(@NotNull SpecExFunction input type + * @param Exception type * @return returns the input argument. */ @NotNull diff --git a/src/main/java/net/xyzsd/dichotomy/trying/function/SpecExSupplier.java b/src/main/java/net/xyzsd/dichotomy/trying/function/SpecExSupplier.java index 6d03e17..319276f 100644 --- a/src/main/java/net/xyzsd/dichotomy/trying/function/SpecExSupplier.java +++ b/src/main/java/net/xyzsd/dichotomy/trying/function/SpecExSupplier.java @@ -3,9 +3,22 @@ import org.jetbrains.annotations.NotNull; +/** + * A functional interface that represents a supplier of a value that may throw an exception of type X. + * + * @param the type of the value to be supplied + * @param the type of exception that may be thrown + */ @FunctionalInterface public interface SpecExSupplier { + /** + * Retrieves a value of type T. + * + * @return The retrieved value of type T. + * + * @throws X if there is an exception of type X while retrieving the value. + */ @NotNull T get() throws X; } \ No newline at end of file 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 f041283..3053f96 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 @@ -1,11 +1,11 @@ /** * Exception-producing functional interfaces. *

    - * The functional interfaces in this package are analagous to the {@link java.util.function} interfaces, + * The functional interfaces in this package are analogous to the {@link java.util.function} interfaces, * except that they can throw exceptions--in particular, checked exceptions. *

    *

    - * These interfaces are primarily provided for use with {@link net.xyzsd.dichotomy.trying.Try2}, to + * 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. *

    */ diff --git a/src/main/java/net/xyzsd/dichotomy/trying/package-info.java b/src/main/java/net/xyzsd/dichotomy/trying/package-info.java new file mode 100644 index 0000000..67024e4 --- /dev/null +++ b/src/main/java/net/xyzsd/dichotomy/trying/package-info.java @@ -0,0 +1,5 @@ +/** + * The {@link net.xyzsd.dichotomy.trying.Try} monad, a specialized type of {@link net.xyzsd.dichotomy.Result}, and + * supporting classes. + */ +package net.xyzsd.dichotomy.trying; \ No newline at end of file diff --git a/src/main/java/net/xyzsd/dichotomy/util/Conversion.java b/src/main/java/net/xyzsd/dichotomy/util/Conversion.java index 3624cb6..d2a1d88 100644 --- a/src/main/java/net/xyzsd/dichotomy/util/Conversion.java +++ b/src/main/java/net/xyzsd/dichotomy/util/Conversion.java @@ -2,15 +2,28 @@ import net.xyzsd.dichotomy.Either; import net.xyzsd.dichotomy.Result; -import net.xyzsd.dichotomy.trying.Try2; +import net.xyzsd.dichotomy.trying.Try; import org.jetbrains.annotations.NotNull; -// conversion utils. keeps dependencies separate + +/** + * Conversion utilities. + *

    + * Convert {@link Either}s to and from {@link Result}s or {@link Try}s. + */ public interface Conversion { + // This is a separate package, to reduce dependencies between monad types if + // a user wants to extract, say, the Either package from the module and use it in their own project /** - * Create an {@link Either} from a {@link Result}. + * Converts a {@link Result} into an {@link Either}. + * + * @param the type of the left value in the {@link Either} + * @param the type of the right value in the {@link Either} + * @param result a {@link Result} to be converted into an {@link Either} + * @return an {@link Either} object representing the converted result + * @throws IllegalArgumentException if the {@code result} parameter is null */ @NotNull static Either toEither(@NotNull Result result) { @@ -19,17 +32,31 @@ static Either toEither(@NotNull Result result) { /** - * Create a {@link Either} from a {@link Try2}. + * Converts a {@link Try} object into an {@link Either} object. + *

    + * This method takes a {@link Try} object and converts it into an {@link Either} object. + * The left value of the {@link Either} represents an exception that occurred during the try operation, + * and the right value represents the result of the try operation. + * + * @param the type of the right value in the {@link Either} + * @param the type of the exception in the {@link Try} + * @param tri a {@link Try} object to be converted into an {@link Either} object + * @return an {@link Either} object representing the converted {@link Try} result + * @throws IllegalArgumentException if the {@code tri} parameter is null */ @NotNull - static Either toEither(@NotNull Try2 tri) { + static Either toEither(@NotNull Try tri) { return tri.fold( Either::ofRight, Either::ofLeft ); } /** - * Create a new {@code Result} from the given {@link Either}. + * Converts an {@link Either} object into a {@link Result} object. * - * @return a new {@code Result}, equivalent to this {@link Either}. + * @param the type of the left value in the {@link Either} + * @param the type of the right value in the {@link Either} + * @param either the {@link Either} object to be converted + * @return a {@link Result} object representing the converted {@link Either} + * @throws IllegalArgumentException if the {@code either} parameter is null */ @NotNull static Result toResult(@NotNull final Either either) { @@ -37,30 +64,51 @@ static Result toResult(@NotNull final Either either) { } + /** - * Create a {@link Result} from a {@link Try2}. + * Converts a {@link Try} object into a {@link Result} object. + * + * @param the type of the value in the {@link Result} + * @param the type of the exception in the {@link Try} + * @param tri a {@link Try} object to be converted into a {@link Result} + * @return a {@link Result} object representing the converted {@link Try} result + * @throws IllegalArgumentException if the {@code tri} parameter is null */ @NotNull - static Result toResult(@NotNull Try2 tri) { + static Result toResult(@NotNull Try tri) { return tri.fold( Result::ofOK, Result::ofErr ); } + /** - * Create a {@link Try2} from an {@link Either}. + * Converts an {@link Either} object into a {@link Try} object. + * + * @param the type of the left value in the {@link Either} + * @param the type of the right value in the {@link Either} + * @param either the {@link Either} object to be converted + * @return a {@link Try} object representing the converted {@link Either} + * @throws IllegalArgumentException if the {@code either} parameter is null */ @NotNull - static Try2 toTry(@NotNull Either either) { - return either.fold( Try2.Err::new, Try2.OK::new ); + static Try toTry(@NotNull Either either) { + return either.fold( Try.Err::new, Try.OK::new ); } + /** - * Create a {@link Try2} from a {@link Result}. + * Converts a {@link Result} into a {@link Try}. + * + * @param the type of the value in the {@link Result} + * @param the type of the exception in the {@link Try} + * @param result a {@link Result} to be converted into a {@link Try} + * @return a {@link Try} object representing the converted result + * @throws IllegalArgumentException if the {@code result} parameter is null */ @NotNull - static Try2 toTry(@NotNull Result result) { - return result.fold( Try2.OK::new, Try2.Err::new ); + static Try toTry(@NotNull Result result) { + return result.fold( Try.OK::new, Try.Err::new ); } diff --git a/src/main/java/net/xyzsd/dichotomy/util/package-info.java b/src/main/java/net/xyzsd/dichotomy/util/package-info.java new file mode 100644 index 0000000..8106090 --- /dev/null +++ b/src/main/java/net/xyzsd/dichotomy/util/package-info.java @@ -0,0 +1,9 @@ +/** + * static conversion utility methods to convert between monadic types. + *

    + * This is a separate package to allow easier severability of classes without dependencies; e.g., a potential user + * may want to extract out the {@link net.xyzsd.dichotomy.Result} type; the only dependency would be + * {@link net.xyzsd.dichotomy.Empty}. + * + */ +package net.xyzsd.dichotomy.util; \ No newline at end of file diff --git a/src/test/java/net/xyzsd/dichotomy/either/EitherTest.java b/src/test/java/net/xyzsd/dichotomy/EitherTest.java similarity index 64% rename from src/test/java/net/xyzsd/dichotomy/either/EitherTest.java rename to src/test/java/net/xyzsd/dichotomy/EitherTest.java index ca22cca..46ddeb9 100644 --- a/src/test/java/net/xyzsd/dichotomy/either/EitherTest.java +++ b/src/test/java/net/xyzsd/dichotomy/EitherTest.java @@ -1,17 +1,15 @@ -package net.xyzsd.dichotomy.either; +package net.xyzsd.dichotomy; -import net.xyzsd.dichotomy.Either; -import net.xyzsd.dichotomy.Result; import net.xyzsd.dichotomy.util.Conversion; +import org.junit.jupiter.api.Test; +import static net.xyzsd.dichotomy.TestUtils.neverFunction; +import static net.xyzsd.dichotomy.TestUtils.neverSupplier; import static org.junit.jupiter.api.Assertions.*; import java.io.IOException; import java.util.Optional; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.function.Consumer; import java.util.function.Function; -import java.util.function.Supplier; public class EitherTest { @@ -21,54 +19,31 @@ public class EitherTest { static final Either RIGHT = Either.ofRight( RVAL ); - public static class SingleUseConsumer implements Consumer { - private final AtomicInteger useCount = new AtomicInteger(); - - @Override - public final void accept(T t) { - useCount.incrementAndGet(); - } - - public final boolean wasActivatedOnce() { - return (useCount.get() == 1); - } - } - - - // 'never' types: check if implementation calls a supplier / function when it doesn't need to - // Using rawtypes is easier. if the cast fails... we have an error anyway - @SuppressWarnings("rawtypes") - public static final Supplier NEVERSUPPLIER = () -> {throw new IllegalStateException( "NEVERSUPPLIER::get invoked!" );}; - - @SuppressWarnings("rawtypes") - public static final Function NEVERFUNCTION = (x) -> {throw new IllegalStateException( "NEVERFUNCTION::apply invoked!" );}; - - - @org.junit.jupiter.api.Test + @Test void ofLeft() { assertEquals( Either.Left.class, LEFT.getClass() ); assertEquals( LVAL, ((Either.Left) LEFT).get() ); } - @org.junit.jupiter.api.Test + @Test void ofRight() { assertEquals( Either.Right.class, RIGHT.getClass() ); assertEquals( RVAL, ((Either.Right) RIGHT).get() ); } - @org.junit.jupiter.api.Test + @Test void fromResult() { - assertEquals( RIGHT, Conversion.toEither( Result.ofOK(RVAL)) ); - assertEquals( LEFT, Conversion.toEither( Result.ofErr(LVAL)) ); + assertEquals( RIGHT, Conversion.toEither( Result.ofOK( RVAL ) ) ); + assertEquals( LEFT, Conversion.toEither( Result.ofErr( LVAL ) ) ); } - @org.junit.jupiter.api.Test + @Test void left() { assertEquals( Optional.of( LVAL ), LEFT.left() ); assertTrue( LEFT.right().isEmpty() ); } - @org.junit.jupiter.api.Test + @Test void right() { assertEquals( Optional.of( RVAL ), RIGHT.right() ); assertTrue( RIGHT.left().isEmpty() ); @@ -76,21 +51,29 @@ void right() { // match(Consumer,Consumer) - @org.junit.jupiter.api.Test + @Test void biMatch() { - SingleUseConsumer leftConsumer = new SingleUseConsumer<>(); - SingleUseConsumer rightConsumer = new SingleUseConsumer<>(); + { + TestUtils.SingleUseConsumer leftConsumer = new TestUtils.SingleUseConsumer<>(); + TestUtils.SingleUseConsumer rightConsumer = new TestUtils.SingleUseConsumer<>(); + // + LEFT.biMatch( leftConsumer, rightConsumer ); + assertTrue( leftConsumer.usedJustOnce() ); + assertTrue( rightConsumer.neverUsed() ); + } - LEFT.biMatch( leftConsumer, rightConsumer ); - assertTrue( leftConsumer.wasActivatedOnce() ); - assertFalse( rightConsumer.wasActivatedOnce() ); - RIGHT.biMatch( leftConsumer, rightConsumer ); - assertTrue( leftConsumer.wasActivatedOnce() ); - assertTrue( rightConsumer.wasActivatedOnce() ); + { + TestUtils.SingleUseConsumer leftConsumer = new TestUtils.SingleUseConsumer<>(); + TestUtils.SingleUseConsumer rightConsumer = new TestUtils.SingleUseConsumer<>(); + // + RIGHT.biMatch( leftConsumer, rightConsumer ); + assertTrue( leftConsumer.neverUsed() ); + assertTrue( rightConsumer.usedJustOnce() ); + } } - @org.junit.jupiter.api.Test + @Test void biMap() { Function fnSS = s -> "STRING"; Function fnIS = i -> "INT"; @@ -111,11 +94,11 @@ void biMap() { } ); // and must not execute if not needed - assertDoesNotThrow( () -> RIGHT.biMap( NEVERFUNCTION, fnIS ) ); - assertDoesNotThrow( () -> LEFT.biMap( fnSS, NEVERFUNCTION ) ); + assertDoesNotThrow( () -> RIGHT.biMap( neverFunction(), fnIS ) ); + assertDoesNotThrow( () -> LEFT.biMap( fnSS, neverFunction() ) ); } - @org.junit.jupiter.api.Test + @Test void biFlatMap() { final Function> fnStoE = (x) -> Either.ofLeft( 1111L ); final Function> fnIntToE = (x) -> Either.ofRight( 2222L ); @@ -131,11 +114,11 @@ void biFlatMap() { RIGHT.biFlatMap( fnStoE, x -> null ); } ); - assertDoesNotThrow( () -> RIGHT.biFlatMap( NEVERFUNCTION, fnIntToE ) ); - assertDoesNotThrow( () -> LEFT.biFlatMap( fnStoE, NEVERFUNCTION ) ); + assertDoesNotThrow( () -> RIGHT.biFlatMap( neverFunction(), fnIntToE ) ); + assertDoesNotThrow( () -> LEFT.biFlatMap( fnStoE, neverFunction() ) ); } - @org.junit.jupiter.api.Test + @Test void fold() { Function fnSL = s -> 1111L; Function fnIL = i -> -9999L; @@ -155,102 +138,102 @@ void fold() { RIGHT.fold( fnSL, x -> null ); } ); - assertDoesNotThrow( () -> LEFT.fold( fnSL, NEVERFUNCTION ) ); - assertDoesNotThrow( () -> RIGHT.fold( NEVERFUNCTION, fnIL ) ); + assertDoesNotThrow( () -> LEFT.fold( fnSL, neverFunction() ) ); + assertDoesNotThrow( () -> RIGHT.fold( neverFunction(), fnIL ) ); } - @org.junit.jupiter.api.Test + @Test void filter() { final Function fn = x -> "FUNCTION"; // left returns left, always assertEquals( LEFT, LEFT.filter( x -> true, fn ) ); assertEquals( LEFT, LEFT.filter( x -> false, fn ) ); - assertDoesNotThrow( () -> LEFT.filter( x -> true, NEVERFUNCTION ) ); - assertDoesNotThrow( () -> LEFT.filter( x -> false, NEVERFUNCTION ) ); + assertDoesNotThrow( () -> LEFT.filter( x -> true, neverFunction() ) ); + assertDoesNotThrow( () -> LEFT.filter( x -> false, neverFunction() ) ); // right returns right, if predicate matches assertEquals( RIGHT, RIGHT.filter( x -> true, fn ) ); - assertDoesNotThrow( () -> RIGHT.filter( x -> true, NEVERFUNCTION ) ); + assertDoesNotThrow( () -> RIGHT.filter( x -> true, neverFunction() ) ); // right returns mapper result, if predicate fails final Either filter = RIGHT.filter( x -> false, fn ); assertTrue( filter.containsLeft( "FUNCTION" ) ); } - @org.junit.jupiter.api.Test + @Test void forfeit() { final Function fn = x -> "FUNCTION"; assertEquals( LVAL, LEFT.forfeit( fn ) ); assertEquals( "FUNCTION", RIGHT.forfeit( fn ) ); - assertDoesNotThrow( () -> LEFT.forfeit( NEVERFUNCTION ) ); + assertDoesNotThrow( () -> LEFT.forfeit( neverFunction() ) ); } - @org.junit.jupiter.api.Test + @Test void recover() { final Function fn = x -> 999; assertEquals( 999, LEFT.recover( fn ) ); assertEquals( RVAL, RIGHT.recover( fn ) ); - assertDoesNotThrow( () -> RIGHT.recover( NEVERFUNCTION ) ); + assertDoesNotThrow( () -> RIGHT.recover( neverFunction() ) ); } - @org.junit.jupiter.api.Test + @Test void stream() { assertEquals( 1, RIGHT.stream().count() ); assertTrue( RIGHT.stream().allMatch( (i) -> (i == RVAL) ) ); assertEquals( 0, RIGHT.streamLeft().count() ); } - @org.junit.jupiter.api.Test + @Test void streamLeft() { assertEquals( 1, LEFT.streamLeft().count() ); assertTrue( LEFT.streamLeft().allMatch( (s) -> s.equals( LVAL ) ) ); assertEquals( 0, LEFT.stream().count() ); } - @org.junit.jupiter.api.Test + @Test void contains() { assertTrue( RIGHT.contains( 42 ) ); assertFalse( RIGHT.contains( 1000 ) ); } - @org.junit.jupiter.api.Test + @Test void containsLeft() { assertTrue( LEFT.containsLeft( LVAL ) ); assertFalse( LEFT.containsLeft( "" ) ); } - @org.junit.jupiter.api.Test + @Test void matches() { - assertTrue( RIGHT.matches( x -> true ) ); - assertTrue( RIGHT.matches( x -> (x == RVAL) ) ); - assertFalse( RIGHT.matches( x -> false ) ); + assertTrue( RIGHT.ifPredicate( x -> true ) ); + assertTrue( RIGHT.ifPredicate( x -> (x == RVAL) ) ); + assertFalse( RIGHT.ifPredicate( x -> false ) ); // left Eithers should NEVER match any predicate for the right either (matches() is right biased) - assertFalse( LEFT.matches( x -> true ) ); - assertFalse( LEFT.matches( x -> (x == RVAL) ) ); - assertFalse( LEFT.matches( x -> false ) ); + assertFalse( LEFT.ifPredicate( x -> true ) ); + assertFalse( LEFT.ifPredicate( x -> (x == RVAL) ) ); + assertFalse( LEFT.ifPredicate( x -> false ) ); } - @org.junit.jupiter.api.Test + @Test void matchesLeft() { // right Eithers should NEVER match any predicate for the left Either - assertFalse( RIGHT.matchesLeft( x -> true ) ); - assertFalse( RIGHT.matchesLeft( LVAL::equals ) ); - assertFalse( RIGHT.matchesLeft( x -> false ) ); + assertFalse( RIGHT.ifPredicateLeft( x -> true ) ); + assertFalse( RIGHT.ifPredicateLeft( LVAL::equals ) ); + assertFalse( RIGHT.ifPredicateLeft( x -> false ) ); - assertTrue( LEFT.matchesLeft( x -> true ) ); - assertTrue( LEFT.matchesLeft( LVAL::equals ) ); - assertFalse( LEFT.matchesLeft( x -> false ) ); + assertTrue( LEFT.ifPredicateLeft( x -> true ) ); + assertTrue( LEFT.ifPredicateLeft( LVAL::equals ) ); + assertFalse( LEFT.ifPredicateLeft( x -> false ) ); } // right-biased map - @org.junit.jupiter.api.Test + @Test void testMap() { Function fnIS = i -> "INT"; assertEquals( Either.ofLeft( LVAL ), LEFT.map( fnIS ) ); - assertDoesNotThrow( () -> LEFT.map( NEVERFUNCTION ) ); + assertDoesNotThrow( () -> LEFT.map( neverFunction() ) ); assertEquals( Either.ofRight( "INT" ), RIGHT.map( fnIS ) ); assertThrows( NullPointerException.class, () -> RIGHT.map( x -> null ) ); @@ -258,29 +241,29 @@ void testMap() { } // right-biased flatmap - @org.junit.jupiter.api.Test + @Test void testFlatMap() { final Function> fnIntToE = (x) -> Either.ofRight( 2222L ); assertEquals( Either.ofLeft( LVAL ), LEFT.flatMap( fnIntToE ) ); - assertDoesNotThrow( () -> LEFT.flatMap( NEVERFUNCTION ) ); + assertDoesNotThrow( () -> LEFT.flatMap( neverFunction() ) ); assertEquals( Either.ofRight( 2222L ), RIGHT.flatMap( fnIntToE ) ); assertThrows( NullPointerException.class, () -> RIGHT.flatMap( x -> null ) ); } - @org.junit.jupiter.api.Test + @Test void mapLeft() { Function fnSS = s -> "STRING"; assertEquals( Either.ofRight( RVAL ), RIGHT.mapLeft( fnSS ) ); - assertDoesNotThrow( () -> RIGHT.mapLeft( NEVERFUNCTION ) ); + assertDoesNotThrow( () -> RIGHT.mapLeft( neverFunction() ) ); assertEquals( Either.ofLeft( "STRING" ), LEFT.mapLeft( fnSS ) ); assertThrows( NullPointerException.class, () -> LEFT.mapLeft( x -> null ) ); } - @org.junit.jupiter.api.Test + @Test void flatMapLeft() { final Function> fnStoE = (x) -> Either.ofLeft( 1111L ); @@ -288,65 +271,66 @@ void flatMapLeft() { assertThrows( NullPointerException.class, () -> LEFT.flatMapLeft( x -> null ) ); assertEquals( Either.ofRight( RVAL ), RIGHT.flatMapLeft( fnStoE ) ); - assertDoesNotThrow( () -> RIGHT.flatMapLeft( NEVERFUNCTION ) ); + assertDoesNotThrow( () -> RIGHT.flatMapLeft( neverFunction() ) ); } - @org.junit.jupiter.api.Test + @Test void matchLeft() { - final SingleUseConsumer consumer = new SingleUseConsumer<>(); - LEFT.matchLeft( consumer ); - assertTrue( consumer.wasActivatedOnce() ); + final TestUtils.SingleUseConsumer leftConsumer = new TestUtils.SingleUseConsumer<>(); + LEFT.matchLeft( leftConsumer ); + assertTrue( leftConsumer.usedJustOnce() ); - RIGHT.matchLeft( consumer ); - assertTrue( consumer.wasActivatedOnce() ); + final TestUtils.SingleUseConsumer rightConsumer = new TestUtils.SingleUseConsumer<>(); + RIGHT.matchLeft( rightConsumer ); + assertTrue( rightConsumer.neverUsed() ); } // match(Consumer) with right bias - @org.junit.jupiter.api.Test + @Test void testMatch() { - final SingleUseConsumer consumer = new SingleUseConsumer<>(); - - RIGHT.match( consumer ); - assertTrue( consumer.wasActivatedOnce() ); + final TestUtils.SingleUseConsumer rightConsumer = new TestUtils.SingleUseConsumer<>(); + RIGHT.match( rightConsumer ); + assertTrue( rightConsumer.usedJustOnce() ); - LEFT.match( consumer ); - assertTrue( consumer.wasActivatedOnce() ); + final TestUtils.SingleUseConsumer leftConsumer = new TestUtils.SingleUseConsumer<>(); + LEFT.match( leftConsumer ); + assertTrue( leftConsumer.neverUsed() ); } - @org.junit.jupiter.api.Test + @Test void orElse() { assertEquals( RVAL, RIGHT.orElse( 222 ) ); assertEquals( 222, LEFT.orElse( 222 ) ); } - @org.junit.jupiter.api.Test + @Test void orElseLeft() { assertEquals( "ALTERNATE", RIGHT.orElseLeft( "ALTERNATE" ) ); assertEquals( LVAL, LEFT.orElseLeft( "ALTERNATE" ) ); } - @org.junit.jupiter.api.Test + @Test void orElseGet() { // correctness assertEquals( RVAL, RIGHT.orElseGet( () -> 222 ) ); assertEquals( 222, LEFT.orElseGet( () -> 222 ) ); // efficiency - assertEquals( RVAL, RIGHT.orElseGet( NEVERSUPPLIER ) ); + assertEquals( RVAL, RIGHT.orElseGet( neverSupplier() ) ); } - @org.junit.jupiter.api.Test + @Test void orElseGetLeft() { // correctness assertEquals( "ALTERNATE", RIGHT.orElseGetLeft( () -> "ALTERNATE" ) ); assertEquals( LVAL, LEFT.orElseGetLeft( () -> "ALTERNATE" ) ); // efficiency - assertEquals( LVAL, LEFT.orElseGetLeft( NEVERSUPPLIER ) ); + assertEquals( LVAL, LEFT.orElseGetLeft( neverSupplier() ) ); } - @org.junit.jupiter.api.Test + @Test void and() { assertEquals( LEFT, LEFT.and( Either.ofRight( "RIGHT_STRING" ) ) ); @@ -356,9 +340,9 @@ void and() { ); } - @org.junit.jupiter.api.Test + @Test void testAnd() { - assertEquals( LEFT, LEFT.and( NEVERSUPPLIER ) ); + assertEquals( LEFT, LEFT.and( neverSupplier() ) ); assertEquals( Either.ofRight( "RIGHT_STRING" ), RIGHT.and( () -> Either.ofRight( "RIGHT_STRING" ) ) @@ -370,18 +354,18 @@ void testAnd() { ); } - @org.junit.jupiter.api.Test + @Test void or() { - assertEquals( Either.ofLeft( 555L), - LEFT.or( Either.ofLeft( 555L ) ) + assertEquals( Either.ofLeft( 555L ), + LEFT.or( Either.ofLeft( 555L ) ) ); assertEquals( RIGHT, RIGHT.or( LEFT ) ); } - @org.junit.jupiter.api.Test + @Test void testOr() { - assertEquals( Either.ofLeft( 555L), + assertEquals( Either.ofLeft( 555L ), LEFT.or( () -> Either.ofLeft( 555L ) ) ); @@ -390,39 +374,39 @@ void testOr() { () -> LEFT.or( () -> null ) ); - assertEquals( RIGHT, RIGHT.or( NEVERSUPPLIER ) ); + assertEquals( RIGHT, RIGHT.or( neverSupplier() ) ); } - @org.junit.jupiter.api.Test + @Test void getOrThrow() { assertThrows( ArithmeticException.class, - () -> LEFT.getOrThrow( ArithmeticException::new ) + () -> LEFT.expect( ArithmeticException::new ) ); - assertEquals( RVAL, RIGHT.getOrThrow( ArithmeticException::new ) ); + assertEquals( RVAL, RIGHT.expect( ArithmeticException::new ) ); } - @org.junit.jupiter.api.Test + @Test void getOrThrowWrapped() { assertThrows( ArithmeticException.class, - () -> LEFT.getOrThrowWrapped( ArithmeticException::new ) + () -> LEFT.getOrThrow( ArithmeticException::new ) ); try { - LEFT.getOrThrowWrapped( IOException::new ); - } catch(IOException e) { + LEFT.getOrThrow( IOException::new ); + } catch (IOException e) { // getOrThrowWrapped() calls the IOException(String) constructor, // since LEFT is a String type, rather than the no-arg IOException // constructor, as getOrThrow() via a Supplier would. assertEquals( LVAL, e.getMessage() ); } - assertEquals( RVAL, RIGHT.getOrThrowWrapped( ArithmeticException::new ) ); + assertEquals( RVAL, RIGHT.getOrThrow( ArithmeticException::new ) ); } - @org.junit.jupiter.api.Test + @Test void swap() { Either left = Either.ofLeft( "left" ); Either leftSwap = left.swap(); @@ -433,14 +417,14 @@ void swap() { assertTrue( rightSwap.containsLeft( "right" ) ); } - @org.junit.jupiter.api.Test + @Test void toResult() { - assertEquals( Result.ofErr(LVAL), Conversion.toResult(LEFT) ); - assertEquals( Result.ofOK(RVAL), Conversion.toResult(RIGHT) ); + assertEquals( Result.ofErr( LVAL ), Conversion.toResult( LEFT ) ); + assertEquals( Result.ofOK( RVAL ), Conversion.toResult( RIGHT ) ); } // because we don't want illegal monads - @org.junit.jupiter.api.Test + @Test void monadicLaws() { // NOTE: the terms 'left identity' and 'right identity' below, are general terms for monad properties, // and do not refer to right and left Eithers! @@ -455,17 +439,17 @@ void monadicLaws() { // given: LVAL (our value) [already defined] // given: LEFT (our Either which wraps LVAL) [already defined] // given: the following function: - final Function> concat = s -> Either.ofLeft(s+s); + final Function> concat = s -> Either.ofLeft( s + s ); // then the following should be equivalent: assertEquals( - concat.apply(LVAL), + concat.apply( LVAL ), LEFT.flatMapLeft( concat ) ); // and similarly, for right Eithers: - final Function> square = i -> Either.ofRight( i*i); + final Function> square = i -> Either.ofRight( i * i ); assertEquals( - square.apply(RVAL) , + square.apply( RVAL ), RIGHT.flatMap( square ) ); @@ -498,8 +482,8 @@ void monadicLaws() { // flatmap nesting order should not matter // given: functions square & concat from above (for right and left monads, respectively) // given: the following functions: - final Function> delim = s -> Either.ofLeft(s+","+s); - final Function> hundredMore = i -> Either.ofRight(i+100); + final Function> delim = s -> Either.ofLeft( s + "," + s ); + final Function> hundredMore = i -> Either.ofRight( i + 100 ); // then: assertEquals( LEFT.flatMapLeft( delim ).flatMapLeft( concat ), @@ -513,7 +497,7 @@ void monadicLaws() { } - @org.junit.jupiter.api.Test + @Test void swapEquivalences() { final Either original = Either.ofLeft( "left" ); final Either swapped = original.swap(); @@ -521,8 +505,8 @@ void swapEquivalences() { // could do this for multiple methods.... // as an example, the following should be equivalent: assertEquals( - original.forfeit( NEVERFUNCTION ), - swapped.recover(NEVERFUNCTION) + original.forfeit( neverFunction() ), + swapped.recover( neverFunction() ) ); } diff --git a/src/test/java/net/xyzsd/dichotomy/MaybeTest.java b/src/test/java/net/xyzsd/dichotomy/MaybeTest.java new file mode 100644 index 0000000..9791f77 --- /dev/null +++ b/src/test/java/net/xyzsd/dichotomy/MaybeTest.java @@ -0,0 +1,268 @@ +package net.xyzsd.dichotomy; + +import net.xyzsd.dichotomy.TestUtils.SingleUseConsumer; +import org.junit.jupiter.api.Test; + +import java.util.NoSuchElementException; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.function.Supplier; + +import static net.xyzsd.dichotomy.TestUtils.*; +import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertFalse; + +class MaybeTest { + + static final String TEST_STR = "TEST"; + static final String NEW_STRING = "NEW_STRING"; + static final String MORE_STRING = "MORE"; + + static final Maybe MAYBE_YES = Maybe.of( TEST_STR ); + static final Maybe MAYBE_NOT = Maybe.ofNone(); + static final Maybe MAYBE_MORE = Maybe.of( MORE_STRING ); + + + static final int INT666 = 666; + static final int INT777 = 777; + static final Function STR2INT = (x) -> INT666; + static final Function INT2INT = (x) -> x + 111; + + static final Supplier STRSUP = () -> NEW_STRING; + static final Supplier INTSUP = () -> 777; + + static final Supplier NULLSUP = () -> null; + + @Test + void of() { + assertEquals( MAYBE_YES.getClass(), Maybe.Some.class ); + } + + @Test + void ofNone() { + assertEquals( MAYBE_NOT.getClass(), Maybe.None.class ); + } + + @Test + void ofNullable() { + Maybe maybeYes = Maybe.ofNullable( TEST_STR ); + assertEquals( maybeYes.getClass(), Maybe.Some.class ); + + Maybe maybeNot = Maybe.ofNullable( null ); + assertEquals( maybeNot.getClass(), Maybe.None.class ); + } + + @Test + void biMatch() { + assertThrows( NullPointerException.class, () -> MAYBE_YES.biMatch( null, NEVERRUNNABLE ) ); + assertThrows( NullPointerException.class, () -> MAYBE_YES.biMatch( neverConsumer(), null ) ); + assertThrows( NullPointerException.class, () -> MAYBE_NOT.biMatch( null, NEVERRUNNABLE ) ); + assertThrows( NullPointerException.class, () -> MAYBE_NOT.biMatch( neverConsumer(), null ) ); + + SingleUseConsumer stringConsumer = new SingleUseConsumer<>(); + final Maybe maybeYes = assertDoesNotThrow( () -> MAYBE_YES.biMatch( stringConsumer, NEVERRUNNABLE ) ); + assertEquals( 1, stringConsumer.activationCount() ); + assertEquals( maybeYes, MAYBE_YES ); + + SingleUseRunnable singleUseRunnable = new SingleUseRunnable(); + final Maybe maybeNot = assertDoesNotThrow( () -> MAYBE_NOT.biMatch( neverConsumer(), singleUseRunnable ) ); + assertEquals( 1, singleUseRunnable.activationCount() ); + assertEquals( maybeNot, MAYBE_NOT ); + } + + @Test + void filter() { + assertThrows( NullPointerException.class, () -> MAYBE_YES.filter( null ) ); + assertThrows( NullPointerException.class, () -> MAYBE_NOT.filter( null ) ); + + Predicate predicate = "TEST"::equals; + + assertEquals( MAYBE_YES, MAYBE_YES.filter( predicate ) ); + assertEquals( Maybe.ofNone(), MAYBE_YES.filter( predicate.negate() ) ); + // + assertEquals( Maybe.ofNone(), MAYBE_NOT.filter( predicate ) ); + } + + @Test + void fold() { + assertThrows( NullPointerException.class, () -> MAYBE_YES.fold( null, neverSupplier() ) ); + assertThrows( NullPointerException.class, () -> MAYBE_YES.fold( neverFunction(), null ) ); + assertThrows( NullPointerException.class, () -> MAYBE_NOT.fold( null, neverSupplier() ) ); + assertThrows( NullPointerException.class, () -> MAYBE_NOT.fold( neverFunction(), null ) ); + + assertEquals( + INT666, + MAYBE_YES.fold( STR2INT, neverSupplier() ) + ); + + assertEquals( + INT777, + MAYBE_NOT.fold( neverFunction(), INTSUP ) + ); + } + + @Test + void stream() { + assertEquals( 1, MAYBE_YES.stream().count() ); + assertEquals( 0, MAYBE_NOT.stream().count() ); + } + + @Test + void map() { + assertThrows( NullPointerException.class, () -> MAYBE_YES.map( null ) ); + assertThrows( NullPointerException.class, () -> MAYBE_NOT.map( null ) ); + assertThrows( NullPointerException.class, () -> MAYBE_YES.map( (s) -> null ) ); + assertDoesNotThrow( () -> MAYBE_NOT.map( neverFunction() ) ); + + assertEquals( + MAYBE_MORE, + MAYBE_YES.map( (x) -> MORE_STRING ) + ); + } + + @Test + void match() { + assertThrows( NullPointerException.class, () -> MAYBE_YES.consume( null ) ); + assertThrows( NullPointerException.class, () -> MAYBE_NOT.consume( null ) ); + + SingleUseConsumer stringConsumer = new SingleUseConsumer<>(); + Maybe maybeYes = assertDoesNotThrow( () -> MAYBE_YES.match( stringConsumer ) ); + assertEquals( 1, stringConsumer.activationCount() ); + assertEquals( maybeYes, MAYBE_YES ); + } + + @Test + void consume() { + assertThrows( NullPointerException.class, () -> MAYBE_YES.consume( null ) ); + assertThrows( NullPointerException.class, () -> MAYBE_NOT.consume( null ) ); + + SingleUseConsumer stringConsumer = new SingleUseConsumer<>(); + assertDoesNotThrow( () -> MAYBE_YES.consume( stringConsumer ) ); + assertEquals( 1, stringConsumer.activationCount() ); + } + + @Test + void flatMap() { + assertThrows( NullPointerException.class, () -> MAYBE_YES.flatMap( null ) ); + assertThrows( NullPointerException.class, () -> MAYBE_NOT.flatMap( null ) ); + assertThrows( NullPointerException.class, () -> MAYBE_YES.flatMap( (s) -> null ) ); + assertDoesNotThrow( () -> MAYBE_NOT.flatMap( neverFunction() ) ); + + assertEquals( + MAYBE_MORE, + MAYBE_YES.flatMap( (x) -> MAYBE_MORE ) + ); + } + + @Test + void matches() { + assertThrows( NullPointerException.class, () -> MAYBE_YES.matches( null ) ); + assertThrows( NullPointerException.class, () -> MAYBE_NOT.matches( null ) ); + + Predicate predicate = "TEST"::equals; + + assertTrue( MAYBE_YES.matches( predicate ) ); + assertFalse( MAYBE_YES.matches( predicate.negate() ) ); + // + assertFalse( MAYBE_NOT.matches( predicate ) ); + } + + @Test + void contains() { + assertTrue( MAYBE_YES.contains( TEST_STR ) ); + assertFalse( MAYBE_YES.contains( null ) ); + // + assertFalse( MAYBE_NOT.contains( TEST_STR ) ); + assertTrue( MAYBE_NOT.contains( null ) ); + } + + @Test + void hasSome() { + assertTrue( MAYBE_YES.hasSome() ); + assertFalse( MAYBE_NOT.hasSome() ); + } + + @Test + void orElse() { + // assertThrows( NullPointerException.class, () -> MAYBE_YES.orElse( (String)null ) ); + assertThrows( NullPointerException.class, () -> MAYBE_NOT.orElse( (String) null ) ); + assertEquals( TEST_STR, MAYBE_YES.orElse( "*invalid*" ) ); + assertEquals( "*invalid*", MAYBE_NOT.orElse( "*invalid*" ) ); + } + + @Test + void testOrElse() { + assertThrows( NullPointerException.class, () -> MAYBE_YES.orElse( (Supplier) null ) ); + assertThrows( NullPointerException.class, () -> MAYBE_NOT.orElse( (Supplier) null ) ); + assertThrows( NullPointerException.class, () -> MAYBE_NOT.orElse( NULLSUP ) ); + assertEquals( TEST_STR, MAYBE_YES.orElse( neverSupplier() ) ); + assertEquals( "*invalid*", MAYBE_NOT.orElse( () -> "*invalid*" ) ); + } + + @Test + void and() { + assertThrows( NullPointerException.class, () -> MAYBE_YES.and( (Maybe) null ) ); + assertThrows( NullPointerException.class, () -> MAYBE_NOT.and( (Maybe) null ) ); + + assertEquals( MAYBE_MORE, MAYBE_YES.and( MAYBE_MORE ) ); + assertEquals( MAYBE_NOT, MAYBE_YES.and( MAYBE_NOT ) ); + + assertEquals( MAYBE_NOT, MAYBE_NOT.and( MAYBE_YES ) ); + assertEquals( MAYBE_NOT, MAYBE_NOT.and( MAYBE_NOT ) ); + } + + @Test + void testAnd() { + assertThrows( NullPointerException.class, () -> MAYBE_YES.and( (Supplier>) null ) ); + assertThrows( NullPointerException.class, () -> MAYBE_YES.and( () -> null ) ); + assertThrows( NullPointerException.class, () -> MAYBE_NOT.and( (Supplier>) null ) ); + + assertEquals( MAYBE_MORE, MAYBE_YES.and( () -> MAYBE_MORE ) ); + assertEquals( MAYBE_NOT, MAYBE_YES.and( () -> MAYBE_NOT ) ); + + assertEquals( MAYBE_NOT, MAYBE_NOT.and( () -> MAYBE_YES ) ); + assertEquals( MAYBE_NOT, MAYBE_NOT.and( () -> MAYBE_NOT ) ); + } + + @Test + void or() { + assertThrows( NullPointerException.class, () -> MAYBE_YES.or( (Maybe) null ) ); + assertThrows( NullPointerException.class, () -> MAYBE_NOT.or( (Maybe) null ) ); + + assertEquals( MAYBE_YES, MAYBE_YES.or( MAYBE_MORE ) ); + assertEquals( MAYBE_YES, MAYBE_YES.or( MAYBE_NOT ) ); + + assertEquals( MAYBE_YES, MAYBE_NOT.or( MAYBE_YES ) ); + assertEquals( MAYBE_MORE, MAYBE_NOT.or( MAYBE_MORE ) ); + } + + @Test + void testOr() { + assertThrows( NullPointerException.class, () -> MAYBE_YES.or( (Supplier>) null ) ); + assertThrows( NullPointerException.class, () -> MAYBE_NOT.or( () -> null ) ); + assertThrows( NullPointerException.class, () -> MAYBE_NOT.or( (Supplier>) null ) ); + + assertEquals( MAYBE_YES, MAYBE_YES.or( () -> MAYBE_MORE ) ); + assertEquals( MAYBE_YES, MAYBE_YES.or( () -> MAYBE_NOT ) ); + + assertEquals( MAYBE_YES, MAYBE_NOT.or( () -> MAYBE_YES ) ); + assertEquals( MAYBE_MORE, MAYBE_NOT.or( () -> MAYBE_MORE ) ); + } + + @Test + void expect() { + final String s = assertDoesNotThrow( MAYBE_YES::expect ); + assertEquals( TEST_STR, s ); + + assertThrows( NoSuchElementException.class, MAYBE_NOT::expect ); + } + + @Test + void getOrThrow() { + final String s = assertDoesNotThrow( () -> MAYBE_YES.getOrThrow( ArithmeticException::new ) ); + assertEquals( TEST_STR, s ); + + assertThrows( ArithmeticException.class, () -> MAYBE_NOT.getOrThrow( ArithmeticException::new ) ); + } +} \ No newline at end of file diff --git a/src/test/java/net/xyzsd/dichotomy/result/ResultTest.java b/src/test/java/net/xyzsd/dichotomy/ResultTest.java similarity index 72% rename from src/test/java/net/xyzsd/dichotomy/result/ResultTest.java rename to src/test/java/net/xyzsd/dichotomy/ResultTest.java index 7f5f9ca..8f3597a 100644 --- a/src/test/java/net/xyzsd/dichotomy/result/ResultTest.java +++ b/src/test/java/net/xyzsd/dichotomy/ResultTest.java @@ -1,19 +1,18 @@ -package net.xyzsd.dichotomy.result; +package net.xyzsd.dichotomy; -import net.xyzsd.dichotomy.None; -import net.xyzsd.dichotomy.Result; import static net.xyzsd.dichotomy.Result.OK; import static net.xyzsd.dichotomy.Result.Err; import org.junit.jupiter.api.Test; -import net.xyzsd.dichotomy.either.EitherTest.SingleUseConsumer; +import net.xyzsd.dichotomy.TestUtils.SingleUseConsumer; import java.io.IOException; import java.util.NoSuchElementException; import java.util.Optional; import java.util.function.Function; -import java.util.function.Supplier; +import static net.xyzsd.dichotomy.TestUtils.neverFunction; +import static net.xyzsd.dichotomy.TestUtils.neverSupplier; import static org.junit.jupiter.api.Assertions.*; @@ -44,19 +43,6 @@ class ResultTest { static final Function> fnERR_FLAT = (e) -> Result.ofErr( fnERR_VAL ); - // TODO: needs to be in a common util class; copied from EitherTest - // 'never' types: check if implementation calls a supplier / function when it doesn't need to - // Using rawtypes is easier. if the cast fails... we have an error anyway - @SuppressWarnings("rawtypes") - public static final Supplier NEVERSUPPLIER = () -> { - throw new IllegalStateException( "NEVERSUPPLIER::get invoked!" ); - }; - - @SuppressWarnings("rawtypes") - public static final Function NEVERFUNCTION = (x) -> { - throw new IllegalStateException( "NEVERFUNCTION::apply invoked!" ); - }; - @Test void ofOK() { @@ -67,19 +53,19 @@ void ofOK() { } @Test - void testOfOK() { - final Result result = Result.ofOK(); + void ofOK_NONE() { + final Result result = Result.ofOK(); assertEquals( OK.class, result.getClass() ); - assertEquals( new None(), ((OK) result).get() ); + assertEquals( new Empty(), ((OK) result).get() ); } @Test void ofNullable() { // this test assumes that other method tests pass - Result resultFromNull = Result.ofNullable( null ); + Result resultFromNull = Result.ofNullable( null ); assertTrue( resultFromNull instanceof Err ); - Result resultNotNull = Result.ofNullable( OK_VALUE ); + Result resultNotNull = Result.ofNullable( OK_VALUE ); assertTrue( resultNotNull instanceof OK); } @@ -87,10 +73,10 @@ void ofNullable() { @Test void from() { // this test assumes that other method tests pass - Result resultFromEmpty = Result.from( Optional.empty() ); + Result resultFromEmpty = Result.from( Optional.empty() ); assertTrue( resultFromEmpty instanceof Err ); - Result result = Result.from( Optional.of( OK_VALUE ) ); + Result result = Result.from( Optional.of( OK_VALUE ) ); assertTrue( result instanceof OK ); } @@ -104,6 +90,26 @@ void ofErr() { } + @Test + void ok() { + Result resultERR = Result.ofErr( ERR_VALUE ); + assertTrue( resultERR.ok().isEmpty() ); + + Result resultOK = Result.ofOK( OK_VALUE ); + assertTrue( resultOK.ok().isPresent() ); + assertEquals( resultOK.ok().get(), OK_VALUE ); + } + + @Test + void err() { + Result resultERR = Result.ofErr( ERR_VALUE ); + assertTrue( resultERR.err().isPresent() ); + assertEquals( resultERR.err().get(), ERR_VALUE ); + + Result resultOK = Result.ofOK( OK_VALUE ); + assertTrue( resultOK.err().isEmpty() ); + } + @Test void swap() { Result left = Result.ofOK( "ok!" ); @@ -123,16 +129,23 @@ void swap() { @Test void biMatch() { - SingleUseConsumer okConsumer = new SingleUseConsumer<>(); - SingleUseConsumer errConsumer = new SingleUseConsumer<>(); - - OK.biMatch( okConsumer, errConsumer ); - assertTrue( okConsumer.wasActivatedOnce() ); - assertFalse( errConsumer.wasActivatedOnce() ); + { + SingleUseConsumer okConsumer = new SingleUseConsumer<>(); + SingleUseConsumer errConsumer = new SingleUseConsumer<>(); + // + OK.biMatch( okConsumer, errConsumer ); + assertTrue( okConsumer.usedJustOnce() ); + assertTrue( errConsumer.neverUsed() ); + } - ERR.biMatch( okConsumer, errConsumer ); - assertTrue( okConsumer.wasActivatedOnce() ); - assertTrue( errConsumer.wasActivatedOnce() ); + { + SingleUseConsumer okConsumer = new SingleUseConsumer<>(); + SingleUseConsumer errConsumer = new SingleUseConsumer<>(); + // + ERR.biMatch( okConsumer, errConsumer ); + assertTrue( okConsumer.neverUsed() ); + assertTrue( errConsumer.usedJustOnce() ); + } } @Test @@ -153,8 +166,8 @@ void biMap() { } ); // unused side must not apply function - assertDoesNotThrow( () -> OK.biMap( fnOK, NEVERFUNCTION ) ); - assertDoesNotThrow( () -> ERR.biMap( NEVERFUNCTION, fnERR2STR ) ); + assertDoesNotThrow( () -> OK.biMap( fnOK, neverFunction() ) ); + assertDoesNotThrow( () -> ERR.biMap( neverFunction(), fnERR2STR ) ); } @Test @@ -177,22 +190,22 @@ void biFlatMap() { ERR.biFlatMap( fnOK_FLAT, x -> null ); } ); - assertDoesNotThrow( () -> OK.biFlatMap( fnOK_FLAT, NEVERFUNCTION ) ); - assertDoesNotThrow( () -> ERR.biFlatMap( NEVERFUNCTION, fnERR_FLAT ) ); + assertDoesNotThrow( () -> OK.biFlatMap( fnOK_FLAT, neverFunction() ) ); + assertDoesNotThrow( () -> ERR.biFlatMap( neverFunction(), fnERR_FLAT ) ); } @Test void fold() { - assertEquals( fnOK_VAL, OK.fold( fnOK, NEVERFUNCTION ) ); - assertEquals( fnERR_VAL, ERR.fold( NEVERFUNCTION, fnERR2ERR ) ); + assertEquals( fnOK_VAL, OK.fold( fnOK, neverFunction() ) ); + assertEquals( fnERR_VAL, ERR.fold( neverFunction(), fnERR2ERR ) ); // null-returning function test assertThrows( NullPointerException.class, () -> { - OK.fold( x -> null, NEVERFUNCTION ); + OK.fold( x -> null, neverFunction() ); } ); assertThrows( NullPointerException.class, () -> { - ERR.fold( NEVERFUNCTION, x -> null ); + ERR.fold( neverFunction(), x -> null ); } ); } @@ -206,11 +219,11 @@ void stream() { @Test void filter() { // Err->Err - assertEquals( ERR, ERR.filter( x -> true, NEVERFUNCTION ) ); - assertEquals( ERR, ERR.filter( x -> false, NEVERFUNCTION ) ); + assertEquals( ERR, ERR.filter( x -> true, neverFunction() ) ); + assertEquals( ERR, ERR.filter( x -> false, neverFunction() ) ); // OK->OK if predicate matches - assertEquals( OK, OK.filter( x -> true, NEVERFUNCTION ) ); + assertEquals( OK, OK.filter( x -> true, neverFunction() ) ); // OK->Err if predicate doesn't match assertEquals( Result.ofErr( fnSTR2ERR_VAL ), OK.filter( x -> false, fnSTR2ERR ) ); @@ -218,18 +231,20 @@ void filter() { @Test void match() { - SingleUseConsumer consumer = new SingleUseConsumer<>(); - OK.match( consumer ); - assertTrue( consumer.wasActivatedOnce() ); - ERR.match( consumer ); - assertTrue( consumer.wasActivatedOnce() ); + SingleUseConsumer okConsumer = new SingleUseConsumer<>(); + OK.match( okConsumer ); + assertTrue( okConsumer.usedJustOnce() ); + + SingleUseConsumer errConsumer = new SingleUseConsumer<>(); + ERR.match( errConsumer ); + assertTrue( errConsumer.neverUsed() ); } @Test void map() { assertEquals( Result.ofOK( fnOK_VAL ), OK.map( fnOK ) ); assertEquals( ERR, ERR.map( fnOK ) ); - assertDoesNotThrow( () -> {ERR.map( NEVERFUNCTION );} ); + assertDoesNotThrow( () -> {ERR.map( neverFunction() );} ); assertThrows( NullPointerException.class, () -> OK.map( x -> null ) ); } @@ -237,17 +252,17 @@ void map() { void flatMap() { assertEquals( Result.ofOK( fnOK_VAL ), OK.flatMap( fnOK_FLAT ) ); assertEquals( ERR, ERR.flatMap( fnOK_FLAT ) ); - assertDoesNotThrow( () -> {ERR.flatMap( NEVERFUNCTION );} ); + assertDoesNotThrow( () -> {ERR.flatMap( neverFunction() );} ); assertThrows( NullPointerException.class, () -> OK.flatMap( x -> null ) ); } @Test void matches() { - assertTrue( OK.matches( x -> true ) ); - assertFalse( OK.matches( x -> false ) ); + assertTrue( OK.ifPredicate( x -> true ) ); + assertFalse( OK.ifPredicate( x -> false ) ); - assertFalse( ERR.matches( x -> true ) ); - assertFalse( ERR.matches( x -> false ) ); + assertFalse( ERR.ifPredicate( x -> true ) ); + assertFalse( ERR.ifPredicate( x -> false ) ); } @Test @@ -264,13 +279,13 @@ void orElse() { @Test void orElseGet() { - assertEquals( OK_VALUE, OK.orElseGet( NEVERSUPPLIER ) ); - assertEquals( "orElse-alternate", ERR.orElseGet( () -> "orElse-alternate" ) ); + assertEquals( OK_VALUE, OK.orElse( neverSupplier() ) ); + assertEquals( "orElse-alternate", ERR.orElse( () -> "orElse-alternate" ) ); } @Test void recover() { - assertEquals( OK_VALUE, OK.recover( NEVERFUNCTION ) ); + assertEquals( OK_VALUE, OK.recover( neverFunction() ) ); assertEquals( fnERR_VAL.getMessage(), ERR.recover( fnERR2STR ) ); } @@ -283,18 +298,20 @@ void streamErr() { @Test void matchErr() { - SingleUseConsumer consumer = new SingleUseConsumer<>(); - ERR.matchErr( consumer ); - assertTrue( consumer.wasActivatedOnce() ); - OK.matchErr( consumer ); - assertTrue( consumer.wasActivatedOnce() ); + SingleUseConsumer errConsumer = new SingleUseConsumer<>(); + ERR.matchErr( errConsumer ); + assertTrue( errConsumer.usedJustOnce() ); + + SingleUseConsumer okConsumer = new SingleUseConsumer<>(); + OK.matchErr( okConsumer ); + assertTrue( okConsumer.neverUsed() ); } @Test void mapErr() { assertEquals( Result.ofErr( fnERR_VAL ), ERR.mapErr( fnERR2ERR ) ); assertEquals( OK, OK.mapErr( fnERR2ERR ) ); - assertDoesNotThrow( () -> {OK.mapErr( NEVERFUNCTION );} ); + assertDoesNotThrow( () -> {OK.mapErr( neverFunction() );} ); assertThrows( NullPointerException.class, () -> ERR.mapErr( x -> null ) ); } @@ -302,17 +319,17 @@ void mapErr() { void flatMapErr() { assertEquals( Result.ofErr( fnERR_VAL ), ERR.flatMapErr( fnERR_FLAT ) ); assertEquals( OK, OK.flatMapErr( fnERR_FLAT ) ); - assertDoesNotThrow( () -> {OK.flatMapErr( NEVERFUNCTION );} ); + assertDoesNotThrow( () -> {OK.flatMapErr( neverFunction() );} ); assertThrows( NullPointerException.class, () -> ERR.flatMapErr( x -> null ) ); } @Test void matchesErr() { - assertTrue( ERR.matchesErr( x -> true ) ); - assertFalse( ERR.matchesErr( x -> false ) ); + assertTrue( ERR.ifPredicateErr( x -> true ) ); + assertFalse( ERR.ifPredicateErr( x -> false ) ); - assertFalse( OK.matchesErr( x -> true ) ); - assertFalse( OK.matchesErr( x -> false ) ); + assertFalse( OK.ifPredicateErr( x -> true ) ); + assertFalse( OK.ifPredicateErr( x -> false ) ); } @Test @@ -329,13 +346,13 @@ void orElseErr() { @Test void orElseGetErr() { - assertEquals( ERR_VALUE, ERR.orElseGetErr( NEVERSUPPLIER ) ); - assertEquals( fnERR_VAL, OK.orElseGetErr( () -> fnERR_VAL ) ); + assertEquals( ERR_VALUE, ERR.orElseErr( neverSupplier() ) ); + assertEquals( fnERR_VAL, OK.orElseErr( () -> fnERR_VAL ) ); } @Test void forfeit() { - assertEquals( ERR_VALUE, ERR.forfeit( NEVERFUNCTION ) ); + assertEquals( ERR_VALUE, ERR.forfeit( neverFunction() ) ); assertEquals( fnSTR2ERR_VAL, OK.forfeit( fnSTR2ERR ) ); } @@ -347,7 +364,7 @@ void and() { @Test void testAnd() { - assertEquals( ERR, ERR.and( NEVERSUPPLIER ) ); + assertEquals( ERR, ERR.and( neverSupplier() ) ); assertEquals( Result.ofOK( "nextResult" ), @@ -371,7 +388,7 @@ void or() { @Test void testOr() { - assertEquals( OK, OK.or( NEVERSUPPLIER ) ); + assertEquals( OK, OK.or( neverSupplier() ) ); assertEquals( Result.ofErr( "nextResult" ), @@ -382,6 +399,7 @@ void testOr() { NullPointerException.class, () -> { ERR.or( () -> null ); } ); + } @Test @@ -418,15 +436,16 @@ void expect() { @Test void orThrowWrapped() { - assertDoesNotThrow( () -> OK.orThrowWrapped( NEVERFUNCTION ) ); - assertEquals( OK_VALUE, OK.orThrowWrapped(NEVERFUNCTION) ); + final String s = assertDoesNotThrow( () -> OK.getOrThrowMapped( neverFunction() ) ); + assertEquals( OK_VALUE, s ); + assertThrows( IOException.class, - () -> ERR.orThrowWrapped( IOException::new ) + () -> ERR.getOrThrowMapped( IOException::new ) ); assertThrows( RuntimeException.class, - () -> ERR.orThrow( RuntimeException::new ) + () -> ERR.getOrThrow( RuntimeException::new ) ); // but also see this @@ -434,11 +453,11 @@ void orThrowWrapped() { final Result result = Result.ofErr( MESSAGE ); assertThrows( IOException.class, - () -> result.orThrowWrapped( IOException::new ) + () -> result.getOrThrowMapped( IOException::new ) ); try { - result.orThrowWrapped( IOException::new ); + result.getOrThrowMapped( IOException::new ); } catch(IOException e) { assertEquals( MESSAGE, e.getMessage() ); } @@ -446,16 +465,17 @@ void orThrowWrapped() { @Test void orThrow() { - assertDoesNotThrow( () -> OK.orThrow( NEVERSUPPLIER ) ); - assertEquals( OK_VALUE, OK.orThrow(NEVERSUPPLIER) ); + final String s = assertDoesNotThrow( () -> OK.getOrThrow( neverSupplier() ) ); + + assertEquals( OK_VALUE, s ); assertThrows( IOException.class, - () -> ERR.orThrow( IOException::new ) + () -> ERR.getOrThrow( IOException::new ) ); assertThrows( RuntimeException.class, - () -> ERR.orThrow( RuntimeException::new ) + () -> ERR.getOrThrow( RuntimeException::new ) ); } diff --git a/src/test/java/net/xyzsd/dichotomy/TestUtils.java b/src/test/java/net/xyzsd/dichotomy/TestUtils.java new file mode 100644 index 0000000..736a6ee --- /dev/null +++ b/src/test/java/net/xyzsd/dichotomy/TestUtils.java @@ -0,0 +1,117 @@ +package net.xyzsd.dichotomy; + +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Supplier; + +// Test utilities +public interface TestUtils { + + // TODO: needs to be in a common util class; copied from EitherTest + // 'never' types: check if implementation calls a supplier / function when it doesn't need to + // Using rawtypes is easier. if the cast fails... we have an error anyway + + // A supplier that should NOT be invoked + Supplier NEVERSUPPLIER = () -> { + throw new IllegalStateException( "NEVERSUPPLIER::get invoked!" ); + }; + + // A function that should NOT be invoked + Function NEVERFUNCTION = (x) -> { + throw new IllegalStateException( "NEVERFUNCTION::apply invoked!" ); + }; + + // a runner that should NOT be invoked + Runnable NEVERRUNNABLE = () -> { + throw new IllegalStateException( "NEVERRUN was invoked!" ); + }; + + // a consumer that should NOT be invoked + Consumer NEVERCONSUMER = (x) -> { + throw new IllegalStateException( "NEVERCONSUMER was invoked!" ); + }; + + + // coerced types of above + @SuppressWarnings("unchecked") + static Supplier neverSupplier() { + return (Supplier) NEVERSUPPLIER; + } + + @SuppressWarnings("unchecked") + static Function neverFunction() { + return (Function) NEVERFUNCTION; + } + + @SuppressWarnings("unchecked") + static Consumer neverConsumer() { + return (Consumer) NEVERCONSUMER; + } + + + // A Consumer that keeps track of the amount of times accept() was called. + // Should only be created once and then discarded. Do not reuse. + final class SingleUseConsumer implements Consumer { + + private final SingleUseRunnable runnable = new SingleUseRunnable(); + + @Override + public void accept(T t) { + runnable.run(); + } + + public boolean usedJustOnce() { + return runnable.usedJustOnce(); + } + + public int activationCount() { + return runnable.activationCount(); + } + + public boolean neverUsed() { + return runnable.neverUsed(); + } + } + + final class SingleUseRunnable implements Runnable { + // DO NOT query directly. Only use query() method + private int count = 0; + + + private static void verifyUnused(final int counter) { + if (counter < 0) { + throw new IllegalStateException( "ILLEGAL re-use of single use item after query. Create a new item!" ); + } + } + + public int activationCount() { + synchronized (this) { + int finalCount = count; + verifyUnused( finalCount ); + count = -1; + return finalCount; + } + } + + @Override + public void run() { + synchronized (this) { + verifyUnused(count); + count++; + } + } + + public boolean usedJustOnce() { + return (activationCount() == 1); + } + + + + public boolean neverUsed() { + return (activationCount() == 0); + } + } + +} diff --git a/src/test/java/net/xyzsd/dichotomy/result/Try2Test.java b/src/test/java/net/xyzsd/dichotomy/result/Try2Test.java deleted file mode 100644 index 643e5dd..0000000 --- a/src/test/java/net/xyzsd/dichotomy/result/Try2Test.java +++ /dev/null @@ -1,131 +0,0 @@ -package net.xyzsd.dichotomy.result; - -import net.xyzsd.dichotomy.trying.Try2; -import net.xyzsd.dichotomy.trying.function.ExFunction; -import net.xyzsd.dichotomy.trying.function.ExSupplier; -import net.xyzsd.dichotomy.trying.function.SpecExSupplier; -import org.junit.jupiter.api.Test; - -import java.io.IOException; -import java.util.function.Function; -import java.util.function.Supplier; - -import static org.junit.jupiter.api.Assertions.*; - -class Try2Test { - - private static final String OK_STRING = "ok!"; - private static final RuntimeException CANNOT_SUPPLY_RT = new RuntimeException( "Failure!" ); - private static final Exception CANNOT_SUPPLY_CHECKED = new Exception( "Failure!" ); - - private static final ArithmeticException ARITHMETIC_EXCEPTION = new ArithmeticException("Failure!"); - - private static final ExSupplier STRING_NEVERSUPPLIER = () -> { - throw new IllegalStateException( "!!should not execute!!" ); - }; - private static final ExSupplier STRING_SUPPLIER_OK = () -> OK_STRING; - private static final ExSupplier STRING_SUPPLIER_RUNTIME = () -> {throw CANNOT_SUPPLY_RT;}; - private static final ExSupplier STRING_SUPPLIER_CHECKED = () -> {throw CANNOT_SUPPLY_CHECKED;}; - - private static final SpecExSupplier X_SPEC_STRING_SUPPLIER_OK = () -> OK_STRING; - private static final SpecExSupplier X_SPEC_STRING_SUPPLIER_RUNTIME = () -> { - throw CANNOT_SUPPLY_RT; - }; - private static final SpecExSupplier X_SPEC_STRING_SUPPLIER_RUNTIME_AE = () -> { - throw ARITHMETIC_EXCEPTION; - }; - private static final SpecExSupplier X_SPEC_STRING_SUPPLIER_AE_AE = () -> { - throw ARITHMETIC_EXCEPTION; - }; - private static final SpecExSupplier X_SPEC_STRING_SUPPLIER_AE_RT = () -> { - throw CANNOT_SUPPLY_RT; - }; - private static final SpecExSupplier X_SPEC_STRING_SUPPLIER_CHECKED = () -> { - throw CANNOT_SUPPLY_CHECKED; - }; - - private static final IllegalArgumentException IAE_NEGATIVE = new IllegalArgumentException("negative argument!"); - private static final ExFunction XFN = i -> { - if(i >= 0) { - return String.valueOf(100*i ); - } else { - throw IAE_NEGATIVE; - } - }; - - - @Test - void basic() { - Try2 try2s = new Try2.OK<>("An OK String!"); - Try2 try2ec = new Try2.Err<>( new IOException("an IO exception") ); - Try2 try2er = new Try2.Err<>( new RuntimeException("a runtime exception") ); - - assertThrows(IOException.class, try2ec::orThrow); - assertThrows(RuntimeException.class, try2er::orThrow); - - } - - - - // TODO: everything below here needs to be redone for Try2 - - @Test - void ofSupplier() { - assertEquals( new Try2.OK<>( OK_STRING ), Try2.of( STRING_SUPPLIER_OK ) ); - assertEquals( new Try2.Err<>( CANNOT_SUPPLY_RT ), Try2.of( STRING_SUPPLIER_RUNTIME ) ); - assertEquals( new Try2.Err<>( CANNOT_SUPPLY_CHECKED ), Try2.of( STRING_SUPPLIER_CHECKED ) ); - } - - @Test - void fromSupplier() { - assertDoesNotThrow( () -> Try2.from( STRING_NEVERSUPPLIER ) ); - - Supplier> supplier; - - supplier = Try2.from( STRING_SUPPLIER_OK ); - assertNotNull( supplier ); - assertEquals( Try2.ofOK( OK_STRING ), supplier.get() ); - - supplier = Try2.from( STRING_SUPPLIER_RUNTIME ); - assertNotNull( supplier ); - assertEquals( Try2.ofErr( CANNOT_SUPPLY_RT ), supplier.get() ); - - supplier = Try2.from( STRING_SUPPLIER_CHECKED ); - assertNotNull( supplier ); - assertEquals( Try2.ofErr( CANNOT_SUPPLY_CHECKED ), supplier.get() ); - } - - - @Test - void ofSpecExSupplier() { - assertEquals( Try2.ofOK( OK_STRING ), Try2.of( X_SPEC_STRING_SUPPLIER_OK, RuntimeException.class ) ); - - assertEquals( Try2.ofErr( CANNOT_SUPPLY_RT ), Try2.of( X_SPEC_STRING_SUPPLIER_RUNTIME, RuntimeException.class ) ); - assertEquals( Try2.ofErr( ARITHMETIC_EXCEPTION ), Try2.of( X_SPEC_STRING_SUPPLIER_AE_AE, ArithmeticException.class ) ); - - // X_SPEC_STRING_SUPPLIER_RUNTIME_AE: We will catch (into a Result) any RuntimeException, which includes ArithmeticExceptions - assertEquals( Try2.ofErr( ARITHMETIC_EXCEPTION ), Try2.of( X_SPEC_STRING_SUPPLIER_RUNTIME_AE, RuntimeException.class ) ); - - // X_SPEC_STRING_SUPPLIER_AE_RT: we will handle ArithmeticExceptions, but any other RuntimeExceptions are not allowed and will throw - assertThrows( RuntimeException.class, () -> Try2.of( X_SPEC_STRING_SUPPLIER_AE_RT, ArithmeticException.class ) ); - - // the following line does not compile, and it should not compile! - //assertEquals( Result.ofErr( CANNOT_SUPPLY_RT ), Try2.of( X_SPEC_STRING_SUPPLIER_CHECKED, RuntimeException.class ) ); - } - - - @Test - void ofFn() { - assertEquals(Try2.ofOK("900"), Try2.of(9, XFN) ); - assertEquals(Try2.ofErr( IAE_NEGATIVE ), Try2.of(-9, XFN) ); - } - - @Test - void fromFn() { - assertDoesNotThrow( () -> Try2.from( in -> { throw new IllegalStateException(); } ) ); - - final Function> fn = Try2.from( XFN ); - assertEquals(Try2.ofOK("900"), fn.apply(9) ); - assertEquals(Try2.ofErr( IAE_NEGATIVE ), fn.apply(-9) ); - } -} \ 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 new file mode 100644 index 0000000..a720e91 --- /dev/null +++ b/src/test/java/net/xyzsd/dichotomy/trying/TryTest.java @@ -0,0 +1,644 @@ +package net.xyzsd.dichotomy.trying; + +import net.xyzsd.dichotomy.Empty; + +import static net.xyzsd.dichotomy.TestUtils.*; +import static net.xyzsd.dichotomy.trying.Try.OK; +import static net.xyzsd.dichotomy.trying.Try.Err; +import net.xyzsd.dichotomy.trying.function.ExFunction; +import net.xyzsd.dichotomy.trying.function.ExSupplier; +import net.xyzsd.dichotomy.trying.function.SpecExSupplier; +import org.junit.jupiter.api.Test; + +import net.xyzsd.dichotomy.TestUtils.SingleUseConsumer; + +import java.io.IOException; +import java.util.NoSuchElementException; +import java.util.function.Function; +import java.util.function.Supplier; + +import static org.junit.jupiter.api.Assertions.*; + +class TryTest { + + + // standard test values + static final String OK_VALUE = "[ok]"; + static final String ERR_VALUE_MESSAGE = "*no such element*"; + static final RuntimeException ERR_VALUE = new NoSuchElementException( ERR_VALUE_MESSAGE ); + + + // preface with "TEST" to avoid confusion between implementation classes + static final Try TEST_OK = Try.ofOK( OK_VALUE ); + static final Try TEST_ERR = Try.ofErr( ERR_VALUE ); + + + // standard mapping functions for testing + static final String fnOK_VAL = "*MAPPED_STRING"; + static final RuntimeException fnERR_VAL = new NoSuchElementException( "*MAPPED_" + ERR_VALUE_MESSAGE ); + + // mapping functions + static final Function fnOK = s -> fnOK_VAL; + static final Function fnERR2ERR = ex -> fnERR_VAL; + static final Function fnERR2STR = ex -> fnERR_VAL.getMessage(); + + static final String AEX_MESSAGE = "mapped_via_fn"; + static final RuntimeException AEX = new ArithmeticException(AEX_MESSAGE); + static final Function fnEX2AEX = (ex) -> AEX; + static final Function fnSTR2AEX = s -> AEX; + + + // flat-mapping functions + static final Function> fnOK_FLAT = (x) -> Try.ofOK( fnOK_VAL ); + static final Function> fnERR_FLAT = (e) -> Try.ofErr( fnERR_VAL ); + + + private static final RuntimeException CANNOT_SUPPLY_RT = new RuntimeException( "Failure!" ); + private static final Exception CANNOT_SUPPLY_CHECKED = new Exception( "Failure!" ); + + private static final ArithmeticException ARITHMETIC_EXCEPTION = new ArithmeticException("Failure!"); + + + private static final ExSupplier STRING_SUPPLIER_OK = () -> OK_VALUE; + private static final ExSupplier STRING_SUPPLIER_RUNTIME = () -> {throw CANNOT_SUPPLY_RT;}; + private static final ExSupplier STRING_SUPPLIER_CHECKED = () -> {throw CANNOT_SUPPLY_CHECKED;}; + + private static final SpecExSupplier X_SPEC_STRING_SUPPLIER_OK = () -> OK_VALUE; + private static final SpecExSupplier X_SPEC_STRING_SUPPLIER_RUNTIME = () -> { + throw CANNOT_SUPPLY_RT; + }; + private static final SpecExSupplier X_SPEC_STRING_SUPPLIER_RUNTIME_AE = () -> { + throw ARITHMETIC_EXCEPTION; + }; + private static final SpecExSupplier X_SPEC_STRING_SUPPLIER_AE_AE = () -> { + throw ARITHMETIC_EXCEPTION; + }; + private static final SpecExSupplier X_SPEC_STRING_SUPPLIER_AE_RT = () -> { + throw CANNOT_SUPPLY_RT; + }; + private static final SpecExSupplier X_SPEC_STRING_SUPPLIER_CHECKED = () -> { + throw CANNOT_SUPPLY_CHECKED; + }; + + private static final IllegalArgumentException IAE_NEGATIVE = new IllegalArgumentException("negative argument!"); + private static final ExFunction XFN = i -> { + if(i >= 0) { + return String.valueOf(100*i ); + } else { + throw IAE_NEGATIVE; + } + }; + + + @Test + void basic() { + Try try2s = new Try.OK<>("An OK String!"); + Try try2ec = new Try.Err<>( new IOException("an IO exception") ); + Try try2er = new Try.Err<>( new RuntimeException("a runtime exception") ); + + assertThrows(IOException.class, try2ec::expect ); + assertThrows(RuntimeException.class, try2er::expect ); + + } + + + + // TODO: everything below here needs to be redone for Try2 + + @Test + void ofSupplier() { + assertEquals( new Try.OK<>( OK_VALUE ), Try.of( STRING_SUPPLIER_OK ) ); + assertEquals( new Try.Err<>( CANNOT_SUPPLY_RT ), Try.of( STRING_SUPPLIER_RUNTIME ) ); + assertEquals( new Try.Err<>( CANNOT_SUPPLY_CHECKED ), Try.of( STRING_SUPPLIER_CHECKED ) ); + } + + @Test + void fromSupplier() { + assertDoesNotThrow( () -> Try.from( neverSupplier() ) ); + + Supplier> supplier; + + supplier = Try.from( STRING_SUPPLIER_OK ); + assertNotNull( supplier ); + assertEquals( Try.ofOK( OK_VALUE ), supplier.get() ); + + supplier = Try.from( STRING_SUPPLIER_RUNTIME ); + assertNotNull( supplier ); + assertEquals( Try.ofErr( CANNOT_SUPPLY_RT ), supplier.get() ); + + supplier = Try.from( STRING_SUPPLIER_CHECKED ); + assertNotNull( supplier ); + assertEquals( Try.ofErr( CANNOT_SUPPLY_CHECKED ), supplier.get() ); + } + + + @Test + void ofSpecExSupplier() { + assertEquals( Try.ofOK( OK_VALUE ), Try.of( X_SPEC_STRING_SUPPLIER_OK, RuntimeException.class ) ); + + assertEquals( Try.ofErr( CANNOT_SUPPLY_RT ), Try.of( X_SPEC_STRING_SUPPLIER_RUNTIME, RuntimeException.class ) ); + assertEquals( Try.ofErr( ARITHMETIC_EXCEPTION ), Try.of( X_SPEC_STRING_SUPPLIER_AE_AE, ArithmeticException.class ) ); + + // X_SPEC_STRING_SUPPLIER_RUNTIME_AE: We will catch (into a Result) any RuntimeException, which includes ArithmeticExceptions + assertEquals( Try.ofErr( ARITHMETIC_EXCEPTION ), Try.of( X_SPEC_STRING_SUPPLIER_RUNTIME_AE, RuntimeException.class ) ); + + // X_SPEC_STRING_SUPPLIER_AE_RT: we will handle ArithmeticExceptions, but any other RuntimeExceptions are not allowed and will throw + assertThrows( RuntimeException.class, () -> Try.of( X_SPEC_STRING_SUPPLIER_AE_RT, ArithmeticException.class ) ); + + // the following line does not compile, and it should not compile! + //assertEquals( Try2.ofErr( CANNOT_SUPPLY_RT ), Try2.of( X_SPEC_STRING_SUPPLIER_CHECKED, RuntimeException.class ) ); + } + + + @Test + void ofFn() { + assertEquals( Try.ofOK("900"), Try.of(9, XFN) ); + assertEquals( Try.ofErr( IAE_NEGATIVE ), Try.of(-9, XFN) ); + } + + @Test + void fromFn() { + assertDoesNotThrow( () -> Try.from( in -> { throw new IllegalStateException(); } ) ); + + final Function> fn = Try.from( XFN ); + assertEquals( Try.ofOK("900"), fn.apply(9) ); + assertEquals( Try.ofErr( IAE_NEGATIVE ), fn.apply(-9) ); + } + + + + + ///////////////////////////////////////////////////////////////////////// + // test methods similar to Result + ///////////////////////////////////////////////////////////////////////// + + + + @Test + void ofOK() { + // testing the OK_VALUE created as a static + assertEquals( OK.class, TEST_OK.getClass() ); + assertEquals( OK_VALUE, ((OK) TEST_OK).get() ); + // ensure null values disallowed + assertThrows( NullPointerException.class, () -> Try.ofOK( null ) ); + } + + @Test + void ofOK_NONE() { + final Try result = Try.ofOK(); + assertEquals( Try.OK.class, result.getClass() ); + assertEquals( new Empty(), ((Try.OK) result).get() ); + } + + + + + @Test + void ofErr() { + Try result = Try.ofErr( ERR_VALUE ); + assertEquals( Err.class, result.getClass() ); + assertEquals( ERR_VALUE, ((Err) result).get() ); + assertEquals( result, TEST_ERR ); + assertThrows( NullPointerException.class, () -> {Try.ofErr( null );} ); + } + + + @Test + void ok() { + Try resultERR = Try.ofErr( ERR_VALUE ); + assertTrue( resultERR.ok().isEmpty() ); + + Try resultOK = Try.ofOK( OK_VALUE ); + assertTrue( resultOK.ok().isPresent() ); + assertEquals( resultOK.ok().get(), OK_VALUE ); + } + + @Test + void err() { + Try resultERR = Try.ofErr( ERR_VALUE ); + assertTrue( resultERR.err().isPresent() ); + assertEquals( resultERR.err().get(), ERR_VALUE ); + + Try resultOK = Try.ofOK( OK_VALUE ); + assertTrue( resultOK.err().isEmpty() ); + } + + + + @Test + void biMatch() { + { + SingleUseConsumer okConsumer = new SingleUseConsumer<>(); + SingleUseConsumer errConsumer = new SingleUseConsumer<>(); + // + TEST_OK.biMatch( okConsumer, errConsumer ); + assertTrue( okConsumer.usedJustOnce() ); + assertTrue( errConsumer.neverUsed() ); + } + + { + SingleUseConsumer okConsumer = new SingleUseConsumer<>(); + SingleUseConsumer errConsumer = new SingleUseConsumer<>(); + // + TEST_ERR.biMatch( okConsumer, errConsumer ); + assertTrue( okConsumer.neverUsed() ); + assertTrue( errConsumer.usedJustOnce() ); + } + } + + @Test + void biMap() { + Try mapped = TEST_OK.biMap( fnOK, fnEX2AEX ); + assertTrue( mapped.contains( fnOK_VAL ) ); + + mapped = TEST_ERR.biMap( fnOK, fnEX2AEX ); + assertTrue( mapped.containsErr( AEX ) ); + + // functions must not return null + assertThrows( NullPointerException.class, () -> { + TEST_OK.biMap( x -> null, fnEX2AEX ); + } ); + + assertThrows( NullPointerException.class, () -> { + TEST_ERR.biMap( fnOK, x -> null ); + } ); + + // unused side must not apply function + assertDoesNotThrow( () -> TEST_OK.biMap( fnOK, neverFunction() ) ); + assertDoesNotThrow( () -> TEST_ERR.biMap( neverFunction(), fnEX2AEX ) ); + } + + @Test + void biFlatMap() { + assertEquals( + Try.ofOK( fnOK_VAL ), + TEST_OK.biFlatMap( fnOK_FLAT, fnERR_FLAT ) + ); + + assertEquals( + Try.ofErr( fnERR_VAL ), + TEST_ERR.biFlatMap( fnOK_FLAT, fnERR_FLAT ) + ); + + assertThrows( NullPointerException.class, () -> { + TEST_OK.biFlatMap( x -> null, fnERR_FLAT ); + } ); + + assertThrows( NullPointerException.class, () -> { + TEST_ERR.biFlatMap( fnOK_FLAT, x -> null ); + } ); + + assertDoesNotThrow( () -> TEST_OK.biFlatMap( fnOK_FLAT, neverFunction() ) ); + assertDoesNotThrow( () -> TEST_ERR.biFlatMap( neverFunction(), fnERR_FLAT ) ); + } + + @Test + void fold() { + assertEquals( fnOK_VAL, TEST_OK.fold( fnOK, neverFunction() ) ); + assertEquals( fnERR_VAL, TEST_ERR.fold( neverFunction(), fnERR2ERR ) ); + + // null-returning function test + assertThrows( NullPointerException.class, () -> { + TEST_OK.fold( x -> null, neverFunction() ); + } ); + + assertThrows( NullPointerException.class, () -> { + TEST_ERR.fold( neverFunction(), x -> null ); + } ); + } + + @Test + void stream() { + assertEquals( 1, TEST_OK.stream().count() ); + assertTrue( TEST_OK.stream().allMatch( OK_VALUE::equals ) ); + assertEquals( 0, TEST_ERR.stream().count() ); + } + + @Test + void filter() { + // Err->Err + assertEquals( TEST_ERR, TEST_ERR.filter( x -> true, neverFunction() ) ); + assertEquals( TEST_ERR, TEST_ERR.filter( x -> false, neverFunction() ) ); + + // OK->OK if predicate matches + assertEquals( TEST_OK, TEST_OK.filter( x -> true, neverFunction() ) ); + + // OK->Err if predicate doesn't match + assertEquals( Try.ofErr( AEX ), TEST_OK.filter( x -> false, fnSTR2AEX ) ); + } + + @Test + void match() { + SingleUseConsumer okConsumer = new SingleUseConsumer<>(); + TEST_OK.match( okConsumer ); + assertTrue( okConsumer.usedJustOnce() ); + + SingleUseConsumer errConsumer = new SingleUseConsumer<>(); + TEST_ERR.match( errConsumer ); + assertTrue( errConsumer.neverUsed() ); + } + + @Test + void map() { + assertEquals( Try.ofOK( fnOK_VAL ), TEST_OK.map( fnOK ) ); + assertEquals( TEST_ERR, TEST_ERR.map( fnOK ) ); + assertDoesNotThrow( () -> {TEST_ERR.map( neverFunction() );} ); + assertThrows( NullPointerException.class, () -> TEST_OK.map( x -> null ) ); + } + + @Test + void flatMap() { + assertEquals( Try.ofOK( fnOK_VAL ), TEST_OK.flatMap( fnOK_FLAT ) ); + assertEquals( TEST_ERR, TEST_ERR.flatMap( fnOK_FLAT ) ); + assertDoesNotThrow( () -> {TEST_ERR.flatMap( neverFunction() );} ); + assertThrows( NullPointerException.class, () -> TEST_OK.flatMap( x -> null ) ); + } + + @Test + void matches() { + assertTrue( TEST_OK.ifPredicate( x -> true ) ); + assertFalse( TEST_OK.ifPredicate( x -> false ) ); + + assertFalse( TEST_ERR.ifPredicate( x -> true ) ); + assertFalse( TEST_ERR.ifPredicate( x -> false ) ); + } + + @Test + void contains() { + assertTrue( TEST_OK.contains( OK_VALUE ) ); + assertFalse( TEST_OK.contains( "" ) ); + } + + @Test + void orElse() { + assertEquals( OK_VALUE, TEST_OK.orElse( "orElse-alternate" ) ); + assertEquals( "orElse-alternate", TEST_ERR.orElse( "orElse-alternate" ) ); + } + + @Test + void orElseGet() { + assertEquals( OK_VALUE, TEST_OK.orElse( neverSupplier() ) ); + assertEquals( "orElse-alternate", TEST_ERR.orElse( () -> "orElse-alternate" ) ); + } + + @Test + void recover() { + assertEquals( OK_VALUE, TEST_OK.recover( neverFunction() ) ); + assertEquals( fnERR_VAL.getMessage(), TEST_ERR.recover( fnERR2STR ) ); + } + + @Test + void streamErr() { + assertEquals( 1, TEST_ERR.streamErr().count() ); + assertTrue( TEST_ERR.streamErr().allMatch( ERR_VALUE::equals ) ); + assertEquals( 0, TEST_OK.streamErr().count() ); + } + + @Test + void matchErr() { + SingleUseConsumer errConsumer = new SingleUseConsumer<>(); + TEST_ERR.matchErr( errConsumer ); + assertTrue( errConsumer.usedJustOnce() ); + + SingleUseConsumer okConsumer = new SingleUseConsumer<>(); + TEST_OK.matchErr( okConsumer ); + assertTrue( okConsumer.neverUsed() ); + } + + @Test + void mapErr() { + assertEquals( Try.ofErr( fnERR_VAL ), TEST_ERR.mapErr( fnERR2ERR ) ); + assertEquals( TEST_OK, TEST_OK.mapErr( fnERR2ERR ) ); + assertDoesNotThrow( () -> {TEST_OK.mapErr( neverFunction() );} ); + assertThrows( NullPointerException.class, () -> TEST_ERR.mapErr( x -> null ) ); + } + + @Test + void flatMapErr() { + assertEquals( Try.ofErr( fnERR_VAL ), TEST_ERR.flatMapErr( fnERR_FLAT ) ); + assertEquals( TEST_OK, TEST_OK.flatMapErr( fnERR_FLAT ) ); + assertDoesNotThrow( () -> {TEST_OK.flatMapErr( neverFunction() );} ); + assertThrows( NullPointerException.class, () -> TEST_ERR.flatMapErr( x -> null ) ); + } + + @Test + void matchesErr() { + assertTrue( TEST_ERR.ifPredicateErr( x -> true ) ); + assertFalse( TEST_ERR.ifPredicateErr( x -> false ) ); + + assertFalse( TEST_OK.ifPredicateErr( x -> true ) ); + assertFalse( TEST_OK.ifPredicateErr( x -> false ) ); + } + + @Test + void containsErr() { + assertTrue( TEST_ERR.containsErr( ERR_VALUE ) ); + assertFalse( TEST_ERR.containsErr( new RuntimeException( "?" ) ) ); + } + + @Test + void orElseErr() { + assertEquals( ERR_VALUE, TEST_ERR.orElseErr( new RuntimeException( "*invalid*" ) ) ); + assertEquals( fnERR_VAL, TEST_OK.orElseErr( fnERR_VAL ) ); + } + + @Test + void orElseErr_Supplier() { + assertEquals( ERR_VALUE, TEST_ERR.orElseErr( neverSupplier() ) ); + assertEquals( fnERR_VAL, TEST_OK.orElseErr( () -> fnERR_VAL ) ); + } + + @Test + void forfeit() { + assertEquals( ERR_VALUE, TEST_ERR.forfeit( neverFunction() ) ); + assertEquals( AEX, TEST_OK.forfeit( fnSTR2AEX ) ); + } + + @Test + void and() { + assertEquals( TEST_ERR, TEST_ERR.and( Try.ofOK( "nextResult" ) ) ); + assertEquals( Try.ofOK( "nextResult" ), TEST_OK.and( Try.ofOK( "nextResult" ) ) ); + } + + @Test + void testAnd() { + assertEquals( TEST_ERR, TEST_ERR.and( neverSupplier() ) ); + + assertEquals( + Try.ofOK( "nextResult" ), + TEST_OK.and( () -> Try.ofOK( "nextResult" ) ) + ); + + assertThrows( + NullPointerException.class, + () -> { TEST_OK.and( () -> null ); } + ); + } + + @Test + void or() { + assertEquals( TEST_OK, TEST_OK.or( Try.ofOK( "nextResult" ) ) ); + assertEquals( + Try.ofErr( AEX ), + TEST_ERR.or( Try.ofErr( AEX ) ) + ); + } + + @Test + void testOr() { + assertEquals( TEST_OK, TEST_OK.or( neverSupplier() ) ); + + assertEquals( + Try.ofErr( AEX ), + TEST_ERR.or( () -> Try.ofErr( AEX ) ) + ); + + assertThrows( + NullPointerException.class, + () -> { TEST_ERR.or( () -> null ); } + ); + } + + + @Test + void orThrow() { + final String s = assertDoesNotThrow( () -> TEST_OK.getOrThrow( neverFunction() ) ); + assertEquals( OK_VALUE, s ); + assertThrows( + IOException.class, + () -> TEST_ERR.getOrThrow( IOException::new ) + ); + assertThrows( + RuntimeException.class, + () -> TEST_ERR.getOrThrow( RuntimeException::new ) + ); + + // but also see this + final String MESSAGE = "My Error Message"; + final Try result = Try.ofErr( new RuntimeException(MESSAGE) ); + assertThrows( + IOException.class, + () -> result.getOrThrow( IOException::new ) + ); + + try { + result.getOrThrow( IOException::new ); + } catch(IOException e) { + assertEquals( MESSAGE, e.getCause().getMessage() ); + } + + final Try resultChecked = Try.ofErr( new IOException(MESSAGE) ); + + // resultChecked.expect(); // must be wrapped in a try + try { + resultChecked.expect(); + } catch (IOException e) { + assertEquals( MESSAGE, e.getMessage() ); + } + + + } + + + @Test + void expect() { + assertEquals( OK_VALUE, TEST_OK.expect() ); + assertDoesNotThrow( TEST_OK::expect ); + + assertThrows( RuntimeException.class, + TEST_ERR::expect ); + + final String MESSAGE = "My Error Message"; + final Try resultChecked = Try.ofErr( new IOException(MESSAGE) ); + + } + + @Test + void monadicLaws() { + // NOTE: the terms 'left identity' and 'right identity' below, are general terms for monad properties, + // for ease of applying functions, we will use a new results type: Try2 + final String MTEST_OK_VALUE = "monadic-laws-test-ok-value"; + final RuntimeException MTEST_ERR_VALUE = new RuntimeException("666"); // because why not + final Try MTEST_OK = Try.ofOK(MTEST_OK_VALUE); + final Try MTEST_ERR = Try.ofErr( MTEST_ERR_VALUE ); + + // FIRST LAW: LEFT IDENTITY + // Simply: data is wrapped, not manipulated/changed. + // + // Given a Result with a value in it and a function that takes the same type of value and + // returns the same type of Result, then applying the function to the value should + // be equivalent to flatMapping on the Try2. + // + // given: the following function: + final Function> concat = s -> Try.ofOK(s+s); + // then, the following should be equivalent: + assertEquals( + concat.apply(MTEST_OK_VALUE), + MTEST_OK.flatMap( concat ) + ); + + // similarly, for Err: + final Function> errConcat = e -> + Try.ofErr(new RuntimeException(e.getMessage()+e.getMessage())); + + final Try op1 = errConcat.apply( MTEST_ERR_VALUE ); + final Try op2 = MTEST_ERR.flatMapErr( errConcat ); + final String op1msg = op1.err().get().getMessage(); + final String op2msg = op2.err().get().getMessage(); + + assertEquals( op1msg, op2msg ); + + + // SECOND LAW: RIGHT IDENTITY + // Simply: data is wrapped, not manipulated/changed. + // + assertEquals( + MTEST_OK, + MTEST_OK.flatMap( Try::ofOK ) + ); + + assertEquals( + MTEST_OK, + MTEST_OK.flatMapErr( Try::ofErr ) + ); + + assertEquals( + MTEST_ERR, + MTEST_ERR.flatMap( Try::ofOK ) + ); + + assertEquals( + MTEST_ERR, + MTEST_ERR.flatMapErr( Try::ofErr ) + ); + + + // THIRD LAW: ASSOCIATIVITY + // flatmap nesting order should not matter + // given: functions from above (for OK and Err monads) + // given: the following functions: + final Function> delim = s -> Try.ofOK(s+","+s); + final Function> ellipse = e -> Try.ofErr(new RuntimeException(e.getMessage()+"...")); + // then: + assertEquals( + MTEST_OK.flatMap( delim ).flatMap( concat ), + MTEST_OK.flatMap( s -> delim.apply( s ).flatMap( concat ) ) + ); + + + final Try errOp1 = MTEST_ERR.flatMapErr( ellipse ).flatMapErr( errConcat ); + final Try errOp2 = MTEST_ERR.flatMapErr( s -> ellipse.apply( s ).flatMapErr( errConcat )); + final String errOp1Msg = op1.err().get().getMessage(); + final String errOp2Msg = op2.err().get().getMessage(); + + assertEquals( errOp1Msg, errOp2Msg ); + + } + + + + + + + +} \ No newline at end of file