From 256d3302a9b8a6eb6e5eee8cba2ab1ecf828ebd1 Mon Sep 17 00:00:00 2001 From: jnape Date: Fri, 27 Nov 2020 17:13:20 -0600 Subject: [PATCH 01/10] [maven-release-plugin] prepare for next development iteration --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 820393917..cc4cfc72d 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ lambda - 5.3.0 + 5.3.1-SNAPSHOT jar Lambda From ee4371e8626c703458b47cba57784d10922ffdbe Mon Sep 17 00:00:00 2001 From: jnape Date: Mon, 7 Dec 2020 10:20:23 -0600 Subject: [PATCH 02/10] Updating CHANGELOG.md --- CHANGELOG.md | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1a1bf3b6b..9c95ef614 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,16 +5,20 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/). ## [Unreleased] +There are currently no unreleased changes. + +## [5.3.0] - 2020-12-07 + ### Changed - `IterateT#unfold` now only computes a single `Pure` for the given input - `ReaderT#fmap` and `StateT#fmap` avoid unnecessary calls to `pure` - `MaybeT` implements `MonadError` -- `Tuple2-8#init`, for populating a `TupleN` with all but the last element ### Added - `$`, function application represented as a higher-order `Fn2` -- `Fn1#withSelf`, a static method for constructing a self-referencing `Fn1` +- `Fn1#withSelf`, a static method for constructing a self-referencing `Fn1` - `HNil/SingletonHList/TupleX#snoc`, a method to add a new last element (append to a tuple) +- `Tuple2-8#init`, for populating a `TupleN` with all but the last element ### Fixed - `IterateT#trampolineM` now yields and stages all recursive result values, rather @@ -576,7 +580,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/). - `Monadic/Dyadic/TriadicFunction`, `Predicate`, `Tuple2`, `Tuple3` - `Functor`, `BiFunctor`, `ProFunctor` -[Unreleased]: https://github.com/palatable/lambda/compare/lambda-5.2.0...HEAD +[Unreleased]: https://github.com/palatable/lambda/compare/lambda-5.3.0...HEAD +[5.3.0]: https://github.com/palatable/lambda/compare/lambda-5.2.0...lambda-5.3.0 [5.2.0]: https://github.com/palatable/lambda/compare/lambda-5.1.0...lambda-5.2.0 [5.1.0]: https://github.com/palatable/lambda/compare/lambda-5.0.0...lambda-5.1.0 [5.0.0]: https://github.com/palatable/lambda/compare/lambda-4.0.0...lambda-5.0.0 From 48ae7020641708ef9af7e00ef16a7b38c5742839 Mon Sep 17 00:00:00 2001 From: jnape Date: Mon, 7 Dec 2020 10:21:01 -0600 Subject: [PATCH 03/10] Updating README to reference latest version --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index eee42045c..85790b22e 100644 --- a/README.md +++ b/README.md @@ -61,14 +61,14 @@ Add the following dependency to your: com.jnape.palatable lambda - 5.2.0 + 5.3.0 ``` `build.gradle` ([Gradle](https://docs.gradle.org/current/userguide/dependency_management.html)): ```gradle -compile group: 'com.jnape.palatable', name: 'lambda', version: '5.2.0' +compile group: 'com.jnape.palatable', name: 'lambda', version: '5.3.0' ``` Examples From a472c9f59b4b0825de2fa4b8098acb799cb1437a Mon Sep 17 00:00:00 2001 From: John Napier Date: Wed, 9 Dec 2020 15:00:29 -0600 Subject: [PATCH 04/10] Updating community section of README --- README.md | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 85790b22e..060d4e9ee 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ Functional patterns for Java - [Either](#either) - [Lenses](#lenses) - [Notes](#notes) - - [Community](#community) + - [Ecosystem](#ecosystem) - [License](#license) Background @@ -740,9 +740,18 @@ Wherever possible, _lambda_ maintains interface compatibility with similar, fami Unfortunately, due to Java's type hierarchy and inheritance inconsistencies, this is not always possible. One surprising example of this is how `Fn1` extends `j.u.f.Function`, but `Fn2` does not extend `j.u.f.BiFunction`. This is because `j.u.f.BiFunction` itself does not extend `j.u.f.Function`, but it does define methods that collide with `j.u.f.Function`. For this reason, both `Fn1` and `Fn2` cannot extend their Java counterparts without sacrificing their own inheritance hierarchy. These types of asymmetries are, unfortunately, not uncommon; however, wherever these situations arise, measures are taken to attempt to ease the transition in and out of core Java types (in the case of `Fn2`, a supplemental `#toBiFunction` method is added). I do not take these inconveniences for granted, and I'm regularly looking for ways to minimize the negative impact of this as much as possible. Suggestions and use cases that highlight particular pain points here are particularly appreciated. -Community +Ecosystem ----- -There are some open-sourced community projects that depend on _lambda_ for their own functionality: these projects are listed below (note that these projects are _not_ affiliated with lambda, and have their own maintainers). If you use _lambda_ in your own open-sourced project, feel free to create an issue and I'll be happy to review the project and add it to this section! + +### Official extension libraries: + +These are officially supported libraries that extend lambda's core functionality and are developed under the same governance and processes as lambda. + +- [Shōki](https://github.com/palatable/shoki) - Purely functional, persistent data structures for the JVM + +### Third-party community libraries: + +These are open-sourced community projects that rely on _lambda_ for significant functionality, but are not necessarily affiliated with lambda and have their own separate maintainers. If you use _lambda_ in your own open-sourced project, feel free to create an issue and I'll be happy to review the project and add it to this section! - [Enhanced Iterables](https://github.com/kschuetz/enhanced-iterables) - Kevin Schuetz [@kschuetz](https://github.com/kschuetz) - [Collection Views](https://github.com/kschuetz/collection-views) - Kevin Schuetz [@kschuetz](https://github.com/kschuetz) From 86743e0978cdf200a5f7f009d9975c43729d8eef Mon Sep 17 00:00:00 2001 From: Nate Riffe Date: Sun, 6 Dec 2020 09:40:04 -0600 Subject: [PATCH 05/10] Loosen EitherMatcher types, add static constructors Add isRight() and isLeft() matcher constructors to EitherMatcher. To implement these using the anything() core matcher as the inner matcher, the type signature in isRightThat(..) and isLeftThat(..) has to be loosened since this and some other core matchers are not strictly typed with parameterized types. --- .../testsupport/matchers/EitherMatcher.java | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/test/java/testsupport/matchers/EitherMatcher.java b/src/test/java/testsupport/matchers/EitherMatcher.java index 748d01be2..2023a22b2 100644 --- a/src/test/java/testsupport/matchers/EitherMatcher.java +++ b/src/test/java/testsupport/matchers/EitherMatcher.java @@ -9,11 +9,12 @@ import static com.jnape.palatable.lambda.adt.Either.right; import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; import static com.jnape.palatable.lambda.io.IO.io; +import static org.hamcrest.CoreMatchers.anything; public final class EitherMatcher extends TypeSafeMatcher> { - private final Either, Matcher> matcher; + private final Either, Matcher> matcher; - private EitherMatcher(Either, Matcher> matcher) { + private EitherMatcher(Either, Matcher> matcher) { this.matcher = matcher; } @@ -44,11 +45,19 @@ public void describeTo(Description description) { .unsafePerformIO(); } - public static EitherMatcher isLeftThat(Matcher lMatcher) { + public static EitherMatcher isLeftThat(Matcher lMatcher) { return new EitherMatcher<>(left(lMatcher)); } - public static EitherMatcher isRightThat(Matcher rMatcher) { + public static EitherMatcher isLeft() { + return isLeftThat(anything()); + } + + public static EitherMatcher isRightThat(Matcher rMatcher) { return new EitherMatcher<>(right(rMatcher)); } + + public static EitherMatcher isRight() { + return isRightThat(anything()); + } } From 777949bc0f332d1966d76507f8e4a09d9072a7a3 Mon Sep 17 00:00:00 2001 From: jnape Date: Wed, 23 Dec 2020 18:29:46 -0600 Subject: [PATCH 06/10] Adding IterateT#runStep for supporting interleaving semantics --- CHANGELOG.md | 5 ++- .../monad/transformer/builtin/IterateT.java | 39 ++++++++++++++----- .../transformer/builtin/IterateTTest.java | 36 +++++++++++++++++ 3 files changed, 69 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c95ef614..13f3971a8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,10 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/). ## [Unreleased] -There are currently no unreleased changes. +### Added + +- `IterateT#runStep`, a method used to run a single step of an IterateT without the contractual + guarantee of emitting a value or reaching the end ## [5.3.0] - 2020-12-07 diff --git a/src/main/java/com/jnape/palatable/lambda/monad/transformer/builtin/IterateT.java b/src/main/java/com/jnape/palatable/lambda/monad/transformer/builtin/IterateT.java index d651795b5..cb719060b 100644 --- a/src/main/java/com/jnape/palatable/lambda/monad/transformer/builtin/IterateT.java +++ b/src/main/java/com/jnape/palatable/lambda/monad/transformer/builtin/IterateT.java @@ -65,8 +65,7 @@ * @param the effect type * @param the element type */ -public class IterateT, A> implements - MonadT, IterateT> { +public class IterateT, A> implements MonadT, IterateT> { private final Pure pureM; private final ImmutableQueue>>, M>>, MonadRec>> spine; @@ -84,15 +83,35 @@ private IterateT(Pure pureM, * @return the embedded {@link Monad} */ public >>, M>> MMTA runIterateT() { - MonadRec>>, M>>, MonadRec>>, M> - mSpine = pureM.apply(spine); - return mSpine.trampolineM(tSpine -> tSpine.head().>>, M>>, MonadRec>>, Maybe>>>, M>>match( - ___ -> pureM.apply(terminate(nothing())), + return pureM., MonadRec, M>>apply(this) + .>>>trampolineM(iterateT -> iterateT.runStep() + .fmap(maybeMore -> maybeMore.match( + fn0(() -> terminate(nothing())), + t -> t.into((Maybe maybeA, IterateT as) -> maybeA.match( + fn0(() -> recurse(as)), + a -> terminate(just(tuple(a, as)))))))) + .coerce(); + } + + /** + * Run a single step of this {@link IterateT}, where a step is the smallest amount of work that could possibly be + * productive in advancing through the {@link IterateT}. Useful for implementing interleaving algorithms that + * require {@link IterateT IterateTs} to yield, emit, or terminate as soon as possible, regardless of whether the + * next element is readily available. + * + * @param the witnessed target type of the step + * @return the step + */ + public , IterateT>>, M>> MStep runStep() { + return spine.head().match( + fn0(() -> pureM., IterateT>>, MStep>apply(nothing())), thunkOrReal -> thunkOrReal.match( - thunk -> thunk.apply().fmap(m -> m.match( - ___ -> recurse(tSpine.tail()), - t -> terminate(just(t.fmap(as -> new IterateT<>(pureM, as.spine.concat(tSpine.tail()))))))), - real -> real.fmap(a -> terminate(just(tuple(a, new IterateT<>(pureM, tSpine.tail())))))))).coerce(); + thunk -> thunk.apply()., IterateT>>>fmap(m -> m.match( + fn0(() -> just(tuple(nothing(), new IterateT<>(pureM, spine.tail())))), + t -> just(t.biMap(Maybe::just, + as -> new IterateT<>(pureM, as.spine.concat(spine.tail())))))) + .coerce(), + ma -> ma.fmap(a -> just(tuple(just(a), new IterateT<>(pureM, spine.tail())))).coerce())); } /** diff --git a/src/test/java/com/jnape/palatable/lambda/monad/transformer/builtin/IterateTTest.java b/src/test/java/com/jnape/palatable/lambda/monad/transformer/builtin/IterateTTest.java index a0e974d43..7c2997ca6 100644 --- a/src/test/java/com/jnape/palatable/lambda/monad/transformer/builtin/IterateTTest.java +++ b/src/test/java/com/jnape/palatable/lambda/monad/transformer/builtin/IterateTTest.java @@ -48,6 +48,7 @@ import static com.jnape.palatable.lambda.monad.transformer.builtin.IterateT.of; import static com.jnape.palatable.lambda.monad.transformer.builtin.IterateT.pureIterateT; import static com.jnape.palatable.lambda.monad.transformer.builtin.IterateT.singleton; +import static com.jnape.palatable.lambda.monad.transformer.builtin.IterateT.suspended; import static com.jnape.palatable.lambda.monad.transformer.builtin.IterateT.unfold; import static com.jnape.palatable.lambda.monoid.builtin.AddAll.addAll; import static com.jnape.palatable.lambda.monoid.builtin.Join.join; @@ -313,4 +314,39 @@ public void flatMapCostsNoMoreEffortThanRequiredToYieldFirstValue() { assertEquals(1, flatMapCost.get()); assertEquals(1, unfoldCost.get()); } + + @Test + public void runStep() { + assertEquals(new Identity<>(nothing()), + IterateT., Integer>empty(pureIdentity()) + ., IterateT, Integer>>>>>runStep()); + + Tuple2, IterateT, Integer>> singletonStep = + singleton(new Identity<>(1)) + ., IterateT, Integer>>>>>runStep() + .runIdentity() + .orElseThrow(AssertionError::new); + assertEquals(just(1), singletonStep._1()); + assertThat(singletonStep._2(), isEmpty()); + + Tuple2, IterateT, Integer>> emptySuspendedStep = + IterateT., Integer>suspended(() -> new Identity<>(nothing()), + pureIdentity()) + ., IterateT, Integer>>>>>runStep() + .runIdentity() + .orElseThrow(AssertionError::new); + + assertEquals(nothing(), emptySuspendedStep._1()); + assertThat(emptySuspendedStep._2(), isEmpty()); + + Tuple2, IterateT, Integer>> nonEmptySuspendedStep = + suspended(() -> new Identity<>(just(tuple(1, empty(pureIdentity())))), + pureIdentity()) + ., IterateT, Integer>>>>>runStep() + .runIdentity() + .orElseThrow(AssertionError::new); + + assertEquals(just(1), nonEmptySuspendedStep._1()); + assertThat(nonEmptySuspendedStep._2(), isEmpty()); + } } \ No newline at end of file From 7af1b9c478ede6c755045b75e7d342572d73d82f Mon Sep 17 00:00:00 2001 From: Armando Cordova <18663098+corlaez@users.noreply.github.com> Date: Mon, 15 Mar 2021 16:52:59 -0400 Subject: [PATCH 07/10] Absent folds short-circuit (#116) --- .../lambda/semigroup/builtin/Absent.java | 48 ++++++++++- .../lambda/semigroup/builtin/AbsentTest.java | 80 ++++++++++++++++++- 2 files changed, 120 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/jnape/palatable/lambda/semigroup/builtin/Absent.java b/src/main/java/com/jnape/palatable/lambda/semigroup/builtin/Absent.java index a692eac77..68162ad74 100644 --- a/src/main/java/com/jnape/palatable/lambda/semigroup/builtin/Absent.java +++ b/src/main/java/com/jnape/palatable/lambda/semigroup/builtin/Absent.java @@ -2,11 +2,21 @@ import com.jnape.palatable.lambda.adt.Maybe; import com.jnape.palatable.lambda.functions.Fn1; -import com.jnape.palatable.lambda.functions.builtin.fn3.LiftA2; +import com.jnape.palatable.lambda.functions.builtin.fn3.FoldRight; import com.jnape.palatable.lambda.functions.specialized.SemigroupFactory; +import com.jnape.palatable.lambda.functor.builtin.Lazy; import com.jnape.palatable.lambda.monoid.builtin.Present; import com.jnape.palatable.lambda.semigroup.Semigroup; +import static com.jnape.palatable.lambda.adt.Maybe.nothing; +import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Into.into; +import static com.jnape.palatable.lambda.functions.builtin.fn3.LiftA2.liftA2; +import static com.jnape.palatable.lambda.functions.recursion.RecursiveResult.recurse; +import static com.jnape.palatable.lambda.functions.recursion.RecursiveResult.terminate; +import static com.jnape.palatable.lambda.functions.recursion.Trampoline.trampoline; +import static com.jnape.palatable.lambda.functor.builtin.Lazy.lazy; + /** * A {@link Semigroup} instance formed by {@link Maybe}<A> and a semigroup over A. The * application to two {@link Maybe} values is absence-biased, such that for a given {@link Maybe} x @@ -32,7 +42,7 @@ private Absent() { @Override public Semigroup> checkedApply(Semigroup aSemigroup) { - return LiftA2., Maybe>liftA2(aSemigroup)::apply; + return shortCircuitSemigroup(aSemigroup); } @SuppressWarnings("unchecked") @@ -40,8 +50,8 @@ public static Absent absent() { return (Absent) INSTANCE; } - public static Semigroup> absent(Semigroup semigroup) { - return Absent.absent().apply(semigroup); + public static Semigroup> absent(Semigroup aSemigroup) { + return shortCircuitSemigroup(aSemigroup); } public static Fn1, Maybe> absent(Semigroup aSemigroup, Maybe x) { @@ -51,4 +61,34 @@ public static Fn1, Maybe> absent(Semigroup aSemigroup, Maybe< public static Maybe absent(Semigroup semigroup, Maybe x, Maybe y) { return absent(semigroup, x).apply(y); } + + private static Semigroup> shortCircuitSemigroup(Semigroup aSemigroup) { + return new Semigroup>() { + @Override + public Maybe checkedApply(Maybe maybeX, Maybe maybeY) { + return liftA2(aSemigroup, maybeX, maybeY); + } + + @Override + public Maybe foldLeft(Maybe acc, Iterable> maybes) { + return trampoline( + into((res, it) -> res.equals(nothing()) + ? terminate(res) + : recurse(tuple(liftA2(aSemigroup, res, it.next()), it))), + tuple(acc, maybes.iterator())); + } + + @Override + public Lazy> foldRight(Maybe accumulation, Iterable> as) { + boolean shouldShortCircuit = accumulation == nothing(); + if (shouldShortCircuit) + return lazy(accumulation); + return FoldRight.foldRight( + (maybeX, acc) -> maybeX.lazyZip(acc.fmap(maybeY -> maybeY.fmap(aSemigroup.flip()))), + lazy(accumulation), + as + ); + } + }; + } } diff --git a/src/test/java/com/jnape/palatable/lambda/semigroup/builtin/AbsentTest.java b/src/test/java/com/jnape/palatable/lambda/semigroup/builtin/AbsentTest.java index 5ef25395e..5ab7b53f6 100644 --- a/src/test/java/com/jnape/palatable/lambda/semigroup/builtin/AbsentTest.java +++ b/src/test/java/com/jnape/palatable/lambda/semigroup/builtin/AbsentTest.java @@ -1,10 +1,17 @@ package com.jnape.palatable.lambda.semigroup.builtin; +import com.jnape.palatable.lambda.adt.Maybe; +import com.jnape.palatable.lambda.adt.Unit; +import com.jnape.palatable.lambda.functions.builtin.fn1.Constantly; import com.jnape.palatable.lambda.semigroup.Semigroup; import org.junit.Test; +import java.util.Arrays; + import static com.jnape.palatable.lambda.adt.Maybe.just; import static com.jnape.palatable.lambda.adt.Maybe.nothing; +import static com.jnape.palatable.lambda.adt.Unit.UNIT; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Repeat.repeat; import static com.jnape.palatable.lambda.semigroup.builtin.Absent.absent; import static org.junit.Assert.assertEquals; @@ -12,12 +19,77 @@ public class AbsentTest { @Test public void semigroup() { + Semigroup addition = Integer::sum; + + assertEquals(just(3), absent(addition, just(1), just(2))); + assertEquals(nothing(), absent(addition, nothing(), just(1))); + assertEquals(nothing(), absent(addition, just(1), nothing())); + assertEquals(nothing(), absent(addition, nothing(), nothing())); + } + + @Test + public void foldRight() { + Absent absent = absent(); + Semigroup addition = Integer::sum; + + assertEquals(just(3), absent.apply(addition).foldRight(just(0), Arrays.asList(just(1), just(2))).value()); + assertEquals(nothing(), absent.apply(addition).foldRight(just(0), Arrays.asList(nothing(), just(1))).value()); + assertEquals(nothing(), absent.apply(addition).foldRight(just(0), Arrays.asList(just(1), nothing())).value()); + assertEquals(nothing(), absent.apply(addition).foldRight(just(0), Arrays.asList(nothing(), nothing())).value()); + } + + @Test + public void foldLeft() { Absent absent = absent(); Semigroup addition = Integer::sum; - assertEquals(just(3), absent.apply(addition, just(1), just(2))); - assertEquals(nothing(), absent.apply(addition, nothing(), just(1))); - assertEquals(nothing(), absent.apply(addition, just(1), nothing())); - assertEquals(nothing(), absent.apply(addition, nothing(), nothing())); + assertEquals(just(3), absent.apply(addition).foldLeft(just(0), Arrays.asList(just(1), just(2)))); + assertEquals(nothing(), absent.apply(addition).foldLeft(just(0), Arrays.asList(nothing(), just(1)))); + assertEquals(nothing(), absent.apply(addition).foldLeft(just(0), Arrays.asList(just(1), nothing()))); + assertEquals(nothing(), absent.apply(addition).foldLeft(just(0), Arrays.asList(nothing(), nothing()))); + } + + @Test(timeout = 200) + public void foldRightShortCircuit() { + Maybe result = Absent.absent(Constantly::constantly) + .foldRight(just(UNIT), repeat(nothing())).value(); + assertEquals(nothing(), result); + + result = Absent.absent(Constantly::constantly) + .foldRight(nothing(), repeat(just(UNIT))).value(); + assertEquals(nothing(), result); + } + + @Test(timeout = 200) + public void foldLeftShortCircuit() { + Maybe result = Absent.absent(Constantly::constantly) + .foldLeft(just(UNIT), repeat(nothing())); + assertEquals(nothing(), result); + + result = Absent.absent(Constantly::constantly) + .foldLeft(nothing(), repeat(just(UNIT))); + assertEquals(nothing(), result); + } + + @Test(timeout = 200) + public void checkedApplyFoldRightShortCircuit() { + Maybe result = Absent.absent().checkedApply(Constantly::constantly) + .foldRight(just(UNIT), repeat(nothing())).value(); + assertEquals(nothing(), result); + + result = Absent.absent().checkedApply(Constantly::constantly) + .foldRight(nothing(), repeat(just(UNIT))).value(); + assertEquals(nothing(), result); + } + + @Test(timeout = 200) + public void checkedApplyFoldLeftShortCircuit() { + Maybe result = Absent.absent().checkedApply(Constantly::constantly) + .foldLeft(just(UNIT), repeat(nothing())); + assertEquals(nothing(), result); + + result = Absent.absent().checkedApply(Constantly::constantly) + .foldLeft(nothing(), repeat(just(UNIT))); + assertEquals(nothing(), result); } } \ No newline at end of file From 31ad72636a68c37b2fb6988afb276dc20d8c1ab1 Mon Sep 17 00:00:00 2001 From: Alexander Bandukwala <7h3kk1d@gmail.com> Date: Sun, 9 May 2021 13:39:12 -0500 Subject: [PATCH 08/10] Add These::fromMaybes (#117) --- .../com/jnape/palatable/lambda/adt/These.java | 19 +++++++++++++++++++ .../jnape/palatable/lambda/adt/TheseTest.java | 11 +++++++++++ 2 files changed, 30 insertions(+) diff --git a/src/main/java/com/jnape/palatable/lambda/adt/These.java b/src/main/java/com/jnape/palatable/lambda/adt/These.java index 6721d33e8..4fc4cc27b 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/These.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/These.java @@ -189,6 +189,25 @@ public static These both(A a, B b) { return new Both<>(tuple(a, b)); } + /** + * Convenience method for converting a pair of {@link Maybe}s into a {@link Maybe} of {@link These}. If both + * {@link Maybe}s are {@link Maybe#just} then the result is a {@link Maybe#just} {@link These#both}. If only one + * {@link Maybe} is {@link Maybe#just} then it will be {@link Maybe#just} {@link These#a} or + * {@link Maybe#just} {@link These#b}. If both {@link Maybe}s are {@link Maybe#nothing} then the result will be + * {@link Maybe#nothing}. + * + * @param maybeA the first optional value + * @param maybeB the second optional value + * @param the first possible type + * @param the second possible type + * @return the wrapped values as a {@link Maybe}<{@link These}<A,B>> + */ + public static Maybe> fromMaybes(Maybe maybeA, Maybe maybeB) { + return maybeA.fmap(a -> maybeB.fmap(b -> both(a, b)).orElse(a(a))) + .fmap(Maybe::just) + .orElse(maybeB.fmap(These::b)); + } + /** * The canonical {@link Pure} instance for {@link These}. * diff --git a/src/test/java/com/jnape/palatable/lambda/adt/TheseTest.java b/src/test/java/com/jnape/palatable/lambda/adt/TheseTest.java index 36a195011..e9f764d49 100644 --- a/src/test/java/com/jnape/palatable/lambda/adt/TheseTest.java +++ b/src/test/java/com/jnape/palatable/lambda/adt/TheseTest.java @@ -12,9 +12,12 @@ import testsupport.traits.MonadRecLaws; import testsupport.traits.TraversableLaws; +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.adt.Maybe.nothing; import static com.jnape.palatable.lambda.adt.These.a; import static com.jnape.palatable.lambda.adt.These.b; import static com.jnape.palatable.lambda.adt.These.both; +import static com.jnape.palatable.lambda.adt.These.fromMaybes; import static com.jnape.palatable.lambda.functor.builtin.Lazy.lazy; import static com.jnape.palatable.traitor.framework.Subjects.subjects; import static org.junit.Assert.assertEquals; @@ -48,4 +51,12 @@ public void staticPure() { These these = These.pureThese().apply(1); assertEquals(b(1), these); } + + @Test + public void fromMaybesPermutations() { + assertEquals(nothing(), fromMaybes(nothing(), nothing())); + assertEquals(just(These.a(1)), fromMaybes(just(1), nothing())); + assertEquals(just(These.b(1)), fromMaybes(nothing(), just(1))); + assertEquals(just(These.both(1, "hello")), fromMaybes(just(1), just("hello"))); + } } \ No newline at end of file From eca9d40ef7a295c57c16b75086af04f9d7989282 Mon Sep 17 00:00:00 2001 From: jnape Date: Tue, 31 Aug 2021 12:12:01 -0500 Subject: [PATCH 09/10] - WriterT#pure now has direct access to embedded monad's pure - EitherMatcher#isLeftOf/isRightOf for equality matching - updating CHANGELOG --- CHANGELOG.md | 13 ++++++++++-- .../monad/transformer/builtin/WriterT.java | 21 +++++++++++-------- .../testsupport/matchers/EitherMatcher.java | 9 ++++++++ 3 files changed, 32 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 13f3971a8..314f61106 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,14 +1,23 @@ # Change Log + All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/). ## [Unreleased] +### Changed +- `Absent` folds short-circuit on the first `nothing()` +- `EitherMatcher#isLeftThat/isRightThat` support contravariant bounds on their delegates + ### Added +- `IterateT#runStep`, a method used to run a single step of an IterateT without the contractual guarantee of emitting a + value or reaching the end +- `These#fromMaybes :: Maybe a -> Maybe b -> Maybe (These a b)` +- `EitherMatcher#isLeftOf/isRightOf` for asserting equality -- `IterateT#runStep`, a method used to run a single step of an IterateT without the contractual - guarantee of emitting a value or reaching the end +### Fixed +- `WriterT` now keeps an immediate reference to the embedded monad's `pure` ## [5.3.0] - 2020-12-07 diff --git a/src/main/java/com/jnape/palatable/lambda/monad/transformer/builtin/WriterT.java b/src/main/java/com/jnape/palatable/lambda/monad/transformer/builtin/WriterT.java index 7a0205543..c8625bbf8 100644 --- a/src/main/java/com/jnape/palatable/lambda/monad/transformer/builtin/WriterT.java +++ b/src/main/java/com/jnape/palatable/lambda/monad/transformer/builtin/WriterT.java @@ -34,9 +34,12 @@ public final class WriterT, A> implements MonadWriter>, MonadT, WriterT> { + private final Pure pureM; private final Fn1, ? extends MonadRec, M>> writerFn; - private WriterT(Fn1, ? extends MonadRec, M>> writerFn) { + private WriterT(Pure pureM, + Fn1, ? extends MonadRec, M>> writerFn) { + this.pureM = pureM; this.writerFn = writerFn; } @@ -82,7 +85,7 @@ public > MW execWriterT(Monoid monoid) { */ @Override public WriterT> listens(Fn1 fn) { - return new WriterT<>(writerFn.fmap(m -> m.fmap(into((a, w) -> both(both(constantly(a), fn), id(), w))))); + return new WriterT<>(pureM, writerFn.fmap(m -> m.fmap(into((a, w) -> both(both(constantly(a), fn), id(), w))))); } /** @@ -90,7 +93,7 @@ public WriterT> listens(Fn1 fn) { */ @Override public WriterT censor(Fn1 fn) { - return new WriterT<>(writerFn.fmap(mt -> mt.fmap(t -> t.fmap(fn)))); + return new WriterT<>(pureM, writerFn.fmap(mt -> mt.fmap(t -> t.fmap(fn)))); } /** @@ -107,7 +110,7 @@ public > WriterT lift(MonadRec mb) { @Override public WriterT trampolineM( Fn1, WriterT>> fn) { - return new WriterT<>(monoid -> runWriterT(monoid).trampolineM(into((a, w) -> fn.apply(a) + return new WriterT<>(pureM, monoid -> runWriterT(monoid).trampolineM(into((a, w) -> fn.apply(a) .>>coerce() .runWriterT(monoid).fmap(t -> t.fmap(monoid.apply(w))) .fmap(into((aOrB, w_) -> aOrB.biMap(a_ -> tuple(a_, w_), b -> tuple(b, w_))))))); @@ -126,7 +129,7 @@ public WriterT fmap(Fn1 fn) { */ @Override public WriterT pure(B b) { - return new WriterT<>(m -> runWriterT(m).pure(tuple(b, m.identity()))); + return new WriterT<>(pureM, m -> pureM.apply(tuple(b, m.identity()))); } /** @@ -134,7 +137,7 @@ public WriterT pure(B b) { */ @Override public WriterT flatMap(Fn1>> f) { - return new WriterT<>(monoid -> writerFn.apply(monoid) + return new WriterT<>(pureM, monoid -> writerFn.apply(monoid) .flatMap(into((a, w) -> f.apply(a).>coerce().runWriterT(monoid) .fmap(t -> t.fmap(monoid.apply(w)))))); } @@ -144,7 +147,7 @@ public WriterT flatMap(Fn1 WriterT zip(Applicative, WriterT> appFn) { - return new WriterT<>(monoid -> runWriterT(monoid) + return new WriterT<>(pureM, monoid -> runWriterT(monoid) .zip(appFn.>>coerce().runWriterT(monoid) .fmap(into((f, y) -> into((a, x) -> tuple(f.apply(a), monoid.apply(x, y))))))); } @@ -196,7 +199,7 @@ public static > WriterT tell(MonadRec, A> WriterT listen(MonadRec ma) { - return new WriterT<>(monoid -> ma.fmap(a -> tuple(a, monoid.identity()))); + return new WriterT<>(Pure.of(ma), monoid -> ma.fmap(a -> tuple(a, monoid.identity()))); } /** @@ -209,7 +212,7 @@ public static , A> WriterT listen(MonadRec< * @return the {@link WriterT} */ public static , A> WriterT writerT(MonadRec, M> maw) { - return new WriterT<>(constantly(maw)); + return new WriterT<>(Pure.of(maw), constantly(maw)); } /** diff --git a/src/test/java/testsupport/matchers/EitherMatcher.java b/src/test/java/testsupport/matchers/EitherMatcher.java index 2023a22b2..b18f68af6 100644 --- a/src/test/java/testsupport/matchers/EitherMatcher.java +++ b/src/test/java/testsupport/matchers/EitherMatcher.java @@ -10,6 +10,7 @@ import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; import static com.jnape.palatable.lambda.io.IO.io; import static org.hamcrest.CoreMatchers.anything; +import static org.hamcrest.CoreMatchers.equalTo; public final class EitherMatcher extends TypeSafeMatcher> { private final Either, Matcher> matcher; @@ -53,6 +54,10 @@ public static EitherMatcher isLeft() { return isLeftThat(anything()); } + public static EitherMatcher isLeftOf(L l) { + return isLeftThat(equalTo(l)); + } + public static EitherMatcher isRightThat(Matcher rMatcher) { return new EitherMatcher<>(right(rMatcher)); } @@ -60,4 +65,8 @@ public static EitherMatcher isRightThat(Matcher rMatcher public static EitherMatcher isRight() { return isRightThat(anything()); } + + public static EitherMatcher isRightOf(R r) { + return isRightThat(equalTo(r)); + } } From c3b262b38cc82ed4148662e1e343a67e81519a68 Mon Sep 17 00:00:00 2001 From: jnape Date: Tue, 31 Aug 2021 12:20:24 -0500 Subject: [PATCH 10/10] [maven-release-plugin] prepare release lambda-5.4.0 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index cc4cfc72d..231ca6768 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ lambda - 5.3.1-SNAPSHOT + 5.4.0 jar Lambda