From 39d54649d1dd7df1b707d412817713c1a080e5cd Mon Sep 17 00:00:00 2001 From: jnape Date: Sun, 13 Oct 2019 16:50:27 -0500 Subject: [PATCH 01/29] [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 0a12b1146..c94448fa2 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ lambda - 5.1.0 + 5.1.1-SNAPSHOT jar Lambda From bef44da9c6a6656b200bc82bdb8af9798fef6bba Mon Sep 17 00:00:00 2001 From: jnape Date: Tue, 22 Oct 2019 18:52:04 -0500 Subject: [PATCH 02/29] Updating CHANGELOG and README --- CHANGELOG.md | 7 ++++++- README.md | 4 ++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a9a96405d..3a6900a75 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/). ## [Unreleased] + +There are currently no unreleased changes + +## [5.1.0] - 2019-10-13 ### Changed - All monad transformers that can support composable parallelism do support it @@ -539,7 +543,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.0.0...HEAD +[Unreleased]: https://github.com/palatable/lambda/compare/lambda-5.1.0...HEAD +[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 [4.0.0]: https://github.com/palatable/lambda/compare/lambda-3.3.0...lambda-4.0.0 [3.3.0]: https://github.com/palatable/lambda/compare/lambda-3.2.0...lambda-3.3.0 diff --git a/README.md b/README.md index be008c911..005924e67 100644 --- a/README.md +++ b/README.md @@ -60,14 +60,14 @@ Add the following dependency to your: com.jnape.palatable lambda - 5.0.0 + 5.1.0 ``` `build.gradle` ([Gradle](https://docs.gradle.org/current/userguide/dependency_management.html)): ```gradle -compile group: 'com.jnape.palatable', name: 'lambda', version: '5.0.0' +compile group: 'com.jnape.palatable', name: 'lambda', version: '5.1.0' ``` Examples From 233b4f0e68155cb1988a32ec2e6669a0e8d76dd9 Mon Sep 17 00:00:00 2001 From: John Napier Date: Wed, 30 Oct 2019 09:28:04 -0500 Subject: [PATCH 03/29] Adding Community section --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index 005924e67..fdd0981c0 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,7 @@ Functional patterns for Java - [Either](#either) - [Lenses](#lenses) - [Notes](#notes) + - [Community](#community) - [License](#license) Background @@ -739,6 +740,13 @@ 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 +----- +There are some open-sourced community projects that also leverage or extend _lambda_: these projects are listed below. 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) + License ------- From ec22f1a65cf5cbf9f825251824d1c6910ef3b8c2 Mon Sep 17 00:00:00 2001 From: John Napier Date: Wed, 30 Oct 2019 09:32:46 -0500 Subject: [PATCH 04/29] Update README.md --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index fdd0981c0..edaf4dde9 100644 --- a/README.md +++ b/README.md @@ -29,8 +29,8 @@ Functional patterns for Java - [CoProducts](#coproducts) - [Either](#either) - [Lenses](#lenses) - - [Notes](#notes) - - [Community](#community) + - [Notes](#notes) + - [Community](#community) - [License](#license) Background @@ -742,7 +742,7 @@ Unfortunately, due to Java's type hierarchy and inheritance inconsistencies, thi Community ----- -There are some open-sourced community projects that also leverage or extend _lambda_: these projects are listed below. 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! +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! - [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 974fa525019b886968845d11f6e367e50d565fd0 Mon Sep 17 00:00:00 2001 From: jnape Date: Wed, 20 Nov 2019 09:57:15 -0600 Subject: [PATCH 05/29] HList#cons static factory method auto-promotes --- .../palatable/lambda/adt/hlist/HList.java | 6 +++-- .../palatable/lambda/adt/hlist/HListTest.java | 24 ++++++++++++------- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/src/main/java/com/jnape/palatable/lambda/adt/hlist/HList.java b/src/main/java/com/jnape/palatable/lambda/adt/hlist/HList.java index 91f19edbe..421855a10 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/hlist/HList.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/hlist/HList.java @@ -1,5 +1,7 @@ package com.jnape.palatable.lambda.adt.hlist; +import com.jnape.palatable.lambda.functions.builtin.fn1.Downcast; + import java.util.Objects; /** @@ -63,7 +65,7 @@ public static HNil nil() { * @return the newly created HList */ public static HCons cons(Head head, Tail tail) { - return new HCons<>(head, tail); + return Downcast., HCons>downcast(tail.cons(head)); } /** @@ -271,7 +273,7 @@ public final boolean equals(Object other) { if (other instanceof HCons) { HCons that = (HCons) other; return this.head.equals(that.head) - && this.tail.equals(that.tail); + && this.tail.equals(that.tail); } return false; } diff --git a/src/test/java/com/jnape/palatable/lambda/adt/hlist/HListTest.java b/src/test/java/com/jnape/palatable/lambda/adt/hlist/HListTest.java index 6be65b6dc..1f75ce450 100644 --- a/src/test/java/com/jnape/palatable/lambda/adt/hlist/HListTest.java +++ b/src/test/java/com/jnape/palatable/lambda/adt/hlist/HListTest.java @@ -2,15 +2,9 @@ import org.junit.Test; -import static com.jnape.palatable.lambda.adt.hlist.HList.cons; -import static com.jnape.palatable.lambda.adt.hlist.HList.nil; -import static com.jnape.palatable.lambda.adt.hlist.HList.singletonHList; -import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotEquals; -import static org.junit.Assert.assertSame; -import static org.junit.Assert.assertTrue; +import static com.jnape.palatable.lambda.adt.hlist.HList.*; +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.junit.Assert.*; public class HListTest { @@ -36,6 +30,18 @@ public void convenienceStaticFactoryMethods() { assertEquals(nil().cons(false).cons(4.0).cons("3").cons('2').cons(1), tuple(1, '2', "3", 4.0, false)); } + @Test + public void autoPromotion() { + assertThat(cons(1, nil()), instanceOf(SingletonHList.class)); + assertThat(cons(1, singletonHList(1)), instanceOf(Tuple2.class)); + assertThat(cons(1, tuple(1, 1)), instanceOf(Tuple3.class)); + assertThat(cons(1, tuple(1, 1, 1)), instanceOf(Tuple4.class)); + assertThat(cons(1, tuple(1, 1, 1, 1)), instanceOf(Tuple5.class)); + assertThat(cons(1, tuple(1, 1, 1, 1, 1)), instanceOf(Tuple6.class)); + assertThat(cons(1, tuple(1, 1, 1, 1, 1, 1)), instanceOf(Tuple7.class)); + assertThat(cons(1, tuple(1, 1, 1, 1, 1, 1, 1)), instanceOf(Tuple8.class)); + } + @Test public void nilReusesInstance() { assertSame(nil(), nil()); From 74a58a0f5b929eba9b6145121121ddaf35d1ea46 Mon Sep 17 00:00:00 2001 From: jnape Date: Wed, 20 Nov 2019 09:58:22 -0600 Subject: [PATCH 06/29] Updating CHANGELOG --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3a6900a75..0d82489f9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/). ## [Unreleased] -There are currently no unreleased changes +### Changed +- `HList#cons` static factory method auto-promotes to specialized `HList` if there is one ## [5.1.0] - 2019-10-13 ### Changed From 99acf00c88c2353384189de0ab3044fd93c18fdf Mon Sep 17 00:00:00 2001 From: jnape Date: Tue, 10 Dec 2019 14:19:11 -0600 Subject: [PATCH 07/29] Adding MergeHMaps, a Monoid merging HMaps by using key-specific semigroups --- CHANGELOG.md | 3 + .../lambda/monoid/builtin/MergeHMaps.java | 81 +++++++++++++++++++ .../lambda/monoid/builtin/MergeHMapsTest.java | 62 ++++++++++++++ 3 files changed, 146 insertions(+) create mode 100644 src/main/java/com/jnape/palatable/lambda/monoid/builtin/MergeHMaps.java create mode 100644 src/test/java/com/jnape/palatable/lambda/monoid/builtin/MergeHMapsTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 0d82489f9..488f8b414 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,9 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/). ### Changed - `HList#cons` static factory method auto-promotes to specialized `HList` if there is one +### Added +- `MergeHMaps`, a `Monoid` that merges `HMap`s by merging the values via key-specified `Semigroup`s + ## [5.1.0] - 2019-10-13 ### Changed - All monad transformers that can support composable parallelism do support it diff --git a/src/main/java/com/jnape/palatable/lambda/monoid/builtin/MergeHMaps.java b/src/main/java/com/jnape/palatable/lambda/monoid/builtin/MergeHMaps.java new file mode 100644 index 000000000..fc2f28c95 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/monoid/builtin/MergeHMaps.java @@ -0,0 +1,81 @@ +package com.jnape.palatable.lambda.monoid.builtin; + +import com.jnape.palatable.lambda.adt.Maybe; +import com.jnape.palatable.lambda.adt.hmap.HMap; +import com.jnape.palatable.lambda.adt.hmap.TypeSafeKey; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.Fn2; +import com.jnape.palatable.lambda.functions.builtin.fn3.FoldLeft; +import com.jnape.palatable.lambda.monoid.Monoid; +import com.jnape.palatable.lambda.semigroup.Semigroup; + +import java.util.Map; + +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.adt.Maybe.maybe; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Map.map; +import static com.jnape.palatable.lambda.monoid.builtin.Last.last; +import static com.jnape.palatable.lambda.monoid.builtin.Present.present; +import static com.jnape.palatable.lambda.optics.functions.Set.set; +import static com.jnape.palatable.lambda.optics.lenses.MapLens.valueAt; +import static java.util.Arrays.asList; +import static java.util.Collections.emptyMap; + +/** + * A {@link Monoid} instance formed by merging {@link HMap HMaps} using the chosen + * {@link TypeSafeKey} -> {@link Semigroup} + * {@link MergeHMaps#key(TypeSafeKey, Semigroup) mappings}, defaulting to {@link Last} in case no + * {@link Semigroup} has been chosen for a given {@link TypeSafeKey}. + */ +public final class MergeHMaps implements Monoid { + + private final Map, Fn2> bindings; + private final Φ> defaultBinding; + + private MergeHMaps(Map, Fn2> bindings, + Φ> defaultBinding) { + this.bindings = bindings; + this.defaultBinding = defaultBinding; + } + + public MergeHMaps key(TypeSafeKey key, Semigroup semigroup) { + return new MergeHMaps(set(valueAt(key), just(merge(key, present(semigroup))), bindings), defaultBinding); + } + + @Override + public HMap identity() { + return HMap.emptyHMap(); + } + + @Override + public HMap checkedApply(HMap x, HMap y) throws Throwable { + return reduceLeft(asList(x, y)); + } + + @Override + public HMap foldMap(Fn1 fn, Iterable bs) { + return FoldLeft.foldLeft((acc, m) -> FoldLeft.foldLeft((result, k) -> maybe(bindings.get(k)) + .orElseGet(() -> defaultBinding.eliminate(k)) + .apply(result, m), acc, m.keys()), identity(), map(fn, bs)); + } + + public static MergeHMaps mergeHMaps() { + return new MergeHMaps(emptyMap(), new Φ>() { + @Override + public Fn2 eliminate(TypeSafeKey key) { + return merge(key, last()); + } + }); + } + + private static Fn2 merge(TypeSafeKey key, Semigroup> semigroup) { + return (x, y) -> semigroup.apply(x.get(key), y.get(key)) + .fmap(a -> x.put(key, a)) + .orElse(x); + } + + @SuppressWarnings({"NonAsciiCharacters"}) + private interface Φ { + R eliminate(TypeSafeKey key); + } +} diff --git a/src/test/java/com/jnape/palatable/lambda/monoid/builtin/MergeHMapsTest.java b/src/test/java/com/jnape/palatable/lambda/monoid/builtin/MergeHMapsTest.java new file mode 100644 index 000000000..0bf2d978b --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/monoid/builtin/MergeHMapsTest.java @@ -0,0 +1,62 @@ +package com.jnape.palatable.lambda.monoid.builtin; + +import com.jnape.palatable.lambda.adt.hmap.TypeSafeKey; +import org.junit.Test; + +import static com.jnape.palatable.lambda.adt.hmap.HMap.emptyHMap; +import static com.jnape.palatable.lambda.adt.hmap.HMap.hMap; +import static com.jnape.palatable.lambda.adt.hmap.HMap.singletonHMap; +import static com.jnape.palatable.lambda.adt.hmap.TypeSafeKey.typeSafeKey; +import static com.jnape.palatable.lambda.monoid.builtin.Join.join; +import static com.jnape.palatable.lambda.monoid.builtin.MergeHMaps.mergeHMaps; +import static java.util.Arrays.asList; +import static org.junit.Assert.assertEquals; + +public class MergeHMapsTest { + + @Test + public void allKeysAccountedFor() { + TypeSafeKey.Simple stringKey = typeSafeKey(); + MergeHMaps mergeHMaps = mergeHMaps().key(stringKey, join()); + + assertEquals(emptyHMap(), mergeHMaps.apply(emptyHMap(), emptyHMap())); + assertEquals(singletonHMap(stringKey, "foo"), + mergeHMaps.apply(singletonHMap(stringKey, "foo"), emptyHMap())); + assertEquals(singletonHMap(stringKey, "foobar"), + mergeHMaps.apply(singletonHMap(stringKey, "foo"), + singletonHMap(stringKey, "bar"))); + } + + @Test + public void unaccountedForKeyUsesLastByDefault() { + TypeSafeKey.Simple stringKey = typeSafeKey(); + + assertEquals(singletonHMap(stringKey, "foo"), + mergeHMaps().apply(singletonHMap(stringKey, "foo"), emptyHMap())); + assertEquals(singletonHMap(stringKey, "bar"), + mergeHMaps().apply(emptyHMap(), singletonHMap(stringKey, "bar"))); + assertEquals(singletonHMap(stringKey, "bar"), + mergeHMaps().apply(singletonHMap(stringKey, "foo"), singletonHMap(stringKey, "bar"))); + } + + @Test + public void sparseKeysAcrossMaps() { + TypeSafeKey.Simple stringKey = typeSafeKey(); + TypeSafeKey.Simple intKey = typeSafeKey(); + TypeSafeKey.Simple boolKey = typeSafeKey(); + + MergeHMaps mergeHMaps = mergeHMaps() + .key(stringKey, join()) + .key(intKey, Integer::sum); + + assertEquals(hMap(stringKey, "foobar", + intKey, 3, + boolKey, false), + mergeHMaps.reduceLeft(asList(singletonHMap(stringKey, "foo"), + singletonHMap(intKey, 1), + singletonHMap(boolKey, true), + hMap(stringKey, "bar", + intKey, 2, + boolKey, false)))); + } +} \ No newline at end of file From 93be6ed6a76eddd8df48d3f3e6d7bca94954ef52 Mon Sep 17 00:00:00 2001 From: John Napier Date: Wed, 1 Jan 2020 18:16:08 -0600 Subject: [PATCH 08/29] Shameless self-promotion button --- .github/FUNDING.yml | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 .github/FUNDING.yml diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 000000000..66b54c05c --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,12 @@ +# These are supported funding model platforms + +github: [jnape] +patreon: # Replace with a single Patreon username +open_collective: # Replace with a single Open Collective username +ko_fi: # Replace with a single Ko-fi username +tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel +community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry +liberapay: # Replace with a single Liberapay username +issuehunt: # Replace with a single IssueHunt username +otechie: # Replace with a single Otechie username +custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] From e31ad461775a650b864058b524214684fb23ddea Mon Sep 17 00:00:00 2001 From: jnape Date: Sun, 26 Jan 2020 15:30:05 -0600 Subject: [PATCH 09/29] Javadoc --- src/main/java/com/jnape/palatable/lambda/functions/Fn1.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/jnape/palatable/lambda/functions/Fn1.java b/src/main/java/com/jnape/palatable/lambda/functions/Fn1.java index 3f2be1e92..f21e70e23 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/Fn1.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/Fn1.java @@ -123,7 +123,7 @@ default Fn1 flatMap(Fn1>> f) { } /** - * Also left-to-right composition (sadly). + * Left-to-right composition. * * @param the return type of the next function to invoke * @param f the function to invoke with this function's return value From c87ac5b2e66c0edd81a23258c8f7f880c6a6e6a6 Mon Sep 17 00:00:00 2001 From: jnape Date: Sat, 8 Feb 2020 16:14:50 -0600 Subject: [PATCH 10/29] Mockito is a test dependency; missed this in a previous release --- pom.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/pom.xml b/pom.xml index c94448fa2..5f84c2dd2 100644 --- a/pom.xml +++ b/pom.xml @@ -74,6 +74,7 @@ org.mockito mockito-core 2.28.2 + test com.jnape.palatable From ffb4c2721ea3b81612b01b64c315bed7acd5d6dc Mon Sep 17 00:00:00 2001 From: Alexander Bandukwala <7h3kk1d@gmail.com> Date: Thu, 24 Oct 2019 11:57:03 -0500 Subject: [PATCH 11/29] Add utility functions for interoperability with java.util.Comparator - Add ComparisonRelation to encapsulate Comparator/Comparable#compare semantics - Add Compare to abstract over Comparator#compare using ComparisonRelation - Add comparator compatible functions (CmpEqWith, GTEWith, GTWith, LTEWith, LTWith) --- .../lambda/functions/builtin/fn3/CmpEqBy.java | 5 +- .../functions/builtin/fn3/CmpEqWith.java | 62 +++++++++++++ .../lambda/functions/builtin/fn3/Compare.java | 56 ++++++++++++ .../lambda/functions/builtin/fn3/GTBy.java | 5 +- .../lambda/functions/builtin/fn3/GTEBy.java | 6 +- .../lambda/functions/builtin/fn3/GTEWith.java | 62 +++++++++++++ .../lambda/functions/builtin/fn3/GTWith.java | 62 +++++++++++++ .../lambda/functions/builtin/fn3/LTBy.java | 4 +- .../lambda/functions/builtin/fn3/LTEBy.java | 5 +- .../lambda/functions/builtin/fn3/LTEWith.java | 62 +++++++++++++ .../lambda/functions/builtin/fn3/LTWith.java | 62 +++++++++++++ .../ordering/ComparisonRelation.java | 89 +++++++++++++++++++ .../lambda/semigroup/builtin/MaxBy.java | 1 + .../lambda/semigroup/builtin/MaxWith.java | 52 +++++++++++ .../lambda/semigroup/builtin/MinWith.java | 52 +++++++++++ .../functions/builtin/fn3/CmpEqWithTest.java | 19 ++++ .../functions/builtin/fn3/CompareTest.java | 19 ++++ .../functions/builtin/fn3/GTEWithTest.java | 21 +++++ .../functions/builtin/fn3/GTWithTest.java | 20 +++++ .../functions/builtin/fn3/LTEWithTest.java | 21 +++++ .../functions/builtin/fn3/LTWithTest.java | 20 +++++ .../ordering/ComparisonRelationTest.java | 23 +++++ .../lambda/semigroup/builtin/MaxWithTest.java | 21 +++++ .../lambda/semigroup/builtin/MinWithTest.java | 21 +++++ 24 files changed, 763 insertions(+), 7 deletions(-) create mode 100644 src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/CmpEqWith.java create mode 100644 src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/Compare.java create mode 100644 src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/GTEWith.java create mode 100644 src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/GTWith.java create mode 100644 src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/LTEWith.java create mode 100644 src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/LTWith.java create mode 100644 src/main/java/com/jnape/palatable/lambda/functions/ordering/ComparisonRelation.java create mode 100644 src/main/java/com/jnape/palatable/lambda/semigroup/builtin/MaxWith.java create mode 100644 src/main/java/com/jnape/palatable/lambda/semigroup/builtin/MinWith.java create mode 100644 src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/CmpEqWithTest.java create mode 100644 src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/CompareTest.java create mode 100644 src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/GTEWithTest.java create mode 100644 src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/GTWithTest.java create mode 100644 src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/LTEWithTest.java create mode 100644 src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/LTWithTest.java create mode 100644 src/test/java/com/jnape/palatable/lambda/functions/ordering/ComparisonRelationTest.java create mode 100644 src/test/java/com/jnape/palatable/lambda/semigroup/builtin/MaxWithTest.java create mode 100644 src/test/java/com/jnape/palatable/lambda/semigroup/builtin/MinWithTest.java diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/CmpEqBy.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/CmpEqBy.java index 3b483aeaf..a6e04baa9 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/CmpEqBy.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/CmpEqBy.java @@ -6,7 +6,9 @@ import com.jnape.palatable.lambda.functions.specialized.BiPredicate; import com.jnape.palatable.lambda.functions.specialized.Predicate; +import static com.jnape.palatable.lambda.functions.builtin.fn3.CmpEqWith.cmpEqWith; import static com.jnape.palatable.lambda.functions.specialized.Predicate.predicate; +import static java.util.Comparator.comparing; /** * Given a mapping function from some type A to some {@link Comparable} type B and two values @@ -16,6 +18,7 @@ * @param the value type * @param the mapped comparison type * @see CmpEq + * @see CmpEqWith * @see LTBy * @see GTBy */ @@ -28,7 +31,7 @@ private CmpEqBy() { @Override public Boolean checkedApply(Fn1 compareFn, A x, A y) { - return compareFn.apply(x).compareTo(compareFn.apply(y)) == 0; + return cmpEqWith(comparing(compareFn.toFunction()), x, y); } @Override diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/CmpEqWith.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/CmpEqWith.java new file mode 100644 index 000000000..0a0c1f1ce --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/CmpEqWith.java @@ -0,0 +1,62 @@ +package com.jnape.palatable.lambda.functions.builtin.fn3; + +import com.jnape.palatable.lambda.functions.Fn3; +import com.jnape.palatable.lambda.functions.specialized.BiPredicate; +import com.jnape.palatable.lambda.functions.specialized.Predicate; + +import java.util.Comparator; + +import static com.jnape.palatable.lambda.functions.builtin.fn3.Compare.compare; +import static com.jnape.palatable.lambda.functions.ordering.ComparisonRelation.equal; +import static com.jnape.palatable.lambda.functions.specialized.Predicate.predicate; + +/** + * Given a {@link Comparator} from some type A and two values of type A, return true + * if the first value is strictly equal to the second value (according to {@link Comparator#compare(A, A)} + * otherwise, return false. + * + * @param the value type + * @see CmpEqBy + * @see LTBy + * @see GTBy + * @see Compare + */ +public final class CmpEqWith implements Fn3, A, A, Boolean> { + + private static final CmpEqWith INSTANCE = new CmpEqWith<>(); + + private CmpEqWith() { + } + + @Override + public BiPredicate apply(Comparator compareFn) { + return Fn3.super.apply(compareFn)::apply; + } + + @Override + public Predicate apply(Comparator compareFn, A x) { + return predicate(Fn3.super.apply(compareFn, x)); + } + + @SuppressWarnings("unchecked") + public static CmpEqWith cmpEqWith() { + return (CmpEqWith) INSTANCE; + } + + public static BiPredicate cmpEqWith(Comparator comparator) { + return CmpEqWith.cmpEqWith().apply(comparator); + } + + public static Predicate cmpEqWith(Comparator comparator, A x) { + return CmpEqWith.cmpEqWith(comparator).apply(x); + } + + public static Boolean cmpEqWith(Comparator comparator, A x, A y) { + return cmpEqWith(comparator, x).apply(y); + } + + @Override + public Boolean checkedApply(Comparator comparator, A a, A a2) { + return compare(comparator, a, a2).equals(equal()); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/Compare.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/Compare.java new file mode 100644 index 000000000..4f0bea533 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/Compare.java @@ -0,0 +1,56 @@ +package com.jnape.palatable.lambda.functions.builtin.fn3; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.Fn2; +import com.jnape.palatable.lambda.functions.Fn3; +import com.jnape.palatable.lambda.functions.ordering.ComparisonRelation; + +import java.util.Comparator; + +/** + * Given a {@link Comparator} from some type A and two values of type A, return a + * {@link ComparisonRelation} of the first value with reference to the second value (according to {@link Comparator#compare(A, A)}. + * The order of parameters is flipped with respect to {@link Comparator#compare(A, A)} for more idiomatic partial application. + * + *

+ * Example: + *

+ * {@code
+ *  Compare.compare(naturalOrder(), 1, 2); // ComparisonRelation.GreaterThan
+ *  Compare.compare(naturalOrder(), 2, 1); // ComparisonRelation.LessThan
+ *  Compare.compare(naturalOrder(), 1, 1); // ComparisonRelation.Equal
+ * }
+ * 
+ *

+ * + * @param
the value type + * @see Comparator + * @see Compare + */ +public final class Compare implements Fn3, A, A, ComparisonRelation> { + private static final Compare INSTANCE = new Compare<>(); + + private Compare() { } + + @Override + public ComparisonRelation checkedApply(Comparator aComparator, A a, A a2) throws Throwable { + return ComparisonRelation.fromInt(aComparator.compare(a2, a)); + } + + @SuppressWarnings("unchecked") + public static Compare compare() { + return (Compare) INSTANCE; + } + + public static Fn2 compare(Comparator comparator) { + return Compare.compare().apply(comparator); + } + + public static Fn1 compare(Comparator comparator, A a) { + return compare(comparator).apply(a); + } + + public static ComparisonRelation compare(Comparator aComparator, A a, A a2) { + return compare(aComparator, a).apply(a2); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/GTBy.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/GTBy.java index cb9b45168..caddec12e 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/GTBy.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/GTBy.java @@ -6,7 +6,9 @@ import com.jnape.palatable.lambda.functions.specialized.BiPredicate; import com.jnape.palatable.lambda.functions.specialized.Predicate; +import static com.jnape.palatable.lambda.functions.builtin.fn3.GTWith.gtWith; import static com.jnape.palatable.lambda.functions.specialized.Predicate.predicate; +import static java.util.Comparator.comparing; /** * Given a mapping function from some type A to some {@link Comparable} type B and two values @@ -16,6 +18,7 @@ * @param the value type * @param the mapped comparison type * @see GT + * @see GTWith * @see LTBy */ public final class GTBy> implements Fn3, A, A, Boolean> { @@ -27,7 +30,7 @@ private GTBy() { @Override public Boolean checkedApply(Fn1 compareFn, A y, A x) { - return compareFn.apply(x).compareTo(compareFn.apply(y)) > 0; + return gtWith(comparing(compareFn.toFunction()), y, x); } @Override diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/GTEBy.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/GTEBy.java index 30ef09852..7a1b4acad 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/GTEBy.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/GTEBy.java @@ -6,8 +6,9 @@ import com.jnape.palatable.lambda.functions.specialized.BiPredicate; import com.jnape.palatable.lambda.functions.specialized.Predicate; -import static com.jnape.palatable.lambda.functions.builtin.fn3.CmpEqBy.cmpEqBy; +import static com.jnape.palatable.lambda.functions.builtin.fn3.GTEWith.gteWith; import static com.jnape.palatable.lambda.functions.specialized.Predicate.predicate; +import static java.util.Comparator.comparing; /** * Given a mapping function from some type A to some {@link Comparable} type B and two values @@ -18,6 +19,7 @@ * @param the value type * @param the mapped comparison type * @see GTE + * @see GTEWith * @see LTEBy */ public final class GTEBy> implements Fn3, A, A, Boolean> { @@ -29,7 +31,7 @@ private GTEBy() { @Override public Boolean checkedApply(Fn1 compareFn, A y, A x) { - return GTBy.gtBy(compareFn).or(cmpEqBy(compareFn)).apply(y, x); + return gteWith(comparing(compareFn.toFunction()), y, x); } @Override diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/GTEWith.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/GTEWith.java new file mode 100644 index 000000000..7a3c8cc6d --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/GTEWith.java @@ -0,0 +1,62 @@ +package com.jnape.palatable.lambda.functions.builtin.fn3; + +import com.jnape.palatable.lambda.functions.Fn3; +import com.jnape.palatable.lambda.functions.builtin.fn2.GTE; +import com.jnape.palatable.lambda.functions.specialized.BiPredicate; +import com.jnape.palatable.lambda.functions.specialized.Predicate; + +import java.util.Comparator; + +import static com.jnape.palatable.lambda.functions.builtin.fn3.LTWith.ltWith; +import static com.jnape.palatable.lambda.functions.specialized.Predicate.predicate; + +/** + * Given a {@link Comparator} from some type A and two values of type A, + * return true if the second value is greater than or equal to the first value in + * terms of their mapped B results according to {@link Comparator#compare(A, A)}; + * otherwise, return false. + * + * @param the value type + * @see GTE + * @see GTEBy + * @see LTEWith + */ +public final class GTEWith implements Fn3, A, A, Boolean> { + + private static final GTEWith INSTANCE = new GTEWith<>(); + + private GTEWith() { + } + + @Override + public BiPredicate apply(Comparator compareFn) { + return Fn3.super.apply(compareFn)::apply; + } + + @Override + public Predicate apply(Comparator compareFn, A x) { + return predicate(Fn3.super.apply(compareFn, x)); + } + + @SuppressWarnings("unchecked") + public static GTEWith gteWith() { + return (GTEWith) INSTANCE; + } + + public static BiPredicate gteWith(Comparator comparator) { + return GTEWith.gteWith().apply(comparator); + } + + public static Predicate gteWith(Comparator comparator, A y) { + return GTEWith.gteWith(comparator).apply(y); + } + + public static Boolean gteWith(Comparator comparator, A y, A x) { + return gteWith(comparator, y).apply(x); + } + + @Override + public Boolean checkedApply(Comparator comparator, A a, A a2) { + return !ltWith(comparator, a, a2); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/GTWith.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/GTWith.java new file mode 100644 index 000000000..d65bec00f --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/GTWith.java @@ -0,0 +1,62 @@ +package com.jnape.palatable.lambda.functions.builtin.fn3; + +import com.jnape.palatable.lambda.functions.Fn3; +import com.jnape.palatable.lambda.functions.builtin.fn2.GT; +import com.jnape.palatable.lambda.functions.specialized.BiPredicate; +import com.jnape.palatable.lambda.functions.specialized.Predicate; + +import java.util.Comparator; + +import static com.jnape.palatable.lambda.functions.builtin.fn3.Compare.compare; +import static com.jnape.palatable.lambda.functions.ordering.ComparisonRelation.greaterThan; +import static com.jnape.palatable.lambda.functions.specialized.Predicate.predicate; + +/** + * Given a {@link Comparator} from some type A and two values of type A, + * return true if the second value is strictly greater than the first value in + * terms of their mapped B results; otherwise, return false. + * + * @param the value type + * @see GT + * @see GTBy + * @see LTWith + */ +public final class GTWith implements Fn3, A, A, Boolean> { + + private static final GTWith INSTANCE = new GTWith<>(); + + private GTWith() { + } + + @Override + public BiPredicate apply(Comparator compareFn) { + return Fn3.super.apply(compareFn)::apply; + } + + @Override + public Predicate apply(Comparator compareFn, A x) { + return predicate(Fn3.super.apply(compareFn, x)); + } + + @SuppressWarnings("unchecked") + public static GTWith gtWith() { + return (GTWith) INSTANCE; + } + + public static BiPredicate gtWith(Comparator comparator) { + return GTWith.gtWith().apply(comparator); + } + + public static Predicate gtWith(Comparator comparator, A y) { + return GTWith.gtWith(comparator).apply(y); + } + + public static Boolean gtWith(Comparator comparator, A y, A x) { + return gtWith(comparator, y).apply(x); + } + + @Override + public Boolean checkedApply(Comparator comparator, A a, A a2) { + return compare(comparator, a, a2).equals(greaterThan()); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/LTBy.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/LTBy.java index c442d9871..05e163844 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/LTBy.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/LTBy.java @@ -6,7 +6,9 @@ import com.jnape.palatable.lambda.functions.specialized.BiPredicate; import com.jnape.palatable.lambda.functions.specialized.Predicate; +import static com.jnape.palatable.lambda.functions.builtin.fn3.LTWith.ltWith; import static com.jnape.palatable.lambda.functions.specialized.Predicate.predicate; +import static java.util.Comparator.comparing; /** * Given a mapping function from some type A to some {@link Comparable} type B and two values @@ -27,7 +29,7 @@ private LTBy() { @Override public Boolean checkedApply(Fn1 compareFn, A y, A x) { - return compareFn.apply(x).compareTo(compareFn.apply(y)) < 0; + return ltWith(comparing(compareFn.toFunction()), y, x); } @Override diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/LTEBy.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/LTEBy.java index bc755470d..4c377d583 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/LTEBy.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/LTEBy.java @@ -6,7 +6,8 @@ import com.jnape.palatable.lambda.functions.specialized.BiPredicate; import com.jnape.palatable.lambda.functions.specialized.Predicate; -import static com.jnape.palatable.lambda.functions.builtin.fn3.CmpEqBy.cmpEqBy; +import static com.jnape.palatable.lambda.functions.builtin.fn3.LTEWith.lteWith; +import static java.util.Comparator.comparing; /** * Given a mapping function from some type A to some {@link Comparable} type B and two values @@ -28,7 +29,7 @@ private LTEBy() { @Override public Boolean checkedApply(Fn1 compareFn, A y, A x) { - return LTBy.ltBy(compareFn).or(cmpEqBy(compareFn)).apply(y, x); + return lteWith(comparing(compareFn.toFunction()), y, x); } @Override diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/LTEWith.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/LTEWith.java new file mode 100644 index 000000000..06e621d3f --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/LTEWith.java @@ -0,0 +1,62 @@ +package com.jnape.palatable.lambda.functions.builtin.fn3; + +import com.jnape.palatable.lambda.functions.Fn3; +import com.jnape.palatable.lambda.functions.builtin.fn2.LTE; +import com.jnape.palatable.lambda.functions.specialized.BiPredicate; +import com.jnape.palatable.lambda.functions.specialized.Predicate; + +import java.util.Comparator; + +import static com.jnape.palatable.lambda.functions.builtin.fn3.GTWith.gtWith; +import static com.jnape.palatable.lambda.functions.specialized.Predicate.predicate; + +/** + * Given a {@link Comparator} from some type A and two values of type A, + * return true if the second value is less than or equal to the first value in + * terms of their mapped B results according to {@link Comparator#compare(A, A)}; + * otherwise, return false. + * + * @param the value type + * @see LTE + * @see LTEBy + * @see GTEWith + */ +public final class LTEWith implements Fn3, A, A, Boolean> { + + private static final LTEWith INSTANCE = new LTEWith<>(); + + private LTEWith() { + } + + @Override + public BiPredicate apply(Comparator compareFn) { + return Fn3.super.apply(compareFn)::apply; + } + + @Override + public Predicate apply(Comparator compareFn, A x) { + return predicate(Fn3.super.apply(compareFn, x)); + } + + @SuppressWarnings("unchecked") + public static LTEWith lteWith() { + return (LTEWith) INSTANCE; + } + + public static BiPredicate lteWith(Comparator comparator) { + return LTEWith.lteWith().apply(comparator); + } + + public static Predicate lteWith(Comparator comparator, A y) { + return LTEWith.lteWith(comparator).apply(y); + } + + public static Boolean lteWith(Comparator comparator, A y, A x) { + return lteWith(comparator, y).apply(x); + } + + @Override + public Boolean checkedApply(Comparator comparator, A a, A a2) { + return !gtWith(comparator, a, a2); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/LTWith.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/LTWith.java new file mode 100644 index 000000000..14652a4c6 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/LTWith.java @@ -0,0 +1,62 @@ +package com.jnape.palatable.lambda.functions.builtin.fn3; + +import com.jnape.palatable.lambda.functions.Fn3; +import com.jnape.palatable.lambda.functions.builtin.fn2.LT; +import com.jnape.palatable.lambda.functions.specialized.BiPredicate; +import com.jnape.palatable.lambda.functions.specialized.Predicate; + +import java.util.Comparator; + +import static com.jnape.palatable.lambda.functions.builtin.fn3.Compare.compare; +import static com.jnape.palatable.lambda.functions.ordering.ComparisonRelation.lessThan; +import static com.jnape.palatable.lambda.functions.specialized.Predicate.predicate; + +/** + * Given a comparator for some type A and two values of type A, + * return true if the second value is strictly less than than the first value in + * terms of their mapped B results; otherwise, return false. + * + * @param the value type + * @see LT + * @see LTBy + * @see GTWith + */ +public final class LTWith implements Fn3, A, A, Boolean> { + + private static final LTWith INSTANCE = new LTWith<>(); + + private LTWith() { + } + + @Override + public BiPredicate apply(Comparator compareFn) { + return Fn3.super.apply(compareFn)::apply; + } + + @Override + public Predicate apply(Comparator compareFn, A x) { + return predicate(Fn3.super.apply(compareFn, x)); + } + + @SuppressWarnings("unchecked") + public static LTWith ltWith() { + return (LTWith) INSTANCE; + } + + public static BiPredicate ltWith(Comparator comparator) { + return LTWith.ltWith().apply(comparator); + } + + public static Predicate ltWith(Comparator comparator, A y) { + return LTWith.ltWith(comparator).apply(y); + } + + public static Boolean ltWith(Comparator comparator, A y, A x) { + return ltWith(comparator, y).apply(x); + } + + @Override + public Boolean checkedApply(Comparator comparator, A a, A a2) { + return compare(comparator, a, a2).equals(lessThan()); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functions/ordering/ComparisonRelation.java b/src/main/java/com/jnape/palatable/lambda/functions/ordering/ComparisonRelation.java new file mode 100644 index 000000000..3878f385a --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/ordering/ComparisonRelation.java @@ -0,0 +1,89 @@ +package com.jnape.palatable.lambda.functions.ordering; + +import com.jnape.palatable.lambda.adt.coproduct.CoProduct3; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.builtin.fn3.Compare; + +import java.util.Comparator; + +/** + * Specialized {@link CoProduct3} representing the possible results of a ordered comparison. + * Used by {@link Compare} as the result of a comparison. + * + * @see Compare + */ +public abstract class ComparisonRelation + implements CoProduct3 { + private ComparisonRelation() { } + + /** + * Return a comparison relation from the result of a {@link Comparator} or {@link Comparable} result + * + * @param signifier The result of {@link Comparator#compare(Object, Object)} or {@link Comparable#compareTo(Object)} + * @return The intended {@link ComparisonRelation} of the signifier + */ + public static ComparisonRelation fromInt(int signifier) { + return signifier > 0 ? greaterThan() : signifier == 0 ? equal() : lessThan(); + } + + public static GreaterThan greaterThan() { + return GreaterThan.INSTANCE; + } + + public static LessThan lessThan() { + return LessThan.INSTANCE; + } + + public static Equal equal() { + return Equal.INSTANCE; + } + + public final static class LessThan extends ComparisonRelation { + private static final LessThan INSTANCE = new LessThan(); + + private LessThan() { + } + + @Override + public String toString() { + return "LessThan"; + } + + @Override + public R match(Fn1 aFn, Fn1 bFn, Fn1 cFn) { + return aFn.apply(this); + } + } + + public final static class Equal extends ComparisonRelation { + private static final Equal INSTANCE = new Equal(); + + private Equal() { + } + + public String toString() { + return "Equal"; + } + + @Override + public R match(Fn1 aFn, Fn1 bFn, Fn1 cFn) { + return bFn.apply(this); + } + } + + public final static class GreaterThan extends ComparisonRelation { + private static final GreaterThan INSTANCE = new GreaterThan(); + + private GreaterThan() { } + + @Override + public String toString() { + return "GreaterThan"; + } + + @Override + public R match(Fn1 aFn, Fn1 bFn, Fn1 cFn) { + return cFn.apply(this); + } + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/semigroup/builtin/MaxBy.java b/src/main/java/com/jnape/palatable/lambda/semigroup/builtin/MaxBy.java index 78d361ba8..2abee980e 100644 --- a/src/main/java/com/jnape/palatable/lambda/semigroup/builtin/MaxBy.java +++ b/src/main/java/com/jnape/palatable/lambda/semigroup/builtin/MaxBy.java @@ -18,6 +18,7 @@ * @param the value type * @param the mapped comparison type * @see Max + * @see MaxWith * @see MinBy */ public final class MaxBy> implements SemigroupFactory, A> { diff --git a/src/main/java/com/jnape/palatable/lambda/semigroup/builtin/MaxWith.java b/src/main/java/com/jnape/palatable/lambda/semigroup/builtin/MaxWith.java new file mode 100644 index 000000000..ed3f07373 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/semigroup/builtin/MaxWith.java @@ -0,0 +1,52 @@ +package com.jnape.palatable.lambda.semigroup.builtin; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.specialized.SemigroupFactory; +import com.jnape.palatable.lambda.semigroup.Semigroup; + +import java.util.Comparator; + +import static com.jnape.palatable.lambda.functions.builtin.fn3.LTWith.ltWith; + +/** + * Given a comparator for some type A, produce a {@link Semigroup} over A that chooses + * between two values x and y via the following rules: + *
    + *
  • If x is strictly less than y in terms of B, return y
  • + *
  • Otherwise, return x
  • + *
+ * + * @param
the value type + * @see Max + * @see MaxBy + * @see MinWith + */ +public final class MaxWith implements SemigroupFactory, A> { + + private static final MaxWith INSTANCE = new MaxWith<>(); + + private MaxWith() { + } + + @SuppressWarnings("unchecked") + public static MaxWith maxWith() { + return (MaxWith) INSTANCE; + } + + public static Semigroup maxWith(Comparator compareFn) { + return MaxWith.maxWith().apply(compareFn); + } + + public static Fn1 maxWith(Comparator compareFn, A x) { + return MaxWith.maxWith(compareFn).apply(x); + } + + public static A maxWith(Comparator compareFn, A x, A y) { + return maxWith(compareFn, x).apply(y); + } + + @Override + public Semigroup checkedApply(Comparator comparator) { + return (x, y) -> ltWith(comparator, y, x) ? y : x; + } +} \ No newline at end of file diff --git a/src/main/java/com/jnape/palatable/lambda/semigroup/builtin/MinWith.java b/src/main/java/com/jnape/palatable/lambda/semigroup/builtin/MinWith.java new file mode 100644 index 000000000..05eec2528 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/semigroup/builtin/MinWith.java @@ -0,0 +1,52 @@ +package com.jnape.palatable.lambda.semigroup.builtin; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.specialized.SemigroupFactory; +import com.jnape.palatable.lambda.semigroup.Semigroup; + +import java.util.Comparator; + +import static com.jnape.palatable.lambda.functions.builtin.fn3.GTWith.gtWith; + +/** + * Given a comparator for some type A, produce a {@link Semigroup} over A that chooses + * between two values x and y via the following rules: + *
    + *
  • If x is strictly greater than y in terms of B, return y
  • + *
  • Otherwise, return x
  • + *
+ * + * @param
the value type + * @see Min + * @see MinBy + * @see MaxBy + */ +public final class MinWith implements SemigroupFactory, A> { + + private static final MinWith INSTANCE = new MinWith<>(); + + private MinWith() { + } + + @SuppressWarnings("unchecked") + public static MinWith minWith() { + return (MinWith) INSTANCE; + } + + public static Semigroup minWith(Comparator compareFn) { + return MinWith.minWith().apply(compareFn); + } + + public static Fn1 minWith(Comparator compareFn, A x) { + return MinWith.minWith(compareFn).apply(x); + } + + public static A minWith(Comparator compareFn, A x, A y) { + return minWith(compareFn, x).apply(y); + } + + @Override + public Semigroup checkedApply(Comparator comparator) { + return (x, y) -> gtWith(comparator, y, x) ? y : x; + } +} diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/CmpEqWithTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/CmpEqWithTest.java new file mode 100644 index 000000000..8d062f8fd --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/CmpEqWithTest.java @@ -0,0 +1,19 @@ +package com.jnape.palatable.lambda.functions.builtin.fn3; + +import org.junit.Test; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; +import static com.jnape.palatable.lambda.functions.builtin.fn3.CmpEqBy.cmpEqBy; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class CmpEqWithTest { + @Test + public void comparisons() { + assertTrue(cmpEqBy(id(), 1, 1)); + assertFalse(cmpEqBy(id(), 1, 2)); + assertFalse(cmpEqBy(id(), 2, 1)); + + assertTrue(cmpEqBy(String::length, "b", "a")); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/CompareTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/CompareTest.java new file mode 100644 index 000000000..8817f94fc --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/CompareTest.java @@ -0,0 +1,19 @@ +package com.jnape.palatable.lambda.functions.builtin.fn3; + +import org.junit.Test; + +import static com.jnape.palatable.lambda.functions.builtin.fn3.Compare.compare; +import static com.jnape.palatable.lambda.functions.ordering.ComparisonRelation.equal; +import static com.jnape.palatable.lambda.functions.ordering.ComparisonRelation.greaterThan; +import static com.jnape.palatable.lambda.functions.ordering.ComparisonRelation.lessThan; +import static java.util.Comparator.naturalOrder; +import static org.junit.Assert.assertEquals; + +public class CompareTest { + @Test + public void comparisons() { + assertEquals(equal(), compare(naturalOrder(), 1, 1)); + assertEquals(lessThan(), compare(naturalOrder(), 2, 1)); + assertEquals(greaterThan(), compare(naturalOrder(), 1, 2)); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/GTEWithTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/GTEWithTest.java new file mode 100644 index 000000000..4ccaac4ec --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/GTEWithTest.java @@ -0,0 +1,21 @@ +package com.jnape.palatable.lambda.functions.builtin.fn3; + +import org.junit.Test; + +import static com.jnape.palatable.lambda.functions.builtin.fn3.GTEWith.gteWith; +import static java.util.Comparator.comparing; +import static java.util.Comparator.naturalOrder; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class GTEWithTest { + @Test + public void comparisons() { + assertTrue(gteWith(naturalOrder(), 1, 2)); + assertTrue(gteWith(naturalOrder(), 1, 1)); + assertFalse(gteWith(naturalOrder(), 2, 1)); + + assertTrue(gteWith(comparing(String::length), "b", "ab")); + assertTrue(gteWith(comparing(String::length), "bc", "ab")); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/GTWithTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/GTWithTest.java new file mode 100644 index 000000000..4520ee272 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/GTWithTest.java @@ -0,0 +1,20 @@ +package com.jnape.palatable.lambda.functions.builtin.fn3; + +import org.junit.Test; + +import static com.jnape.palatable.lambda.functions.builtin.fn3.GTWith.gtWith; +import static java.util.Comparator.comparing; +import static java.util.Comparator.naturalOrder; +import static junit.framework.TestCase.assertTrue; +import static org.junit.Assert.assertFalse; + +public class GTWithTest { + @Test + public void comparisons() { + assertTrue(gtWith(naturalOrder(), 1, 2)); + assertFalse(gtWith(naturalOrder(), 1, 1)); + assertFalse(gtWith(naturalOrder(), 2, 1)); + + assertTrue(gtWith(comparing(String::length), "bb", "aaa")); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/LTEWithTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/LTEWithTest.java new file mode 100644 index 000000000..18f749c16 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/LTEWithTest.java @@ -0,0 +1,21 @@ +package com.jnape.palatable.lambda.functions.builtin.fn3; + +import org.junit.Test; + +import static com.jnape.palatable.lambda.functions.builtin.fn3.LTEWith.lteWith; +import static java.util.Comparator.comparing; +import static java.util.Comparator.naturalOrder; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class LTEWithTest { + @Test + public void comparisons() { + assertTrue(lteWith(naturalOrder(), 2, 1)); + assertTrue(lteWith(naturalOrder(), 1, 1)); + assertFalse(lteWith(naturalOrder(), 1, 2)); + + assertTrue(lteWith(comparing(String::length), "ab", "b")); + assertTrue(lteWith(comparing(String::length), "ab", "bc")); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/LTWithTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/LTWithTest.java new file mode 100644 index 000000000..dacdfd2ff --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/LTWithTest.java @@ -0,0 +1,20 @@ +package com.jnape.palatable.lambda.functions.builtin.fn3; + +import org.junit.Test; + +import static com.jnape.palatable.lambda.functions.builtin.fn3.LTWith.ltWith; +import static java.util.Comparator.comparing; +import static java.util.Comparator.naturalOrder; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class LTWithTest { + @Test + public void comparisons() { + assertTrue(ltWith(naturalOrder(), 2, 1)); + assertFalse(ltWith(naturalOrder(), 1, 1)); + assertFalse(ltWith(naturalOrder(), 1, 2)); + + assertTrue(ltWith(comparing(String::length), "ab", "b")); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/ordering/ComparisonRelationTest.java b/src/test/java/com/jnape/palatable/lambda/functions/ordering/ComparisonRelationTest.java new file mode 100644 index 000000000..8b0083661 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/ordering/ComparisonRelationTest.java @@ -0,0 +1,23 @@ +package com.jnape.palatable.lambda.functions.ordering; + +import org.junit.Test; + +import static com.jnape.palatable.lambda.functions.ordering.ComparisonRelation.equal; +import static com.jnape.palatable.lambda.functions.ordering.ComparisonRelation.greaterThan; +import static com.jnape.palatable.lambda.functions.ordering.ComparisonRelation.lessThan; +import static java.lang.Integer.MAX_VALUE; +import static java.lang.Integer.MIN_VALUE; +import static org.junit.Assert.assertEquals; + +public class ComparisonRelationTest { + @Test + public void fromInt() { + assertEquals(greaterThan(), ComparisonRelation.fromInt(1)); + assertEquals(greaterThan(), ComparisonRelation.fromInt(MAX_VALUE)); + + assertEquals(equal(), ComparisonRelation.fromInt(0)); + + assertEquals(lessThan(), ComparisonRelation.fromInt(-1)); + assertEquals(lessThan(), ComparisonRelation.fromInt(MIN_VALUE)); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/semigroup/builtin/MaxWithTest.java b/src/test/java/com/jnape/palatable/lambda/semigroup/builtin/MaxWithTest.java new file mode 100644 index 000000000..49211aee0 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/semigroup/builtin/MaxWithTest.java @@ -0,0 +1,21 @@ +package com.jnape.palatable.lambda.semigroup.builtin; + +import org.junit.Test; + +import static com.jnape.palatable.lambda.semigroup.builtin.MaxWith.maxWith; +import static java.util.Comparator.comparing; +import static java.util.Comparator.naturalOrder; +import static org.junit.Assert.assertEquals; + +public class MaxWithTest { + @Test + public void semigroup() { + assertEquals((Integer) 1, maxWith(naturalOrder(), 1, 0)); + assertEquals((Integer) 1, maxWith(naturalOrder(), 1, 1)); + assertEquals((Integer) 2, maxWith(naturalOrder(), 1, 2)); + + assertEquals("ab", maxWith(comparing(String::length), "ab", "a")); + assertEquals("ab", maxWith(comparing(String::length), "ab", "cd")); + assertEquals("bc", maxWith(comparing(String::length), "a", "bc")); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/semigroup/builtin/MinWithTest.java b/src/test/java/com/jnape/palatable/lambda/semigroup/builtin/MinWithTest.java new file mode 100644 index 000000000..e6bfb82bb --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/semigroup/builtin/MinWithTest.java @@ -0,0 +1,21 @@ +package com.jnape.palatable.lambda.semigroup.builtin; + +import org.junit.Test; + +import static com.jnape.palatable.lambda.semigroup.builtin.MinWith.minWith; +import static java.util.Comparator.comparing; +import static java.util.Comparator.naturalOrder; +import static org.junit.Assert.assertEquals; + +public class MinWithTest { + @Test + public void semigroup() { + assertEquals((Integer) 1, minWith(naturalOrder(), 1, 2)); + assertEquals((Integer) 1, minWith(naturalOrder(), 1, 1)); + assertEquals((Integer) 0, minWith(naturalOrder(), 1, 0)); + + assertEquals("a", minWith(comparing(String::length), "a", "ab")); + assertEquals("ab", minWith(comparing(String::length), "ab", "cd")); + assertEquals("c", minWith(comparing(String::length), "ab", "c")); + } +} \ No newline at end of file From 4111f73fe9c3e288554031a3654e1c84d569143a Mon Sep 17 00:00:00 2001 From: jnape Date: Mon, 18 Nov 2019 11:35:46 -0600 Subject: [PATCH 12/29] Adding WriterTMatcher and WriterT#evalT/execT --- .../monad/transformer/builtin/WriterT.java | 26 +++++- .../transformer/builtin/WriterTTest.java | 44 ++++++---- .../testsupport/matchers/WriterTMatcher.java | 81 +++++++++++++++++++ 3 files changed, 134 insertions(+), 17 deletions(-) create mode 100644 src/test/java/testsupport/matchers/WriterTMatcher.java 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 aae118a15..7a0205543 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 @@ -46,13 +46,37 @@ private WriterT(Fn1, ? extends MonadRec, M>> writ * accumulation and the result inside the {@link Monad}. * * @param monoid the accumulation {@link Monoid} - * @param the inferred {@link Monad} result + * @param the inferred {@link MonadRec} result * @return the accumulation with the result */ public , M>> MAW runWriterT(Monoid monoid) { return writerFn.apply(monoid).coerce(); } + /** + * Given a {@link Monoid} for the accumulation, run the computation represented by this {@link WriterT} inside the + * {@link Monad monadic effect}, ignoring the resulting accumulation, yielding the value in isolation. + * + * @param monoid the accumulation {@link Monoid} + * @param the inferred {@link MonadRec} result + * @return the result + */ + public > MA evalWriterT(Monoid monoid) { + return runWriterT(monoid).fmap(Tuple2::_1).coerce(); + } + + /** + * Given a {@link Monoid} for the accumulation, run the computation represented by this {@link WriterT} inside the + * {@link Monad monadic effect}, ignoring the value, yielding the accumulation in isolation. + * + * @param monoid the accumulation {@link Monoid} + * @param the inferred {@link MonadRec} accumulation + * @return the accumulation + */ + public > MW execWriterT(Monoid monoid) { + return runWriterT(monoid).fmap(Tuple2::_2).coerce(); + } + /** * {@inheritDoc} */ diff --git a/src/test/java/com/jnape/palatable/lambda/monad/transformer/builtin/WriterTTest.java b/src/test/java/com/jnape/palatable/lambda/monad/transformer/builtin/WriterTTest.java index 932fe7011..c1caa0527 100644 --- a/src/test/java/com/jnape/palatable/lambda/monad/transformer/builtin/WriterTTest.java +++ b/src/test/java/com/jnape/palatable/lambda/monad/transformer/builtin/WriterTTest.java @@ -25,7 +25,11 @@ import static com.jnape.palatable.lambda.monad.transformer.builtin.WriterT.writerT; import static com.jnape.palatable.lambda.monoid.builtin.Join.join; import static com.jnape.palatable.lambda.monoid.builtin.Trivial.trivial; -import static org.junit.Assert.assertEquals; +import static org.hamcrest.core.IsEqual.equalTo; +import static org.junit.Assert.assertThat; +import static testsupport.matchers.WriterTMatcher.whenEvaluatedWith; +import static testsupport.matchers.WriterTMatcher.whenExecutedWith; +import static testsupport.matchers.WriterTMatcher.whenRunWith; import static testsupport.traits.Equivalence.equivalence; @RunWith(Traits.class) @@ -38,38 +42,46 @@ public Equivalence, Integer>> testSubject() { @Test public void accumulationUsesProvidedMonoid() { - Identity> result = writerT(new Identity<>(tuple(1, "foo"))) - .discardR(WriterT.tell(new Identity<>("bar"))) - .flatMap(x -> writerT(new Identity<>(tuple(x + 1, "baz")))) - .runWriterT(join()); + assertThat(writerT(new Identity<>(tuple(1, "foo"))) + .discardR(WriterT.tell(new Identity<>("bar"))) + .flatMap(x -> writerT(new Identity<>(tuple(x + 1, "baz")))), + whenRunWith(join(), equalTo(new Identity<>(tuple(2, "foobarbaz"))))); + } - assertEquals(new Identity<>(tuple(2, "foobarbaz")), result); + @Test + public void eval() { + assertThat(writerT(new Identity<>(tuple(1, "foo"))), + whenEvaluatedWith(join(), equalTo(new Identity<>(1)))); + } + + @Test + public void exec() { + assertThat(writerT(new Identity<>(tuple(1, "foo"))), + whenExecutedWith(join(), equalTo(new Identity<>("foo")))); } @Test public void tell() { - assertEquals(new Identity<>(tuple(UNIT, "")), - WriterT.tell(new Identity<>("")).runWriterT(join())); + assertThat(WriterT.tell(new Identity<>("")), + whenRunWith(join(), equalTo(new Identity<>(tuple(UNIT, ""))))); } @Test public void listen() { - assertEquals(new Identity<>(tuple(1, "")), - WriterT., Integer>listen(new Identity<>(1)).runWriterT(join())); + assertThat(WriterT.listen(new Identity<>(1)), + whenRunWith(join(), equalTo(new Identity<>(tuple(1, ""))))); } @Test public void staticPure() { - WriterT, Integer> apply = WriterT.>pureWriterT(pureIdentity()).apply(1); - assertEquals(new Identity<>(tuple(1, "")), - apply.runWriterT(join())); + assertThat(WriterT.>pureWriterT(pureIdentity()).apply(1), + whenRunWith(join(), equalTo(new Identity<>(tuple(1, ""))))); } @Test public void staticLift() { - WriterT, Integer> apply = WriterT.liftWriterT().apply(new Identity<>(1)); - assertEquals(new Identity<>(tuple(1, "")), - apply.runWriterT(join())); + assertThat(WriterT.liftWriterT().apply(new Identity<>(1)), + whenRunWith(join(), equalTo(new Identity<>(tuple(1, ""))))); } @Test(timeout = 500) diff --git a/src/test/java/testsupport/matchers/WriterTMatcher.java b/src/test/java/testsupport/matchers/WriterTMatcher.java new file mode 100644 index 000000000..36e2786bb --- /dev/null +++ b/src/test/java/testsupport/matchers/WriterTMatcher.java @@ -0,0 +1,81 @@ +package testsupport.matchers; + +import com.jnape.palatable.lambda.adt.hlist.Tuple2; +import com.jnape.palatable.lambda.monad.MonadRec; +import com.jnape.palatable.lambda.monad.transformer.builtin.WriterT; +import com.jnape.palatable.lambda.monoid.Monoid; +import org.hamcrest.Description; +import org.hamcrest.Matcher; +import org.hamcrest.TypeSafeMatcher; + +public final class WriterTMatcher, A> extends + TypeSafeMatcher>> { + + private final Matcher, M>> expected; + private final Monoid wMonoid; + + private WriterTMatcher(Matcher, M>> expected, Monoid wMonoid) { + this.wMonoid = wMonoid; + this.expected = expected; + } + + @Override + protected boolean matchesSafely(MonadRec> item) { + return expected.matches(item.>coerce().runWriterT(wMonoid)); + } + + @Override + public void describeTo(Description description) { + expected.describeTo(description); + } + + @Override + protected void describeMismatchSafely(MonadRec> item, Description mismatchDescription) { + expected.describeMismatch(item.>coerce().runWriterT(wMonoid), mismatchDescription); + } + + public static , A, MAW extends MonadRec, M>> + WriterTMatcher whenRunWith(Monoid wMonoid, Matcher matcher) { + return new WriterTMatcher<>(matcher, wMonoid); + } + + public static , A, MW extends MonadRec> + WriterTMatcher whenExecutedWith(Monoid wMonoid, Matcher matcher) { + return whenRunWith(wMonoid, new TypeSafeMatcher, M>>() { + @Override + protected boolean matchesSafely(MonadRec, M> item) { + return matcher.matches(item.fmap(Tuple2::_2)); + } + + @Override + public void describeTo(Description description) { + matcher.describeTo(description); + } + + @Override + protected void describeMismatchSafely(MonadRec, M> item, Description mismatchDescription) { + matcher.describeMismatch(item.fmap(Tuple2::_2), mismatchDescription); + } + }); + } + + public static , A, MA extends MonadRec> + WriterTMatcher whenEvaluatedWith(Monoid wMonoid, Matcher matcher) { + return whenRunWith(wMonoid, new TypeSafeMatcher, M>>() { + @Override + protected boolean matchesSafely(MonadRec, M> item) { + return matcher.matches(item.fmap(Tuple2::_1)); + } + + @Override + public void describeTo(Description description) { + matcher.describeTo(description); + } + + @Override + protected void describeMismatchSafely(MonadRec, M> item, Description mismatchDescription) { + matcher.describeMismatch(item.fmap(Tuple2::_1), mismatchDescription); + } + }); + } +} \ No newline at end of file From 1a84d0fd86a886a9c4c2a626c86f68147300b63c Mon Sep 17 00:00:00 2001 From: Alexander Bandukwala <7h3kk1d@gmail.com> Date: Tue, 19 Nov 2019 15:16:06 -0600 Subject: [PATCH 13/29] Added MonadError instance to EitherT --- .../monad/transformer/builtin/EitherT.java | 22 ++++++++++++++++++- .../transformer/builtin/EitherTTest.java | 13 +++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/jnape/palatable/lambda/monad/transformer/builtin/EitherT.java b/src/main/java/com/jnape/palatable/lambda/monad/transformer/builtin/EitherT.java index 806fdb636..a23a81fcf 100644 --- a/src/main/java/com/jnape/palatable/lambda/monad/transformer/builtin/EitherT.java +++ b/src/main/java/com/jnape/palatable/lambda/monad/transformer/builtin/EitherT.java @@ -10,6 +10,7 @@ import com.jnape.palatable.lambda.functor.builtin.Compose; import com.jnape.palatable.lambda.functor.builtin.Lazy; import com.jnape.palatable.lambda.monad.Monad; +import com.jnape.palatable.lambda.monad.MonadError; import com.jnape.palatable.lambda.monad.MonadRec; import com.jnape.palatable.lambda.monad.transformer.MonadT; @@ -28,7 +29,8 @@ */ public final class EitherT, L, R> implements Bifunctor>, - MonadT, EitherT> { + MonadT, EitherT>, + MonadError> { private final MonadRec, M> melr; @@ -120,6 +122,24 @@ public EitherT discardR(Applicative> appB) { return MonadT.super.discardR(appB).coerce(); } + /** + * {@inheritDoc} + */ + @Override + public EitherT throwError(L l) { + return eitherT(melr.pure(left(l))); + } + + /** + * {@inheritDoc} + */ + @Override + public EitherT catchError(Fn1>> recoveryFn) { + return eitherT(runEitherT().flatMap(e -> e.match( + l -> recoveryFn.apply(l).>coerce().runEitherT(), + r -> melr.pure(r).fmap(Either::right)))); + } + /** * {@inheritDoc} */ diff --git a/src/test/java/com/jnape/palatable/lambda/monad/transformer/builtin/EitherTTest.java b/src/test/java/com/jnape/palatable/lambda/monad/transformer/builtin/EitherTTest.java index cefc21eda..520a350b4 100644 --- a/src/test/java/com/jnape/palatable/lambda/monad/transformer/builtin/EitherTTest.java +++ b/src/test/java/com/jnape/palatable/lambda/monad/transformer/builtin/EitherTTest.java @@ -2,6 +2,7 @@ import com.jnape.palatable.lambda.adt.Either; import com.jnape.palatable.lambda.adt.Unit; +import com.jnape.palatable.lambda.functions.specialized.Pure; import com.jnape.palatable.lambda.functor.builtin.Identity; import com.jnape.palatable.lambda.io.IO; import com.jnape.palatable.traitor.annotations.TestTraits; @@ -27,6 +28,7 @@ import static com.jnape.palatable.lambda.io.IO.io; import static com.jnape.palatable.lambda.monad.transformer.builtin.EitherT.eitherT; import static com.jnape.palatable.lambda.monad.transformer.builtin.EitherT.liftEitherT; +import static com.jnape.palatable.lambda.monad.transformer.builtin.EitherT.pureEitherT; import static com.jnape.palatable.traitor.framework.Subjects.subjects; import static org.junit.Assert.assertEquals; @@ -73,4 +75,15 @@ public void staticPure() { .apply(1); assertEquals(eitherT(new Identity<>(right(1))), eitherT); } + + @Test + public void monadError() { + Pure, String, ?>> pure = pureEitherT(pureIdentity()); + + assertEquals(eitherT(new Identity<>(left("Hello"))), + pure., String, Integer>>apply(1).throwError("Hello")); + assertEquals(pure.apply(5), + EitherT., String, Integer>eitherT(new Identity<>(left("Hello"))) + .catchError(s -> pure.apply(s.length()))); + } } \ No newline at end of file From 0c5f7e2de8d9077becf0ec032e0637bb94b3089b Mon Sep 17 00:00:00 2001 From: jnape Date: Sun, 1 Dec 2019 17:00:02 -0600 Subject: [PATCH 14/29] Leveraging MonadErrorAssert in EitherTTest --- .../monad/transformer/builtin/EitherTTest.java | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/src/test/java/com/jnape/palatable/lambda/monad/transformer/builtin/EitherTTest.java b/src/test/java/com/jnape/palatable/lambda/monad/transformer/builtin/EitherTTest.java index 520a350b4..e495fb2fd 100644 --- a/src/test/java/com/jnape/palatable/lambda/monad/transformer/builtin/EitherTTest.java +++ b/src/test/java/com/jnape/palatable/lambda/monad/transformer/builtin/EitherTTest.java @@ -2,7 +2,6 @@ import com.jnape.palatable.lambda.adt.Either; import com.jnape.palatable.lambda.adt.Unit; -import com.jnape.palatable.lambda.functions.specialized.Pure; import com.jnape.palatable.lambda.functor.builtin.Identity; import com.jnape.palatable.lambda.io.IO; import com.jnape.palatable.traitor.annotations.TestTraits; @@ -28,9 +27,9 @@ import static com.jnape.palatable.lambda.io.IO.io; import static com.jnape.palatable.lambda.monad.transformer.builtin.EitherT.eitherT; import static com.jnape.palatable.lambda.monad.transformer.builtin.EitherT.liftEitherT; -import static com.jnape.palatable.lambda.monad.transformer.builtin.EitherT.pureEitherT; import static com.jnape.palatable.traitor.framework.Subjects.subjects; import static org.junit.Assert.assertEquals; +import static testsupport.assertion.MonadErrorAssert.assertLaws; @RunWith(Traits.class) public class EitherTTest { @@ -78,12 +77,9 @@ public void staticPure() { @Test public void monadError() { - Pure, String, ?>> pure = pureEitherT(pureIdentity()); - - assertEquals(eitherT(new Identity<>(left("Hello"))), - pure., String, Integer>>apply(1).throwError("Hello")); - assertEquals(pure.apply(5), - EitherT., String, Integer>eitherT(new Identity<>(left("Hello"))) - .catchError(s -> pure.apply(s.length()))); + assertLaws(subjects(eitherT(new Identity<>(right(1))), + eitherT(new Identity<>(left("")))), + "bar", + str -> eitherT(new Identity<>(right(str.length())))); } } \ No newline at end of file From ebce62cdadc1c925a1961a9f8030a415f85acb9c Mon Sep 17 00:00:00 2001 From: jnape Date: Tue, 10 Dec 2019 14:26:11 -0600 Subject: [PATCH 15/29] Updating CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 488f8b414..1afe4208b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/). ### Changed - `HList#cons` static factory method auto-promotes to specialized `HList` if there is one +- `EitherT` gains a `MonadError` instance ### Added - `MergeHMaps`, a `Monoid` that merges `HMap`s by merging the values via key-specified `Semigroup`s From 5cb722b8e640c444eabc6218977ecaa407210b06 Mon Sep 17 00:00:00 2001 From: Michael A Date: Mon, 2 Dec 2019 08:38:04 -0600 Subject: [PATCH 16/29] Added StateT and State matchers --- .../lambda/matchers/StateMatcherTest.java | 34 ++++++ .../lambda/matchers/StateTMatcherTest.java | 56 +++++++++ .../monad/transformer/builtin/StateTTest.java | 85 ++++++------- .../testsupport/matchers/StateMatcher.java | 90 ++++++++++++++ .../testsupport/matchers/StateTMatcher.java | 114 ++++++++++++++++++ 5 files changed, 331 insertions(+), 48 deletions(-) create mode 100644 src/test/java/com/jnape/palatable/lambda/matchers/StateMatcherTest.java create mode 100644 src/test/java/com/jnape/palatable/lambda/matchers/StateTMatcherTest.java create mode 100644 src/test/java/testsupport/matchers/StateMatcher.java create mode 100644 src/test/java/testsupport/matchers/StateTMatcher.java diff --git a/src/test/java/com/jnape/palatable/lambda/matchers/StateMatcherTest.java b/src/test/java/com/jnape/palatable/lambda/matchers/StateMatcherTest.java new file mode 100644 index 000000000..fb27f6f78 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/matchers/StateMatcherTest.java @@ -0,0 +1,34 @@ +package com.jnape.palatable.lambda.matchers; + +import com.jnape.palatable.lambda.adt.Either; +import org.junit.Test; + +import static com.jnape.palatable.lambda.adt.Either.left; +import static com.jnape.palatable.lambda.functor.builtin.State.state; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.core.IsEqual.equalTo; +import static testsupport.matchers.LeftMatcher.isLeftThat; +import static testsupport.matchers.RightMatcher.isRightThat; +import static testsupport.matchers.StateMatcher.*; + +public class StateMatcherTest { + + @Test + public void whenEvalWithMatcher() { + assertThat(state(Either.right(1)), + whenEvaluatedWith("0", isRightThat(equalTo(1)))); + } + + @Test + public void whenExecWithMatcher() { + assertThat(state(Either.right(1)), + whenExecutedWith(left("0"), isLeftThat(equalTo("0")))); + } + + @Test + public void whenRunWithMatcher() { + assertThat(state(Either.right(1)), + whenRunWith(left("0"), isRightThat(equalTo(1)), isLeftThat(equalTo("0")))); + } + +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/matchers/StateTMatcherTest.java b/src/test/java/com/jnape/palatable/lambda/matchers/StateTMatcherTest.java new file mode 100644 index 000000000..a54a0c19b --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/matchers/StateTMatcherTest.java @@ -0,0 +1,56 @@ +package com.jnape.palatable.lambda.matchers; + +import com.jnape.palatable.lambda.adt.Either; +import com.jnape.palatable.lambda.monad.transformer.builtin.StateT; +import org.junit.Test; +import testsupport.matchers.StateTMatcher; + +import java.util.concurrent.atomic.AtomicInteger; + +import static com.jnape.palatable.lambda.adt.Either.left; +import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; +import static com.jnape.palatable.lambda.io.IO.io; +import static com.jnape.palatable.lambda.monad.transformer.builtin.StateT.stateT; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.core.IsEqual.equalTo; +import static org.junit.Assert.assertEquals; +import static testsupport.matchers.IOMatcher.yieldsValue; +import static testsupport.matchers.LeftMatcher.isLeftThat; +import static testsupport.matchers.RightMatcher.isRightThat; +import static testsupport.matchers.StateTMatcher.*; + +public class StateTMatcherTest { + + @Test + public void whenEvalWithMatcher() { + assertThat(stateT(Either.right(1)), + StateTMatcher.whenEvaluatedWith("0", isRightThat(equalTo(1)))); + } + + @Test + public void whenExecWithMatcher() { + assertThat(stateT(Either.right(1)), + whenExecutedWith(left("0"), isRightThat(isLeftThat(equalTo("0"))))); + } + + @Test + public void whenRunWithUsingTwoMatchers() { + assertThat(stateT(Either.right(1)), + whenRunWith(left("0"), isRightThat(equalTo(1)), isRightThat(isLeftThat(equalTo("0"))))); + } + + @Test + public void whenRunWithUsingOneTupleMatcher() { + assertThat(stateT(Either.right(1)), + whenRunWith(left("0"), isRightThat(equalTo(tuple(1, left("0")))))); + } + + @Test + public void onlyRunsStateOnceWithTupleMatcher() { + AtomicInteger count = new AtomicInteger(0); + + assertThat(StateT.gets(s -> io(count::incrementAndGet)), + whenRunWith(0, yieldsValue(equalTo(tuple(1, 0))))); + assertEquals(1, count.get()); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/monad/transformer/builtin/StateTTest.java b/src/test/java/com/jnape/palatable/lambda/monad/transformer/builtin/StateTTest.java index b30fd4596..967947052 100644 --- a/src/test/java/com/jnape/palatable/lambda/monad/transformer/builtin/StateTTest.java +++ b/src/test/java/com/jnape/palatable/lambda/monad/transformer/builtin/StateTTest.java @@ -1,6 +1,5 @@ package com.jnape.palatable.lambda.monad.transformer.builtin; -import com.jnape.palatable.lambda.adt.Maybe; import com.jnape.palatable.lambda.adt.Unit; import com.jnape.palatable.lambda.adt.hlist.Tuple2; import com.jnape.palatable.lambda.functor.builtin.Identity; @@ -8,13 +7,7 @@ import com.jnape.palatable.traitor.runners.Traits; import org.junit.Test; import org.junit.runner.RunWith; -import testsupport.traits.ApplicativeLaws; -import testsupport.traits.Equivalence; -import testsupport.traits.FunctorLaws; -import testsupport.traits.MonadLaws; -import testsupport.traits.MonadReaderLaws; -import testsupport.traits.MonadRecLaws; -import testsupport.traits.MonadWriterLaws; +import testsupport.traits.*; import java.util.ArrayList; import java.util.List; @@ -26,18 +19,19 @@ import static com.jnape.palatable.lambda.optics.functions.Set.set; import static com.jnape.palatable.lambda.optics.lenses.ListLens.elementAt; import static java.util.Arrays.asList; -import static org.junit.Assert.assertEquals; +import static org.hamcrest.MatcherAssert.assertThat; +import static testsupport.matchers.StateTMatcher.*; import static testsupport.traits.Equivalence.equivalence; @RunWith(Traits.class) public class StateTTest { @TestTraits({FunctorLaws.class, - ApplicativeLaws.class, - MonadLaws.class, - MonadRecLaws.class, - MonadReaderLaws.class, - MonadWriterLaws.class}) + ApplicativeLaws.class, + MonadLaws.class, + MonadRecLaws.class, + MonadReaderLaws.class, + MonadWriterLaws.class}) public Equivalence, Integer>> testReader() { return equivalence(StateT.gets(s -> new Identity<>(s.length())), s -> s.runStateT("foo")); } @@ -47,85 +41,80 @@ public void evalAndExec() { StateT, Integer> stateT = StateT.stateT(str -> new Identity<>(tuple(str.length(), str + "_"))); - assertEquals(new Identity<>("__"), stateT.execT("_")); - assertEquals(new Identity<>(1), stateT.evalT("_")); + assertThat(stateT, whenExecuted("_", new Identity<>("__"))); + assertThat(stateT, whenEvaluated("_", new Identity<>(1))); } @Test public void mapStateT() { StateT, Integer> stateT = StateT.stateT(str -> new Identity<>(tuple(str.length(), str + "_"))); - assertEquals(just(tuple(4, "ABC_")), - stateT.mapStateT(id -> id.>>coerce() - .runIdentity() - .into((x, str) -> just(tuple(x + 1, str.toUpperCase())))) - .>>runStateT("abc")); + + assertThat(stateT.mapStateT(id -> id.>>coerce() + .runIdentity() + .into((x, str) -> just(tuple(x + 1, str.toUpperCase())))), + whenRun("abc", just(tuple(4, "ABC_")))); } @Test public void zipping() { - Tuple2> result = StateT., Identity>modify( + StateT, Identity, Unit> result = StateT., Identity>modify( s -> new Identity<>(set(elementAt(s.size()), just("one"), s))) - .discardL(StateT.modify(s -> new Identity<>(set(elementAt(s.size()), just("two"), s)))) - .>>>runStateT(new ArrayList<>()) - .runIdentity(); + .discardL(StateT.modify(s -> new Identity<>(set(elementAt(s.size()), just("two"), s)))); - assertEquals(tuple(UNIT, asList("one", "two")), - result); + assertThat(result, + whenRun(new ArrayList<>(), new Identity<>(tuple(UNIT, asList("one", "two"))))); } @Test public void withStateT() { StateT, Integer> stateT = StateT.stateT(str -> new Identity<>(tuple(str.length(), str + "_"))); - assertEquals(new Identity<>(tuple(3, "ABC_")), - stateT.withStateT(str -> new Identity<>(str.toUpperCase())).runStateT("abc")); + assertThat(stateT.withStateT(str -> new Identity<>(str.toUpperCase())), + whenRun("abc", new Identity<>(tuple(3, "ABC_")))); } @Test public void get() { - assertEquals(new Identity<>(tuple("state", "state")), - StateT.>get(pureIdentity()).runStateT("state")); + assertThat(StateT.get(pureIdentity()), + whenRun("state", new Identity<>(tuple("state", "state")))); } @Test public void gets() { - assertEquals(new Identity<>(tuple(5, "state")), - StateT., Integer>gets(s -> new Identity<>(s.length())).runStateT("state")); + assertThat(StateT.gets(s -> new Identity<>(s.length())), + whenRun("state", new Identity<>(tuple(5, "state")))); } @Test public void put() { - assertEquals(new Identity<>(tuple(UNIT, 1)), StateT.put(new Identity<>(1)).runStateT(0)); + assertThat(StateT.put(new Identity<>(1)), + whenRun(0, new Identity<>(tuple(UNIT, 1)))); } @Test public void modify() { - assertEquals(new Identity<>(tuple(UNIT, 1)), - StateT.>modify(x -> new Identity<>(x + 1)).runStateT(0)); + assertThat(StateT.modify(x -> new Identity<>(x + 1)), + whenRun(0, new Identity<>(tuple(UNIT, 1)))); } @Test public void stateT() { - assertEquals(new Identity<>(tuple(0, "_")), - StateT., Integer>stateT(new Identity<>(0)).runStateT("_")); - assertEquals(new Identity<>(tuple(1, "_1")), - StateT., Integer>stateT(s -> new Identity<>(tuple(s.length(), s + "1"))) - .runStateT("_")); + assertThat(StateT.stateT(new Identity<>(0)), + whenRun("_", new Identity<>(tuple(0, "_")))); + assertThat(StateT.stateT(s -> new Identity<>(tuple(s.length(), s + "1"))), + whenRun("_", new Identity<>(tuple(1, "_1")))); } @Test public void staticPure() { - assertEquals(new Identity<>(tuple(1, "foo")), - StateT.>pureStateT(pureIdentity()) - ., Integer>>apply(1) - .>>runStateT("foo")); + assertThat(StateT.>pureStateT(pureIdentity()).apply(1), + whenRun("foo", new Identity<>(tuple(1, "foo")))); } @Test public void staticLift() { - assertEquals(new Identity<>(tuple(1, "foo")), - StateT.liftStateT()., StateT, Integer>>apply(new Identity<>(1)) - .>>runStateT("foo")); + assertThat(StateT.liftStateT().apply(new Identity<>(1)), + whenRun("foo", new Identity<>(tuple(1, "foo")))); } } \ No newline at end of file diff --git a/src/test/java/testsupport/matchers/StateMatcher.java b/src/test/java/testsupport/matchers/StateMatcher.java new file mode 100644 index 000000000..4f23594f3 --- /dev/null +++ b/src/test/java/testsupport/matchers/StateMatcher.java @@ -0,0 +1,90 @@ +package testsupport.matchers; + +import com.jnape.palatable.lambda.adt.These; +import com.jnape.palatable.lambda.adt.hlist.Tuple2; +import com.jnape.palatable.lambda.functor.builtin.State; +import org.hamcrest.Description; +import org.hamcrest.Matcher; +import org.hamcrest.TypeSafeMatcher; + +import static com.jnape.palatable.lambda.adt.These.*; +import static com.jnape.palatable.lambda.io.IO.io; +import static org.hamcrest.Matchers.equalTo; + +public class StateMatcher extends TypeSafeMatcher> { + private final S initialState; + private final These, Matcher> matchers; + + private StateMatcher(S initialState, These, Matcher> matchers) { + this.initialState = initialState; + this.matchers = matchers; + } + + @Override + protected boolean matchesSafely(State item) { + Tuple2 ran = item.run(initialState); + return matchers.match(a -> a.matches(ran._1()), + b -> b.matches(ran._2()), + ab -> ab._1().matches(ran._1()) && ab._2().matches(ran._2())); + } + + @Override + public void describeTo(Description description) { + matchers.match(a -> io(() -> a.describeTo(description.appendText("Value matching "))), + b -> io(() -> b.describeTo(description.appendText("State matching "))), + ab -> io(() -> { + description.appendText("Value matching: "); + ab._1().describeTo(description); + description.appendText(" and state matching: "); + ab._2().describeTo(description); + })) + .unsafePerformIO(); + } + + @Override + protected void describeMismatchSafely(State item, Description mismatchDescription) { + Tuple2 ran = item.run(initialState); + matchers.match(a -> io(() -> { + mismatchDescription.appendText("value matching "); + a.describeMismatch(ran._1(), mismatchDescription); + }), + b -> io(() -> { + mismatchDescription.appendText("state matching "); + b.describeMismatch(ran._2(), mismatchDescription); + }), + ab -> io(() -> { + mismatchDescription.appendText("value matching: "); + ab._1().describeMismatch(ran._1(), mismatchDescription); + mismatchDescription.appendText(" and state matching: "); + ab._2().describeMismatch(ran._2(), mismatchDescription); + })) + .unsafePerformIO(); + } + + public static StateMatcher whenRunWith(S initialState, Matcher valueMatcher, Matcher stateMatcher) { + return new StateMatcher<>(initialState, both(valueMatcher, stateMatcher)); + } + + @SuppressWarnings("unused") + public static StateMatcher whenRun(S initialState, A value, S state) { + return whenRunWith(initialState, equalTo(value), equalTo(state)); + } + + public static StateMatcher whenExecutedWith(S initialState, Matcher stateMatcher) { + return new StateMatcher<>(initialState, b(stateMatcher)); + } + + @SuppressWarnings("unused") + public static StateMatcher whenExecuted(S initialState, S state) { + return whenExecutedWith(initialState, equalTo(state)); + } + + public static StateMatcher whenEvaluatedWith(S initialState, Matcher valueMatcher) { + return new StateMatcher<>(initialState, a(valueMatcher)); + } + + @SuppressWarnings("unused") + public static StateMatcher whenEvaluated(S initialState, A value) { + return whenEvaluatedWith(initialState, equalTo(value)); + } +} diff --git a/src/test/java/testsupport/matchers/StateTMatcher.java b/src/test/java/testsupport/matchers/StateTMatcher.java new file mode 100644 index 000000000..944b60f43 --- /dev/null +++ b/src/test/java/testsupport/matchers/StateTMatcher.java @@ -0,0 +1,114 @@ +package testsupport.matchers; + +import com.jnape.palatable.lambda.adt.Either; +import com.jnape.palatable.lambda.adt.These; +import com.jnape.palatable.lambda.adt.hlist.Tuple2; +import com.jnape.palatable.lambda.monad.MonadRec; +import com.jnape.palatable.lambda.monad.transformer.builtin.StateT; +import org.hamcrest.Description; +import org.hamcrest.Matcher; +import org.hamcrest.TypeSafeMatcher; + +import static com.jnape.palatable.lambda.adt.Either.left; +import static com.jnape.palatable.lambda.adt.Either.right; +import static com.jnape.palatable.lambda.io.IO.io; +import static org.hamcrest.Matchers.equalTo; + +public class StateTMatcher, A, MA extends MonadRec, MS extends MonadRec, MTS extends MonadRec, M>> extends TypeSafeMatcher> { + private final S initialState; + + private final Either, These, Matcher>> matcher; + + private StateTMatcher(S initialState, These, Matcher> matchers) { + this.initialState = initialState; + this.matcher = right(matchers); + } + + private StateTMatcher(S initialState, Matcher matcher) { + this.initialState = initialState; + this.matcher = left(matcher); + } + + @Override + protected boolean matchesSafely(StateT item) { + MonadRec, M> ran = item.runStateT(initialState); + return matcher.match(bothMatcher -> bothMatcher.matches(ran), + theseMatchers -> theseMatchers.match(a -> a.matches(ran.fmap(Tuple2::_1)), + b -> b.matches(ran.fmap(Tuple2::_2)), + ab -> ab._1().matches(ran.fmap(Tuple2::_1)) && ab._2().matches(ran.fmap(Tuple2::_2)))); + } + + @Override + public void describeTo(Description description) { + matcher.match(bothMatcher -> io(() -> bothMatcher.describeTo(description.appendText("Value and state matching "))), + theseMatchers -> theseMatchers.match(a -> io(() -> a.describeTo(description.appendText("Value matching "))), + b -> io(() -> b.describeTo(description.appendText("State matching "))), + ab -> io(() -> { + description.appendText("Value matching: "); + ab._1().describeTo(description); + description.appendText(" and state matching: "); + ab._2().describeTo(description); + }))) + .unsafePerformIO(); + } + + @Override + protected void describeMismatchSafely(StateT item, Description mismatchDescription) { + MonadRec, M> ran = item.runStateT(initialState); + + matcher.match(bothMatcher -> io(() -> { + mismatchDescription.appendText("value and state matching "); + bothMatcher.describeMismatch(ran, mismatchDescription); + }), + theseMatchers -> theseMatchers.match(a -> io(() -> { + mismatchDescription.appendText("value matching "); + a.describeMismatch(ran.fmap(Tuple2::_1), mismatchDescription); + }), + b -> io(() -> { + mismatchDescription.appendText("state matching "); + b.describeMismatch(ran.fmap(Tuple2::_2), mismatchDescription); + }), + ab -> io(() -> { + mismatchDescription.appendText("value matching: "); + ab._1().describeMismatch(ran.fmap(Tuple2::_1), mismatchDescription); + mismatchDescription.appendText(" and state matching: "); + ab._2().describeMismatch(ran.fmap(Tuple2::_2), mismatchDescription); + }))) + .unsafePerformIO(); + } + + public static , A, MA extends MonadRec, MS extends MonadRec, MTS extends MonadRec, M>> StateTMatcher whenRunWith(S initialState, Matcher bothMatcher) { + return new StateTMatcher<>(initialState, bothMatcher); + } + + public static , A, MA extends MonadRec, MS extends MonadRec, MTS extends MonadRec, M>> StateTMatcher whenRun(S initialState, MTS both) { + return whenRunWith(initialState, equalTo(both)); + } + + // Note: This constructor will run both matchers, which can run effects twice + public static , A, MA extends MonadRec, MS extends MonadRec, MTS extends MonadRec, M>> StateTMatcher whenRunWith(S initialState, Matcher valueMatcher, Matcher stateMatcher) { + return new StateTMatcher<>(initialState, These.both(valueMatcher, stateMatcher)); + } + + // Note: This constructor will run both matchers, which can run effects twice + @SuppressWarnings("unused") + public static , A, MA extends MonadRec, MS extends MonadRec, MTS extends MonadRec, M>> StateTMatcher whenRun(S initialState, MA value, MS state) { + return whenRunWith(initialState, equalTo(value), equalTo(state)); + } + + public static , A, MA extends MonadRec, MS extends MonadRec, MTS extends MonadRec, M>> StateTMatcher whenExecutedWith(S initialState, Matcher stateMatcher) { + return new StateTMatcher<>(initialState, These.b(stateMatcher)); + } + + public static , A, MA extends MonadRec, MS extends MonadRec, MTS extends MonadRec, M>> StateTMatcher whenExecuted(S initialState, MS state) { + return whenExecutedWith(initialState, equalTo(state)); + } + + public static , A, MA extends MonadRec, MS extends MonadRec, MTS extends MonadRec, M>> StateTMatcher whenEvaluatedWith(S initialState, Matcher valueMatcher) { + return new StateTMatcher<>(initialState, These.a(valueMatcher)); + } + + public static , A, MA extends MonadRec, MS extends MonadRec, MTS extends MonadRec, M>> StateTMatcher whenEvaluated(S initialState, MA value) { + return whenEvaluatedWith(initialState, equalTo(value)); + } +} From 2b659fbee448bac8ca570a281fc2bf1467774645 Mon Sep 17 00:00:00 2001 From: Michael A Date: Mon, 2 Dec 2019 08:51:08 -0600 Subject: [PATCH 17/29] Added type parameters to appease earlier versions of Java 8 --- .../palatable/lambda/matchers/StateTMatcherTest.java | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/test/java/com/jnape/palatable/lambda/matchers/StateTMatcherTest.java b/src/test/java/com/jnape/palatable/lambda/matchers/StateTMatcherTest.java index a54a0c19b..478a276ac 100644 --- a/src/test/java/com/jnape/palatable/lambda/matchers/StateTMatcherTest.java +++ b/src/test/java/com/jnape/palatable/lambda/matchers/StateTMatcherTest.java @@ -1,6 +1,9 @@ package com.jnape.palatable.lambda.matchers; import com.jnape.palatable.lambda.adt.Either; +import com.jnape.palatable.lambda.adt.hlist.Tuple2; +import com.jnape.palatable.lambda.io.IO; +import com.jnape.palatable.lambda.monad.MonadRec; import com.jnape.palatable.lambda.monad.transformer.builtin.StateT; import org.junit.Test; import testsupport.matchers.StateTMatcher; @@ -36,13 +39,15 @@ public void whenExecWithMatcher() { @Test public void whenRunWithUsingTwoMatchers() { assertThat(stateT(Either.right(1)), - whenRunWith(left("0"), isRightThat(equalTo(1)), isRightThat(isLeftThat(equalTo("0"))))); + StateTMatcher., Either, Integer, Either, Either>, Either>>>whenRunWith(left("0"), + isRightThat(equalTo(1)), isRightThat(isLeftThat(equalTo("0"))))); } @Test public void whenRunWithUsingOneTupleMatcher() { assertThat(stateT(Either.right(1)), - whenRunWith(left("0"), isRightThat(equalTo(tuple(1, left("0")))))); + StateTMatcher., Either, Integer, Either, Either>, Either>>>whenRunWith(left("0"), + isRightThat(equalTo(tuple(1, left("0")))))); } @Test @@ -50,7 +55,7 @@ public void onlyRunsStateOnceWithTupleMatcher() { AtomicInteger count = new AtomicInteger(0); assertThat(StateT.gets(s -> io(count::incrementAndGet)), - whenRunWith(0, yieldsValue(equalTo(tuple(1, 0))))); + StateTMatcher., Integer, IO, IO, IO>>whenRunWith(0, yieldsValue(equalTo(tuple(1, 0))))); assertEquals(1, count.get()); } } \ No newline at end of file From e3158d1f8aa06c2166f14d32a3a1e9c5c6e7f73f Mon Sep 17 00:00:00 2001 From: Michael A Date: Wed, 4 Dec 2019 15:34:12 -0600 Subject: [PATCH 18/29] Cleaned up type params; made double-run effects clearer --- .../lambda/matchers/StateTMatcherTest.java | 49 ++++++++++----- .../testsupport/matchers/StateTMatcher.java | 60 ++++++++++++------- 2 files changed, 71 insertions(+), 38 deletions(-) diff --git a/src/test/java/com/jnape/palatable/lambda/matchers/StateTMatcherTest.java b/src/test/java/com/jnape/palatable/lambda/matchers/StateTMatcherTest.java index 478a276ac..39a6a22b1 100644 --- a/src/test/java/com/jnape/palatable/lambda/matchers/StateTMatcherTest.java +++ b/src/test/java/com/jnape/palatable/lambda/matchers/StateTMatcherTest.java @@ -1,9 +1,6 @@ package com.jnape.palatable.lambda.matchers; import com.jnape.palatable.lambda.adt.Either; -import com.jnape.palatable.lambda.adt.hlist.Tuple2; -import com.jnape.palatable.lambda.io.IO; -import com.jnape.palatable.lambda.monad.MonadRec; import com.jnape.palatable.lambda.monad.transformer.builtin.StateT; import org.junit.Test; import testsupport.matchers.StateTMatcher; @@ -11,9 +8,11 @@ import java.util.concurrent.atomic.AtomicInteger; import static com.jnape.palatable.lambda.adt.Either.left; +import static com.jnape.palatable.lambda.adt.Either.right; import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; import static com.jnape.palatable.lambda.io.IO.io; import static com.jnape.palatable.lambda.monad.transformer.builtin.StateT.stateT; +import static org.hamcrest.CoreMatchers.not; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.core.IsEqual.equalTo; import static org.junit.Assert.assertEquals; @@ -25,37 +24,57 @@ public class StateTMatcherTest { @Test - public void whenEvalWithMatcher() { - assertThat(stateT(Either.right(1)), - StateTMatcher.whenEvaluatedWith("0", isRightThat(equalTo(1)))); + public void whenEvaluatedWithMatcher() { + assertThat(stateT(right(1)), + whenEvaluatedWith("0", isRightThat(equalTo(1)))); } @Test - public void whenExecWithMatcher() { - assertThat(stateT(Either.right(1)), + public void whenEvaluatedWithMatcherOnObject() { + assertThat(stateT(right(1)), + whenEvaluatedWith("0", not(equalTo(new Object())))); + } + + @Test + public void whenExecutedWithMatcher() { + assertThat(stateT(right(1)), whenExecutedWith(left("0"), isRightThat(isLeftThat(equalTo("0"))))); } + + @Test + public void whenExecutedWithMatcherOnObject() { + assertThat(stateT(right(1)), + whenExecutedWith(left("0"), not(equalTo(new Object())))); + } + @Test public void whenRunWithUsingTwoMatchers() { - assertThat(stateT(Either.right(1)), - StateTMatcher., Either, Integer, Either, Either>, Either>>>whenRunWith(left("0"), - isRightThat(equalTo(1)), isRightThat(isLeftThat(equalTo("0"))))); + //noinspection RedundantTypeArguments + assertThat(stateT(right(1)), + StateTMatcher., Either, Integer, Either, Either>>whenRunWithBoth(left("0"), + isRightThat(equalTo(1)), + isRightThat(isLeftThat(equalTo("0"))))); } @Test public void whenRunWithUsingOneTupleMatcher() { - assertThat(stateT(Either.right(1)), - StateTMatcher., Either, Integer, Either, Either>, Either>>>whenRunWith(left("0"), + assertThat(stateT(right(1)), + whenRunWith(left("0"), isRightThat(equalTo(tuple(1, left("0")))))); } + @Test + public void whenRunWithUsingOneTupleMatcherOnObject() { + assertThat(stateT(right(1)), + whenRunWith(left("0"), not(equalTo(new Object())))); + } + @Test public void onlyRunsStateOnceWithTupleMatcher() { AtomicInteger count = new AtomicInteger(0); - assertThat(StateT.gets(s -> io(count::incrementAndGet)), - StateTMatcher., Integer, IO, IO, IO>>whenRunWith(0, yieldsValue(equalTo(tuple(1, 0))))); + assertThat(StateT.gets(s -> io(count::incrementAndGet)), whenRunWith(0, yieldsValue(equalTo(tuple(1, 0))))); assertEquals(1, count.get()); } } \ No newline at end of file diff --git a/src/test/java/testsupport/matchers/StateTMatcher.java b/src/test/java/testsupport/matchers/StateTMatcher.java index 944b60f43..39ec23899 100644 --- a/src/test/java/testsupport/matchers/StateTMatcher.java +++ b/src/test/java/testsupport/matchers/StateTMatcher.java @@ -14,17 +14,17 @@ import static com.jnape.palatable.lambda.io.IO.io; import static org.hamcrest.Matchers.equalTo; -public class StateTMatcher, A, MA extends MonadRec, MS extends MonadRec, MTS extends MonadRec, M>> extends TypeSafeMatcher> { +public class StateTMatcher, A> extends TypeSafeMatcher> { private final S initialState; - private final Either, These, Matcher>> matcher; + private final Either, M>>, These>, Matcher>>> matcher; - private StateTMatcher(S initialState, These, Matcher> matchers) { + private StateTMatcher(S initialState, These>, Matcher>> matchers) { this.initialState = initialState; this.matcher = right(matchers); } - private StateTMatcher(S initialState, Matcher matcher) { + private StateTMatcher(S initialState, Matcher, M>> matcher) { this.initialState = initialState; this.matcher = left(matcher); } @@ -44,9 +44,9 @@ public void describeTo(Description description) { theseMatchers -> theseMatchers.match(a -> io(() -> a.describeTo(description.appendText("Value matching "))), b -> io(() -> b.describeTo(description.appendText("State matching "))), ab -> io(() -> { - description.appendText("Value matching: "); + description.appendText("Value run matching: "); ab._1().describeTo(description); - description.appendText(" and state matching: "); + description.appendText(", then state run matching: "); ab._2().describeTo(description); }))) .unsafePerformIO(); @@ -69,46 +69,60 @@ protected void describeMismatchSafely(StateT item, Description mismatch b.describeMismatch(ran.fmap(Tuple2::_2), mismatchDescription); }), ab -> io(() -> { - mismatchDescription.appendText("value matching: "); + mismatchDescription.appendText("value run matching: "); ab._1().describeMismatch(ran.fmap(Tuple2::_1), mismatchDescription); - mismatchDescription.appendText(" and state matching: "); + mismatchDescription.appendText(", then state run matching: "); ab._2().describeMismatch(ran.fmap(Tuple2::_2), mismatchDescription); }))) .unsafePerformIO(); } - public static , A, MA extends MonadRec, MS extends MonadRec, MTS extends MonadRec, M>> StateTMatcher whenRunWith(S initialState, Matcher bothMatcher) { - return new StateTMatcher<>(initialState, bothMatcher); + public static , A, MAS extends MonadRec, M>> StateTMatcher whenRunWith(S initialState, Matcher bothMatcher) { + return new StateTMatcher(initialState, extendMatcher(bothMatcher)); } - public static , A, MA extends MonadRec, MS extends MonadRec, MTS extends MonadRec, M>> StateTMatcher whenRun(S initialState, MTS both) { + public static , A> StateTMatcher whenRun(S initialState, MonadRec, M> both) { return whenRunWith(initialState, equalTo(both)); } - // Note: This constructor will run both matchers, which can run effects twice - public static , A, MA extends MonadRec, MS extends MonadRec, MTS extends MonadRec, M>> StateTMatcher whenRunWith(S initialState, Matcher valueMatcher, Matcher stateMatcher) { - return new StateTMatcher<>(initialState, These.both(valueMatcher, stateMatcher)); + // Note: This constructor will run both matchers, which will run effects twice + public static , A, MA extends MonadRec, MS extends MonadRec> StateTMatcher whenRunWithBoth(S initialState, Matcher valueMatcher, Matcher stateMatcher) { + return new StateTMatcher(initialState, These.both(extendMatcher(valueMatcher), extendMatcher(stateMatcher))); } - // Note: This constructor will run both matchers, which can run effects twice + // Note: This constructor will run both matchers, which will run effects twice @SuppressWarnings("unused") - public static , A, MA extends MonadRec, MS extends MonadRec, MTS extends MonadRec, M>> StateTMatcher whenRun(S initialState, MA value, MS state) { - return whenRunWith(initialState, equalTo(value), equalTo(state)); + public static , A, MA extends MonadRec, MS extends MonadRec> StateTMatcher whenRunBoth(S initialState, MonadRec value, MonadRec state) { + return whenRunWithBoth(initialState, equalTo(value), equalTo(state)); } - public static , A, MA extends MonadRec, MS extends MonadRec, MTS extends MonadRec, M>> StateTMatcher whenExecutedWith(S initialState, Matcher stateMatcher) { - return new StateTMatcher<>(initialState, These.b(stateMatcher)); + public static , A, MS extends MonadRec> StateTMatcher whenExecutedWith(S initialState, Matcher stateMatcher) { + return new StateTMatcher(initialState, These.b(extendMatcher(stateMatcher))); } - public static , A, MA extends MonadRec, MS extends MonadRec, MTS extends MonadRec, M>> StateTMatcher whenExecuted(S initialState, MS state) { + public static , A> StateTMatcher whenExecuted(S initialState, MonadRec state) { return whenExecutedWith(initialState, equalTo(state)); } - public static , A, MA extends MonadRec, MS extends MonadRec, MTS extends MonadRec, M>> StateTMatcher whenEvaluatedWith(S initialState, Matcher valueMatcher) { - return new StateTMatcher<>(initialState, These.a(valueMatcher)); + public static , A, MA extends MonadRec> StateTMatcher whenEvaluatedWith(S initialState, Matcher valueMatcher) { + return new StateTMatcher(initialState, These.a(extendMatcher(valueMatcher))); } - public static , A, MA extends MonadRec, MS extends MonadRec, MTS extends MonadRec, M>> StateTMatcher whenEvaluated(S initialState, MA value) { + public static , A> StateTMatcher whenEvaluated(S initialState, MonadRec value) { return whenEvaluatedWith(initialState, equalTo(value)); } + + private static , MX extends MonadRec> Matcher> extendMatcher(Matcher matcher) { + return new TypeSafeMatcher>() { + @Override + protected boolean matchesSafely(MonadRec item) { + return matcher.matches(item); + } + + @Override + public void describeTo(Description description) { + matcher.describeTo(description); + } + }; + } } From e22363dff7e863fd27ae887321c1a1c2e73e1858 Mon Sep 17 00:00:00 2001 From: jnape Date: Tue, 10 Dec 2019 15:21:58 -0600 Subject: [PATCH 19/29] Reformatting --- .../lambda/functor/builtin/StateTest.java | 47 +++---- .../lambda/matchers/StateMatcherTest.java | 19 +-- .../lambda/matchers/StateTMatcherTest.java | 35 ++--- .../monad/transformer/builtin/StateTTest.java | 68 +++++----- .../testsupport/matchers/StateMatcher.java | 58 ++++---- .../testsupport/matchers/StateTMatcher.java | 124 ++++++++++-------- 6 files changed, 183 insertions(+), 168 deletions(-) diff --git a/src/test/java/com/jnape/palatable/lambda/functor/builtin/StateTest.java b/src/test/java/com/jnape/palatable/lambda/functor/builtin/StateTest.java index db591bcfa..7e86042d2 100644 --- a/src/test/java/com/jnape/palatable/lambda/functor/builtin/StateTest.java +++ b/src/test/java/com/jnape/palatable/lambda/functor/builtin/StateTest.java @@ -1,8 +1,5 @@ package com.jnape.palatable.lambda.functor.builtin; -import com.jnape.palatable.lambda.adt.Unit; -import com.jnape.palatable.lambda.adt.hlist.Tuple2; -import com.jnape.palatable.lambda.functions.Fn1; import com.jnape.palatable.traitor.annotations.TestTraits; import com.jnape.palatable.traitor.runners.Traits; import org.junit.Test; @@ -17,8 +14,12 @@ import static com.jnape.palatable.lambda.adt.Unit.UNIT; import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; import static com.jnape.palatable.lambda.functions.builtin.fn2.Into.into; -import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; +import static testsupport.matchers.StateMatcher.whenEvaluated; +import static testsupport.matchers.StateMatcher.whenExecuted; +import static testsupport.matchers.StateMatcher.whenRun; import static testsupport.traits.Equivalence.equivalence; @RunWith(Traits.class) @@ -36,71 +37,65 @@ public Equivalence> testSubject() { @Test public void eval() { - State state = State.put(0); - assertEquals(state.run(1)._1(), state.eval(1)); + assertThat(State.gets(id()), whenEvaluated(1, 1)); } @Test public void exec() { - State state = State.put(0); - assertEquals(state.run(1)._2(), state.exec(1)); + assertThat(State.modify(x -> x + 1), whenExecuted(1, 2)); } @Test public void get() { - assertEquals(tuple(1, 1), State.get().run(1)); + assertThat(State.get(), whenRun(1, 1, 1)); } @Test public void put() { - assertEquals(tuple(UNIT, 1), State.put(1).run(1)); + assertThat(State.put(1), whenRun(1, UNIT, 1)); } @Test public void gets() { - assertEquals(tuple(0, "0"), State.gets(Integer::parseInt).run("0")); + assertThat(State.gets(Integer::parseInt), whenRun("0", 0, "0")); } @Test public void modify() { - assertEquals(tuple(UNIT, 1), State.modify(x -> x + 1).run(0)); + assertThat(State.modify(x -> x + 1), whenRun(0, UNIT, 1)); } @Test public void state() { - assertEquals(tuple(1, UNIT), State.state(1).run(UNIT)); - assertEquals(tuple(1, -1), State.state(x -> tuple(x + 1, x - 1)).run(0)); + assertThat(State.state(1), whenRun(UNIT, 1, UNIT)); + assertThat(State.state(x -> tuple(x + 1, x - 1)), whenRun(0, 1, -1)); } @Test public void stateAccumulation() { - State counter = State.get().flatMap(i -> State.put(i + 1).discardL(State.state(i))); - assertEquals(tuple(0, 1), counter.run(0)); + assertThat(State.get().flatMap(i -> State.put(i + 1).discardL(State.state(i))), + whenRun(0, 0, 1)); } @Test public void zipOrdering() { - Tuple2 result = State.state(s -> tuple(0, s + "1")) - .zip(State.>state(s -> tuple(x -> x + 1, s + "2"))) - .run("_"); - assertEquals(tuple(1, "_12"), result); + assertThat(State.state(s -> tuple(0, s + "1")) + .zip(State.state(s -> tuple(x -> x + 1, s + "2"))), + whenRun("_", 1, "_12")); } @Test public void withState() { - State modified = State.get().withState(x -> x + 1); - assertEquals(tuple(1, 1), modified.run(0)); + assertThat(State.get().withState(x -> x + 1), whenRun(0, 1, 1)); } @Test public void mapState() { - State modified = State.get().mapState(into((a, s) -> tuple(a + 1, s + 2))); - assertEquals(tuple(1, 2), modified.run(0)); + assertThat(State.get().mapState(into((a, s) -> tuple(a + 1, s + 2))), whenRun(0, 1, 2)); } @Test public void staticPure() { - State state = State.pureState().apply(1); - assertEquals(tuple(1, "foo"), state.run("foo")); + assertThat(State.pureState().apply(1), whenRun("foo", 1, "foo")); } } \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/matchers/StateMatcherTest.java b/src/test/java/com/jnape/palatable/lambda/matchers/StateMatcherTest.java index fb27f6f78..c36c06a51 100644 --- a/src/test/java/com/jnape/palatable/lambda/matchers/StateMatcherTest.java +++ b/src/test/java/com/jnape/palatable/lambda/matchers/StateMatcherTest.java @@ -1,34 +1,35 @@ package com.jnape.palatable.lambda.matchers; -import com.jnape.palatable.lambda.adt.Either; import org.junit.Test; import static com.jnape.palatable.lambda.adt.Either.left; +import static com.jnape.palatable.lambda.adt.Either.right; import static com.jnape.palatable.lambda.functor.builtin.State.state; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.core.IsEqual.equalTo; import static testsupport.matchers.LeftMatcher.isLeftThat; import static testsupport.matchers.RightMatcher.isRightThat; -import static testsupport.matchers.StateMatcher.*; +import static testsupport.matchers.StateMatcher.whenEvaluatedWith; +import static testsupport.matchers.StateMatcher.whenExecutedWith; +import static testsupport.matchers.StateMatcher.whenRunWith; public class StateMatcherTest { @Test public void whenEvalWithMatcher() { - assertThat(state(Either.right(1)), - whenEvaluatedWith("0", isRightThat(equalTo(1)))); + assertThat(state(right(1)), + whenEvaluatedWith("0", isRightThat(equalTo(1)))); } @Test public void whenExecWithMatcher() { - assertThat(state(Either.right(1)), - whenExecutedWith(left("0"), isLeftThat(equalTo("0")))); + assertThat(state(right(1)), + whenExecutedWith(left("0"), isLeftThat(equalTo("0")))); } @Test public void whenRunWithMatcher() { - assertThat(state(Either.right(1)), - whenRunWith(left("0"), isRightThat(equalTo(1)), isLeftThat(equalTo("0")))); + assertThat(state(right(1)), + whenRunWith(left("0"), isRightThat(equalTo(1)), isLeftThat(equalTo("0")))); } - } \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/matchers/StateTMatcherTest.java b/src/test/java/com/jnape/palatable/lambda/matchers/StateTMatcherTest.java index 39a6a22b1..e2e1b21c0 100644 --- a/src/test/java/com/jnape/palatable/lambda/matchers/StateTMatcherTest.java +++ b/src/test/java/com/jnape/palatable/lambda/matchers/StateTMatcherTest.java @@ -1,9 +1,7 @@ package com.jnape.palatable.lambda.matchers; -import com.jnape.palatable.lambda.adt.Either; -import com.jnape.palatable.lambda.monad.transformer.builtin.StateT; +import org.hamcrest.core.IsEqual; import org.junit.Test; -import testsupport.matchers.StateTMatcher; import java.util.concurrent.atomic.AtomicInteger; @@ -11,6 +9,7 @@ import static com.jnape.palatable.lambda.adt.Either.right; import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; import static com.jnape.palatable.lambda.io.IO.io; +import static com.jnape.palatable.lambda.monad.transformer.builtin.StateT.gets; import static com.jnape.palatable.lambda.monad.transformer.builtin.StateT.stateT; import static org.hamcrest.CoreMatchers.not; import static org.hamcrest.MatcherAssert.assertThat; @@ -19,62 +18,64 @@ import static testsupport.matchers.IOMatcher.yieldsValue; import static testsupport.matchers.LeftMatcher.isLeftThat; import static testsupport.matchers.RightMatcher.isRightThat; -import static testsupport.matchers.StateTMatcher.*; +import static testsupport.matchers.StateTMatcher.whenEvaluatedWith; +import static testsupport.matchers.StateTMatcher.whenExecutedWith; +import static testsupport.matchers.StateTMatcher.whenRunWith; +import static testsupport.matchers.StateTMatcher.whenRunWithBoth; public class StateTMatcherTest { @Test public void whenEvaluatedWithMatcher() { assertThat(stateT(right(1)), - whenEvaluatedWith("0", isRightThat(equalTo(1)))); + whenEvaluatedWith("0", isRightThat(equalTo(1)))); } @Test public void whenEvaluatedWithMatcherOnObject() { assertThat(stateT(right(1)), - whenEvaluatedWith("0", not(equalTo(new Object())))); + whenEvaluatedWith("0", not(equalTo(new Object())))); } @Test public void whenExecutedWithMatcher() { assertThat(stateT(right(1)), - whenExecutedWith(left("0"), isRightThat(isLeftThat(equalTo("0"))))); + whenExecutedWith(left("0"), isRightThat(isLeftThat(equalTo("0"))))); } - @Test public void whenExecutedWithMatcherOnObject() { assertThat(stateT(right(1)), - whenExecutedWith(left("0"), not(equalTo(new Object())))); + whenExecutedWith(left("0"), not(equalTo(new Object())))); } @Test + @SuppressWarnings("RedundantTypeArguments") public void whenRunWithUsingTwoMatchers() { - //noinspection RedundantTypeArguments assertThat(stateT(right(1)), - StateTMatcher., Either, Integer, Either, Either>>whenRunWithBoth(left("0"), - isRightThat(equalTo(1)), - isRightThat(isLeftThat(equalTo("0"))))); + whenRunWithBoth(left("0"), + isRightThat(IsEqual.equalTo(1)), + isRightThat(isLeftThat(equalTo("0"))))); } @Test public void whenRunWithUsingOneTupleMatcher() { assertThat(stateT(right(1)), - whenRunWith(left("0"), - isRightThat(equalTo(tuple(1, left("0")))))); + whenRunWith(left("0"), + isRightThat(equalTo(tuple(1, left("0")))))); } @Test public void whenRunWithUsingOneTupleMatcherOnObject() { assertThat(stateT(right(1)), - whenRunWith(left("0"), not(equalTo(new Object())))); + whenRunWith(left("0"), not(equalTo(new Object())))); } @Test public void onlyRunsStateOnceWithTupleMatcher() { AtomicInteger count = new AtomicInteger(0); - assertThat(StateT.gets(s -> io(count::incrementAndGet)), whenRunWith(0, yieldsValue(equalTo(tuple(1, 0))))); + assertThat(gets(s -> io(count::incrementAndGet)), whenRunWith(0, yieldsValue(equalTo(tuple(1, 0))))); assertEquals(1, count.get()); } } \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/monad/transformer/builtin/StateTTest.java b/src/test/java/com/jnape/palatable/lambda/monad/transformer/builtin/StateTTest.java index 967947052..b51888c9d 100644 --- a/src/test/java/com/jnape/palatable/lambda/monad/transformer/builtin/StateTTest.java +++ b/src/test/java/com/jnape/palatable/lambda/monad/transformer/builtin/StateTTest.java @@ -1,13 +1,18 @@ package com.jnape.palatable.lambda.monad.transformer.builtin; -import com.jnape.palatable.lambda.adt.Unit; import com.jnape.palatable.lambda.adt.hlist.Tuple2; import com.jnape.palatable.lambda.functor.builtin.Identity; import com.jnape.palatable.traitor.annotations.TestTraits; import com.jnape.palatable.traitor.runners.Traits; import org.junit.Test; import org.junit.runner.RunWith; -import testsupport.traits.*; +import testsupport.traits.ApplicativeLaws; +import testsupport.traits.Equivalence; +import testsupport.traits.FunctorLaws; +import testsupport.traits.MonadLaws; +import testsupport.traits.MonadReaderLaws; +import testsupport.traits.MonadRecLaws; +import testsupport.traits.MonadWriterLaws; import java.util.ArrayList; import java.util.List; @@ -20,18 +25,20 @@ import static com.jnape.palatable.lambda.optics.lenses.ListLens.elementAt; import static java.util.Arrays.asList; import static org.hamcrest.MatcherAssert.assertThat; -import static testsupport.matchers.StateTMatcher.*; +import static testsupport.matchers.StateTMatcher.whenEvaluated; +import static testsupport.matchers.StateTMatcher.whenExecuted; +import static testsupport.matchers.StateTMatcher.whenRun; import static testsupport.traits.Equivalence.equivalence; @RunWith(Traits.class) public class StateTTest { @TestTraits({FunctorLaws.class, - ApplicativeLaws.class, - MonadLaws.class, - MonadRecLaws.class, - MonadReaderLaws.class, - MonadWriterLaws.class}) + ApplicativeLaws.class, + MonadLaws.class, + MonadRecLaws.class, + MonadReaderLaws.class, + MonadWriterLaws.class}) public Equivalence, Integer>> testReader() { return equivalence(StateT.gets(s -> new Identity<>(s.length())), s -> s.runStateT("foo")); } @@ -47,74 +54,69 @@ public void evalAndExec() { @Test public void mapStateT() { - StateT, Integer> stateT = - StateT.stateT(str -> new Identity<>(tuple(str.length(), str + "_"))); - - assertThat(stateT.mapStateT(id -> id.>>coerce() - .runIdentity() - .into((x, str) -> just(tuple(x + 1, str.toUpperCase())))), - whenRun("abc", just(tuple(4, "ABC_")))); + assertThat(StateT., Integer>stateT(str -> new Identity<>(tuple(str.length(), str + "_"))) + .mapStateT(id -> id.>>coerce() + .runIdentity() + .into((x, str) -> just(tuple(x + 1, str.toUpperCase())))), + whenRun("abc", just(tuple(4, "ABC_")))); } @Test public void zipping() { - StateT, Identity, Unit> result = StateT., Identity>modify( - s -> new Identity<>(set(elementAt(s.size()), just("one"), s))) - .discardL(StateT.modify(s -> new Identity<>(set(elementAt(s.size()), just("two"), s)))); - - assertThat(result, - whenRun(new ArrayList<>(), new Identity<>(tuple(UNIT, asList("one", "two"))))); + assertThat( + StateT., Identity>modify(s -> new Identity<>(set(elementAt(s.size()), just("one"), s))) + .discardL(StateT.modify(s -> new Identity<>(set(elementAt(s.size()), just("two"), s)))), + whenRun(new ArrayList<>(), new Identity<>(tuple(UNIT, asList("one", "two"))))); } @Test public void withStateT() { - StateT, Integer> stateT = - StateT.stateT(str -> new Identity<>(tuple(str.length(), str + "_"))); - assertThat(stateT.withStateT(str -> new Identity<>(str.toUpperCase())), - whenRun("abc", new Identity<>(tuple(3, "ABC_")))); + assertThat(StateT., Integer>stateT(str -> new Identity<>(tuple(str.length(), str + "_"))) + .withStateT(str -> new Identity<>(str.toUpperCase())), + whenRun("abc", new Identity<>(tuple(3, "ABC_")))); } @Test public void get() { assertThat(StateT.get(pureIdentity()), - whenRun("state", new Identity<>(tuple("state", "state")))); + whenRun("state", new Identity<>(tuple("state", "state")))); } @Test public void gets() { assertThat(StateT.gets(s -> new Identity<>(s.length())), - whenRun("state", new Identity<>(tuple(5, "state")))); + whenRun("state", new Identity<>(tuple(5, "state")))); } @Test public void put() { assertThat(StateT.put(new Identity<>(1)), - whenRun(0, new Identity<>(tuple(UNIT, 1)))); + whenRun(0, new Identity<>(tuple(UNIT, 1)))); } @Test public void modify() { assertThat(StateT.modify(x -> new Identity<>(x + 1)), - whenRun(0, new Identity<>(tuple(UNIT, 1)))); + whenRun(0, new Identity<>(tuple(UNIT, 1)))); } @Test public void stateT() { assertThat(StateT.stateT(new Identity<>(0)), - whenRun("_", new Identity<>(tuple(0, "_")))); + whenRun("_", new Identity<>(tuple(0, "_")))); assertThat(StateT.stateT(s -> new Identity<>(tuple(s.length(), s + "1"))), - whenRun("_", new Identity<>(tuple(1, "_1")))); + whenRun("_", new Identity<>(tuple(1, "_1")))); } @Test public void staticPure() { assertThat(StateT.>pureStateT(pureIdentity()).apply(1), - whenRun("foo", new Identity<>(tuple(1, "foo")))); + whenRun("foo", new Identity<>(tuple(1, "foo")))); } @Test public void staticLift() { assertThat(StateT.liftStateT().apply(new Identity<>(1)), - whenRun("foo", new Identity<>(tuple(1, "foo")))); + whenRun("foo", new Identity<>(tuple(1, "foo")))); } } \ No newline at end of file diff --git a/src/test/java/testsupport/matchers/StateMatcher.java b/src/test/java/testsupport/matchers/StateMatcher.java index 4f23594f3..339deada8 100644 --- a/src/test/java/testsupport/matchers/StateMatcher.java +++ b/src/test/java/testsupport/matchers/StateMatcher.java @@ -7,12 +7,14 @@ import org.hamcrest.Matcher; import org.hamcrest.TypeSafeMatcher; -import static com.jnape.palatable.lambda.adt.These.*; +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.io.IO.io; import static org.hamcrest.Matchers.equalTo; -public class StateMatcher extends TypeSafeMatcher> { - private final S initialState; +public final class StateMatcher extends TypeSafeMatcher> { + private final S initialState; private final These, Matcher> matchers; private StateMatcher(S initialState, These, Matcher> matchers) { @@ -24,20 +26,20 @@ private StateMatcher(S initialState, These, Matcher item) { Tuple2 ran = item.run(initialState); return matchers.match(a -> a.matches(ran._1()), - b -> b.matches(ran._2()), - ab -> ab._1().matches(ran._1()) && ab._2().matches(ran._2())); + b -> b.matches(ran._2()), + ab -> ab._1().matches(ran._1()) && ab._2().matches(ran._2())); } @Override public void describeTo(Description description) { matchers.match(a -> io(() -> a.describeTo(description.appendText("Value matching "))), - b -> io(() -> b.describeTo(description.appendText("State matching "))), - ab -> io(() -> { - description.appendText("Value matching: "); - ab._1().describeTo(description); - description.appendText(" and state matching: "); - ab._2().describeTo(description); - })) + b -> io(() -> b.describeTo(description.appendText("State matching "))), + ab -> io(() -> { + description.appendText("Value matching: "); + ab._1().describeTo(description); + description.appendText(" and state matching: "); + ab._2().describeTo(description); + })) .unsafePerformIO(); } @@ -45,27 +47,27 @@ public void describeTo(Description description) { protected void describeMismatchSafely(State item, Description mismatchDescription) { Tuple2 ran = item.run(initialState); matchers.match(a -> io(() -> { - mismatchDescription.appendText("value matching "); - a.describeMismatch(ran._1(), mismatchDescription); - }), - b -> io(() -> { - mismatchDescription.appendText("state matching "); - b.describeMismatch(ran._2(), mismatchDescription); - }), - ab -> io(() -> { - mismatchDescription.appendText("value matching: "); - ab._1().describeMismatch(ran._1(), mismatchDescription); - mismatchDescription.appendText(" and state matching: "); - ab._2().describeMismatch(ran._2(), mismatchDescription); - })) + mismatchDescription.appendText("value matching "); + a.describeMismatch(ran._1(), mismatchDescription); + }), + b -> io(() -> { + mismatchDescription.appendText("state matching "); + b.describeMismatch(ran._2(), mismatchDescription); + }), + ab -> io(() -> { + mismatchDescription.appendText("value matching: "); + ab._1().describeMismatch(ran._1(), mismatchDescription); + mismatchDescription.appendText(" and state matching: "); + ab._2().describeMismatch(ran._2(), mismatchDescription); + })) .unsafePerformIO(); } - public static StateMatcher whenRunWith(S initialState, Matcher valueMatcher, Matcher stateMatcher) { + public static StateMatcher whenRunWith(S initialState, Matcher valueMatcher, + Matcher stateMatcher) { return new StateMatcher<>(initialState, both(valueMatcher, stateMatcher)); } - @SuppressWarnings("unused") public static StateMatcher whenRun(S initialState, A value, S state) { return whenRunWith(initialState, equalTo(value), equalTo(state)); } @@ -74,7 +76,6 @@ public static StateMatcher whenExecutedWith(S initialState, Matcher return new StateMatcher<>(initialState, b(stateMatcher)); } - @SuppressWarnings("unused") public static StateMatcher whenExecuted(S initialState, S state) { return whenExecutedWith(initialState, equalTo(state)); } @@ -83,7 +84,6 @@ public static StateMatcher whenEvaluatedWith(S initialState, Matche return new StateMatcher<>(initialState, a(valueMatcher)); } - @SuppressWarnings("unused") public static StateMatcher whenEvaluated(S initialState, A value) { return whenEvaluatedWith(initialState, equalTo(value)); } diff --git a/src/test/java/testsupport/matchers/StateTMatcher.java b/src/test/java/testsupport/matchers/StateTMatcher.java index 39ec23899..8bfa97684 100644 --- a/src/test/java/testsupport/matchers/StateTMatcher.java +++ b/src/test/java/testsupport/matchers/StateTMatcher.java @@ -11,44 +11,49 @@ import static com.jnape.palatable.lambda.adt.Either.left; import static com.jnape.palatable.lambda.adt.Either.right; +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.io.IO.io; import static org.hamcrest.Matchers.equalTo; -public class StateTMatcher, A> extends TypeSafeMatcher> { +public final class StateTMatcher, A> extends TypeSafeMatcher> { private final S initialState; - private final Either, M>>, These>, Matcher>>> matcher; + private final Either< + Matcher, M>>, + These>, Matcher>>> matcher; - private StateTMatcher(S initialState, These>, Matcher>> matchers) { + private StateTMatcher(S initialState, + Either, M>>, + These>, Matcher>>> matcher) { this.initialState = initialState; - this.matcher = right(matchers); - } - - private StateTMatcher(S initialState, Matcher, M>> matcher) { - this.initialState = initialState; - this.matcher = left(matcher); + this.matcher = matcher; } @Override protected boolean matchesSafely(StateT item) { MonadRec, M> ran = item.runStateT(initialState); return matcher.match(bothMatcher -> bothMatcher.matches(ran), - theseMatchers -> theseMatchers.match(a -> a.matches(ran.fmap(Tuple2::_1)), - b -> b.matches(ran.fmap(Tuple2::_2)), - ab -> ab._1().matches(ran.fmap(Tuple2::_1)) && ab._2().matches(ran.fmap(Tuple2::_2)))); + theseMatchers -> theseMatchers.match( + a -> a.matches(ran.fmap(Tuple2::_1)), + b -> b.matches(ran.fmap(Tuple2::_2)), + ab -> ab._1().matches(ran.fmap(Tuple2::_1)) + && ab._2().matches(ran.fmap(Tuple2::_2)))); } @Override public void describeTo(Description description) { - matcher.match(bothMatcher -> io(() -> bothMatcher.describeTo(description.appendText("Value and state matching "))), - theseMatchers -> theseMatchers.match(a -> io(() -> a.describeTo(description.appendText("Value matching "))), - b -> io(() -> b.describeTo(description.appendText("State matching "))), - ab -> io(() -> { - description.appendText("Value run matching: "); - ab._1().describeTo(description); - description.appendText(", then state run matching: "); - ab._2().describeTo(description); - }))) + matcher.match(both -> io(() -> both.describeTo(description.appendText("Value and state matching "))), + these -> these.match( + a -> io(() -> a.describeTo(description.appendText("Value matching "))), + b -> io(() -> b.describeTo(description.appendText("State matching "))), + ab -> io(() -> { + description.appendText("Value run matching: "); + ab._1().describeTo(description); + description.appendText(", then state run matching: "); + ab._2().describeTo(description); + }))) .unsafePerformIO(); } @@ -57,62 +62,73 @@ protected void describeMismatchSafely(StateT item, Description mismatch MonadRec, M> ran = item.runStateT(initialState); matcher.match(bothMatcher -> io(() -> { - mismatchDescription.appendText("value and state matching "); - bothMatcher.describeMismatch(ran, mismatchDescription); - }), - theseMatchers -> theseMatchers.match(a -> io(() -> { - mismatchDescription.appendText("value matching "); - a.describeMismatch(ran.fmap(Tuple2::_1), mismatchDescription); - }), - b -> io(() -> { - mismatchDescription.appendText("state matching "); - b.describeMismatch(ran.fmap(Tuple2::_2), mismatchDescription); - }), - ab -> io(() -> { - mismatchDescription.appendText("value run matching: "); - ab._1().describeMismatch(ran.fmap(Tuple2::_1), mismatchDescription); - mismatchDescription.appendText(", then state run matching: "); - ab._2().describeMismatch(ran.fmap(Tuple2::_2), mismatchDescription); - }))) + mismatchDescription.appendText("value and state matching "); + bothMatcher.describeMismatch(ran, mismatchDescription); + }), + theseMatchers -> theseMatchers.match( + a -> io(() -> { + mismatchDescription.appendText("value matching "); + a.describeMismatch(ran.fmap(Tuple2::_1), mismatchDescription); + }), + b -> io(() -> { + mismatchDescription.appendText("state matching "); + b.describeMismatch(ran.fmap(Tuple2::_2), mismatchDescription); + }), + ab -> io(() -> { + mismatchDescription.appendText("value run matching: "); + ab._1().describeMismatch(ran.fmap(Tuple2::_1), mismatchDescription); + mismatchDescription.appendText(", then state run matching: "); + ab._2().describeMismatch(ran.fmap(Tuple2::_2), mismatchDescription); + }))) .unsafePerformIO(); } - public static , A, MAS extends MonadRec, M>> StateTMatcher whenRunWith(S initialState, Matcher bothMatcher) { - return new StateTMatcher(initialState, extendMatcher(bothMatcher)); + public static , A, MAS extends MonadRec, M>> StateTMatcher + whenRunWith(S initialState, Matcher bothMatcher) { + return new StateTMatcher(initialState, left(extendMatcher(bothMatcher))); } - public static , A> StateTMatcher whenRun(S initialState, MonadRec, M> both) { + public static , A> StateTMatcher whenRun( + S initialState, MonadRec, M> both) { return whenRunWith(initialState, equalTo(both)); } - // Note: This constructor will run both matchers, which will run effects twice - public static , A, MA extends MonadRec, MS extends MonadRec> StateTMatcher whenRunWithBoth(S initialState, Matcher valueMatcher, Matcher stateMatcher) { - return new StateTMatcher(initialState, These.both(extendMatcher(valueMatcher), extendMatcher(stateMatcher))); + public static , A, MA extends MonadRec, MS extends MonadRec> + StateTMatcher whenRunWithBoth(S initialState, + Matcher valueMatcher, + Matcher stateMatcher) { + return new StateTMatcher(initialState, right(both(extendMatcher(valueMatcher), + extendMatcher(stateMatcher)))); } - // Note: This constructor will run both matchers, which will run effects twice - @SuppressWarnings("unused") - public static , A, MA extends MonadRec, MS extends MonadRec> StateTMatcher whenRunBoth(S initialState, MonadRec value, MonadRec state) { + public static , A> StateTMatcher whenRunBoth(S initialState, + MonadRec value, + MonadRec state) { return whenRunWithBoth(initialState, equalTo(value), equalTo(state)); } - public static , A, MS extends MonadRec> StateTMatcher whenExecutedWith(S initialState, Matcher stateMatcher) { - return new StateTMatcher(initialState, These.b(extendMatcher(stateMatcher))); + public static , A, MS extends MonadRec> StateTMatcher whenExecutedWith( + S initialState, Matcher stateMatcher) { + return new StateTMatcher(initialState, right(b(extendMatcher(stateMatcher)))); } - public static , A> StateTMatcher whenExecuted(S initialState, MonadRec state) { + public static , A> StateTMatcher whenExecuted(S initialState, + MonadRec state) { return whenExecutedWith(initialState, equalTo(state)); } - public static , A, MA extends MonadRec> StateTMatcher whenEvaluatedWith(S initialState, Matcher valueMatcher) { - return new StateTMatcher(initialState, These.a(extendMatcher(valueMatcher))); + public static , A, MA extends MonadRec> StateTMatcher whenEvaluatedWith( + S initialState, Matcher valueMatcher) { + return new StateTMatcher(initialState, right(a(extendMatcher(valueMatcher)))); } - public static , A> StateTMatcher whenEvaluated(S initialState, MonadRec value) { + public static , A> StateTMatcher whenEvaluated(S initialState, + MonadRec value) { return whenEvaluatedWith(initialState, equalTo(value)); } - private static , MX extends MonadRec> Matcher> extendMatcher(Matcher matcher) { + private static , MX extends MonadRec> Matcher> extendMatcher( + Matcher matcher) { return new TypeSafeMatcher>() { @Override protected boolean matchesSafely(MonadRec item) { From 50448da248f596b9f822be6fcd4b900bca76427d Mon Sep 17 00:00:00 2001 From: "jon.woolwine" Date: Thu, 12 Dec 2019 11:09:46 -0600 Subject: [PATCH 20/29] Adding MaybeT::filter --- .../lambda/monad/transformer/builtin/MaybeT.java | 11 +++++++++++ .../lambda/monad/transformer/builtin/MaybeTTest.java | 9 +++++++++ 2 files changed, 20 insertions(+) diff --git a/src/main/java/com/jnape/palatable/lambda/monad/transformer/builtin/MaybeT.java b/src/main/java/com/jnape/palatable/lambda/monad/transformer/builtin/MaybeT.java index ca588a7bc..da2ce2725 100644 --- a/src/main/java/com/jnape/palatable/lambda/monad/transformer/builtin/MaybeT.java +++ b/src/main/java/com/jnape/palatable/lambda/monad/transformer/builtin/MaybeT.java @@ -44,6 +44,17 @@ public , M>> MMA runMaybeT() { return mma.coerce(); } + /** + * If the embedded value is present and satisfies predicate + * then return just the embedded value + * + * @param predicate the predicate to apply to the embedded value + * @return maybe the satisfied value embedded under M + */ + public MaybeT filter(Fn1 predicate) { + return maybeT(mma.fmap(ma -> ma.filter(predicate))); + } + /** * {@inheritDoc} */ diff --git a/src/test/java/com/jnape/palatable/lambda/monad/transformer/builtin/MaybeTTest.java b/src/test/java/com/jnape/palatable/lambda/monad/transformer/builtin/MaybeTTest.java index 6af53f359..87e0f9df5 100644 --- a/src/test/java/com/jnape/palatable/lambda/monad/transformer/builtin/MaybeTTest.java +++ b/src/test/java/com/jnape/palatable/lambda/monad/transformer/builtin/MaybeTTest.java @@ -22,6 +22,8 @@ import static com.jnape.palatable.lambda.adt.Either.right; import static com.jnape.palatable.lambda.adt.Maybe.just; import static com.jnape.palatable.lambda.adt.Maybe.nothing; +import static com.jnape.palatable.lambda.functions.builtin.fn2.GT.gt; +import static com.jnape.palatable.lambda.functions.builtin.fn2.LT.lt; import static com.jnape.palatable.lambda.functor.builtin.Identity.pureIdentity; import static com.jnape.palatable.lambda.functor.builtin.Lazy.lazy; import static com.jnape.palatable.lambda.io.IO.io; @@ -70,4 +72,11 @@ public void composedZip() { .unsafePerformAsyncIO(Executors.newFixedThreadPool(2)) .join(); } + + @Test + public void filter() { + MaybeT, Integer> maybeT = pureMaybeT(pureIdentity()).apply(1); + assertEquals(maybeT(new Identity<>(just(1))), maybeT.filter(gt(0))); + assertEquals(maybeT(new Identity<>(nothing())), maybeT.filter(lt(0))); + } } \ No newline at end of file From 44324d4d2d6e0be180242179234a02589f2d6302 Mon Sep 17 00:00:00 2001 From: Alexander Bandukwala <7h3kk1d@gmail.com> Date: Fri, 20 Dec 2019 11:42:16 -0600 Subject: [PATCH 21/29] Added EitherMatcher in place of Left/RightMatcher --- .../jnape/palatable/lambda/adt/TryTest.java | 2 +- .../functions/builtin/fn1/CoalesceTest.java | 4 +- .../lambda/matchers/StateMatcherTest.java | 4 +- .../lambda/matchers/StateTMatcherTest.java | 4 +- .../testsupport/matchers/EitherMatcher.java | 54 +++++++++++++++++++ .../testsupport/matchers/LeftMatcher.java | 44 --------------- .../testsupport/matchers/RightMatcher.java | 44 --------------- 7 files changed, 61 insertions(+), 95 deletions(-) create mode 100644 src/test/java/testsupport/matchers/EitherMatcher.java delete mode 100644 src/test/java/testsupport/matchers/LeftMatcher.java delete mode 100644 src/test/java/testsupport/matchers/RightMatcher.java diff --git a/src/test/java/com/jnape/palatable/lambda/adt/TryTest.java b/src/test/java/com/jnape/palatable/lambda/adt/TryTest.java index 1d4ed3b0f..dd4ff5dfb 100644 --- a/src/test/java/com/jnape/palatable/lambda/adt/TryTest.java +++ b/src/test/java/com/jnape/palatable/lambda/adt/TryTest.java @@ -41,7 +41,7 @@ import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static testsupport.assertion.MonadErrorAssert.assertLaws; -import static testsupport.matchers.LeftMatcher.isLeftThat; +import static testsupport.matchers.EitherMatcher.isLeftThat; @RunWith(Traits.class) public class TryTest { diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/CoalesceTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/CoalesceTest.java index 68db636c1..6502e6104 100644 --- a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/CoalesceTest.java +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/CoalesceTest.java @@ -8,10 +8,10 @@ import static java.util.Arrays.asList; import static java.util.Collections.emptyList; import static org.junit.Assert.assertThat; +import static testsupport.matchers.EitherMatcher.isLeftThat; +import static testsupport.matchers.EitherMatcher.isRightThat; import static testsupport.matchers.IterableMatcher.isEmpty; import static testsupport.matchers.IterableMatcher.iterates; -import static testsupport.matchers.LeftMatcher.isLeftThat; -import static testsupport.matchers.RightMatcher.isRightThat; public class CoalesceTest { diff --git a/src/test/java/com/jnape/palatable/lambda/matchers/StateMatcherTest.java b/src/test/java/com/jnape/palatable/lambda/matchers/StateMatcherTest.java index c36c06a51..42e1eddd3 100644 --- a/src/test/java/com/jnape/palatable/lambda/matchers/StateMatcherTest.java +++ b/src/test/java/com/jnape/palatable/lambda/matchers/StateMatcherTest.java @@ -7,8 +7,8 @@ import static com.jnape.palatable.lambda.functor.builtin.State.state; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.core.IsEqual.equalTo; -import static testsupport.matchers.LeftMatcher.isLeftThat; -import static testsupport.matchers.RightMatcher.isRightThat; +import static testsupport.matchers.EitherMatcher.isLeftThat; +import static testsupport.matchers.EitherMatcher.isRightThat; import static testsupport.matchers.StateMatcher.whenEvaluatedWith; import static testsupport.matchers.StateMatcher.whenExecutedWith; import static testsupport.matchers.StateMatcher.whenRunWith; diff --git a/src/test/java/com/jnape/palatable/lambda/matchers/StateTMatcherTest.java b/src/test/java/com/jnape/palatable/lambda/matchers/StateTMatcherTest.java index e2e1b21c0..562bb8bb4 100644 --- a/src/test/java/com/jnape/palatable/lambda/matchers/StateTMatcherTest.java +++ b/src/test/java/com/jnape/palatable/lambda/matchers/StateTMatcherTest.java @@ -15,9 +15,9 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.core.IsEqual.equalTo; import static org.junit.Assert.assertEquals; +import static testsupport.matchers.EitherMatcher.isLeftThat; +import static testsupport.matchers.EitherMatcher.isRightThat; import static testsupport.matchers.IOMatcher.yieldsValue; -import static testsupport.matchers.LeftMatcher.isLeftThat; -import static testsupport.matchers.RightMatcher.isRightThat; import static testsupport.matchers.StateTMatcher.whenEvaluatedWith; import static testsupport.matchers.StateTMatcher.whenExecutedWith; import static testsupport.matchers.StateTMatcher.whenRunWith; diff --git a/src/test/java/testsupport/matchers/EitherMatcher.java b/src/test/java/testsupport/matchers/EitherMatcher.java new file mode 100644 index 000000000..748d01be2 --- /dev/null +++ b/src/test/java/testsupport/matchers/EitherMatcher.java @@ -0,0 +1,54 @@ +package testsupport.matchers; + +import com.jnape.palatable.lambda.adt.Either; +import org.hamcrest.Description; +import org.hamcrest.Matcher; +import org.hamcrest.TypeSafeMatcher; + +import static com.jnape.palatable.lambda.adt.Either.left; +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; + +public final class EitherMatcher extends TypeSafeMatcher> { + private final Either, Matcher> matcher; + + private EitherMatcher(Either, Matcher> matcher) { + this.matcher = matcher; + } + + @Override + protected void describeMismatchSafely(Either item, Description mismatchDescription) { + mismatchDescription.appendText("was "); + item.match(l -> matcher.match(lMatcher -> io(() -> lMatcher.describeMismatch(l, mismatchDescription)), + rMatcher -> io(() -> mismatchDescription.appendValue(item))), + r -> matcher.match(lMatcher -> io(() -> mismatchDescription.appendValue(item)), + lMatcher -> io(() -> lMatcher.describeMismatch(r, mismatchDescription)))) + .unsafePerformIO(); + } + + @Override + protected boolean matchesSafely(Either actual) { + return actual.match(l -> matcher.match(lMatcher -> lMatcher.matches(l), + constantly(false)), + r -> matcher.match(constantly(false), + rMatcher -> rMatcher.matches(r))); + } + + @Override + public void describeTo(Description description) { + matcher.match(l -> io(() -> description.appendText("Left value of ")) + .flatMap(constantly(io(() -> l.describeTo(description)))), + r -> io(() -> description.appendText("Right value of ")) + .flatMap(constantly(io(() -> r.describeTo(description))))) + .unsafePerformIO(); + } + + public static EitherMatcher isLeftThat(Matcher lMatcher) { + return new EitherMatcher<>(left(lMatcher)); + } + + public static EitherMatcher isRightThat(Matcher rMatcher) { + return new EitherMatcher<>(right(rMatcher)); + } +} diff --git a/src/test/java/testsupport/matchers/LeftMatcher.java b/src/test/java/testsupport/matchers/LeftMatcher.java deleted file mode 100644 index 34ea33079..000000000 --- a/src/test/java/testsupport/matchers/LeftMatcher.java +++ /dev/null @@ -1,44 +0,0 @@ -package testsupport.matchers; - -import com.jnape.palatable.lambda.adt.Either; -import org.hamcrest.Description; -import org.hamcrest.Matcher; -import org.hamcrest.TypeSafeMatcher; - -import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; -import static com.jnape.palatable.lambda.io.IO.io; - -public final class LeftMatcher extends TypeSafeMatcher> { - - private final Matcher lMatcher; - - private LeftMatcher(Matcher lMatcher) { - this.lMatcher = lMatcher; - } - - @Override - protected boolean matchesSafely(Either actual) { - return actual.match(lMatcher::matches, constantly(false)); - } - - @Override - public void describeTo(Description description) { - description.appendText("Left value of "); - lMatcher.describeTo(description); - } - - @Override - protected void describeMismatchSafely(Either item, Description mismatchDescription) { - mismatchDescription.appendText("was "); - item.match(l -> io(() -> { - mismatchDescription.appendText("Left value of "); - lMatcher.describeMismatch(l, mismatchDescription); - }), - r -> io(() -> mismatchDescription.appendValue(item))) - .unsafePerformIO(); - } - - public static LeftMatcher isLeftThat(Matcher lMatcher) { - return new LeftMatcher<>(lMatcher); - } -} diff --git a/src/test/java/testsupport/matchers/RightMatcher.java b/src/test/java/testsupport/matchers/RightMatcher.java deleted file mode 100644 index 1eafc0ab1..000000000 --- a/src/test/java/testsupport/matchers/RightMatcher.java +++ /dev/null @@ -1,44 +0,0 @@ -package testsupport.matchers; - -import com.jnape.palatable.lambda.adt.Either; -import org.hamcrest.Description; -import org.hamcrest.Matcher; -import org.hamcrest.TypeSafeMatcher; - -import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; -import static com.jnape.palatable.lambda.io.IO.io; - -public final class RightMatcher extends TypeSafeMatcher> { - - private final Matcher rMatcher; - - private RightMatcher(Matcher rMatcher) { - this.rMatcher = rMatcher; - } - - @Override - protected boolean matchesSafely(Either actual) { - return actual.match(constantly(false), rMatcher::matches); - } - - @Override - public void describeTo(Description description) { - description.appendText("Right value of "); - rMatcher.describeTo(description); - } - - @Override - protected void describeMismatchSafely(Either item, Description mismatchDescription) { - mismatchDescription.appendText("was "); - item.match(l -> io(() -> mismatchDescription.appendValue(item)), - r -> io(() -> { - mismatchDescription.appendText("Right value of "); - rMatcher.describeMismatch(r, mismatchDescription); - })) - .unsafePerformIO(); - } - - public static RightMatcher isRightThat(Matcher rMatcher) { - return new RightMatcher<>(rMatcher); - } -} From 269cbbcfd3b5594ad18a15b2a12be89f2c9d1d1c Mon Sep 17 00:00:00 2001 From: jnape Date: Tue, 7 Jan 2020 10:27:11 -0600 Subject: [PATCH 22/29] Adding identity function overload --- .../com/jnape/palatable/lambda/functions/builtin/fn1/Id.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Id.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Id.java index c2f8c06ee..c92cf0c81 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Id.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Id.java @@ -23,4 +23,8 @@ public A checkedApply(A a) { public static Id id() { return (Id) INSTANCE; } + + public static A id(A a) { + return Id.id().apply(a); + } } From 75b1fb70fe1720966131beb357d5a4836d8493f7 Mon Sep 17 00:00:00 2001 From: jnape Date: Tue, 7 Jan 2020 10:54:15 -0600 Subject: [PATCH 23/29] Adding MaybeT#or for choosing the first present effect result --- CHANGELOG.md | 3 +++ .../lambda/monad/transformer/builtin/MaybeT.java | 14 +++++++++++++- .../monad/transformer/builtin/MaybeTTest.java | 16 +++++++++++++--- 3 files changed, 29 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1afe4208b..db04728bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,9 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/). ### Added - `MergeHMaps`, a `Monoid` that merges `HMap`s by merging the values via key-specified `Semigroup`s +- `Id#id` overload that accepts an argument and returns it +- `MaybeT#or`, choose the first `MaybeT` that represents an effect around `just` a value +- `StateMatcher, StateTMatcher, WriterTMatcher` ## [5.1.0] - 2019-10-13 ### Changed diff --git a/src/main/java/com/jnape/palatable/lambda/monad/transformer/builtin/MaybeT.java b/src/main/java/com/jnape/palatable/lambda/monad/transformer/builtin/MaybeT.java index da2ce2725..c2e02fd94 100644 --- a/src/main/java/com/jnape/palatable/lambda/monad/transformer/builtin/MaybeT.java +++ b/src/main/java/com/jnape/palatable/lambda/monad/transformer/builtin/MaybeT.java @@ -55,6 +55,18 @@ public MaybeT filter(Fn1 predicate) { return maybeT(mma.fmap(ma -> ma.filter(predicate))); } + /** + * Returns the first {@link MaybeT} that is an effect around {@link Maybe#just(Object) just} a result. + * + * @param other the other {@link MaybeT} + * @return the first present {@link MaybeT} + */ + public MaybeT or(MaybeT other) { + MonadRec, M> mMaybeA = runMaybeT(); + return maybeT(mMaybeA.flatMap(maybeA -> maybeA.match(constantly(other.runMaybeT()), + a -> mMaybeA.pure(just(a))))); + } + /** * {@inheritDoc} */ @@ -145,7 +157,7 @@ public MaybeT discardR(Applicative> appB) { @Override public boolean equals(Object other) { - return other instanceof MaybeT && Objects.equals(mma, ((MaybeT) other).mma); + return other instanceof MaybeT && Objects.equals(mma, ((MaybeT) other).mma); } @Override diff --git a/src/test/java/com/jnape/palatable/lambda/monad/transformer/builtin/MaybeTTest.java b/src/test/java/com/jnape/palatable/lambda/monad/transformer/builtin/MaybeTTest.java index 87e0f9df5..2c578fbc6 100644 --- a/src/test/java/com/jnape/palatable/lambda/monad/transformer/builtin/MaybeTTest.java +++ b/src/test/java/com/jnape/palatable/lambda/monad/transformer/builtin/MaybeTTest.java @@ -27,9 +27,7 @@ import static com.jnape.palatable.lambda.functor.builtin.Identity.pureIdentity; import static com.jnape.palatable.lambda.functor.builtin.Lazy.lazy; import static com.jnape.palatable.lambda.io.IO.io; -import static com.jnape.palatable.lambda.monad.transformer.builtin.MaybeT.liftMaybeT; -import static com.jnape.palatable.lambda.monad.transformer.builtin.MaybeT.maybeT; -import static com.jnape.palatable.lambda.monad.transformer.builtin.MaybeT.pureMaybeT; +import static com.jnape.palatable.lambda.monad.transformer.builtin.MaybeT.*; import static com.jnape.palatable.traitor.framework.Subjects.subjects; import static org.junit.Assert.assertEquals; @@ -79,4 +77,16 @@ public void filter() { assertEquals(maybeT(new Identity<>(just(1))), maybeT.filter(gt(0))); assertEquals(maybeT(new Identity<>(nothing())), maybeT.filter(lt(0))); } + + @Test + public void orSelectsFirstPresentValueInsideEffect() { + assertEquals(maybeT(new Identity<>(just(1))), + maybeT(new Identity<>(just(1))).or(maybeT(new Identity<>(nothing())))); + + assertEquals(maybeT(new Identity<>(just(1))), + maybeT(new Identity<>(nothing())).or(maybeT(new Identity<>(just(1))))); + + assertEquals(maybeT(new Identity<>(just(1))), + maybeT(new Identity<>(just(1))).or(maybeT(new Identity<>(just(2))))); + } } \ No newline at end of file From d81d23563f5bb5c1b86094b33eaf3743d4ae6dee Mon Sep 17 00:00:00 2001 From: jnape Date: Tue, 7 Jan 2020 11:04:49 -0600 Subject: [PATCH 24/29] Adding ReaderT#and, category composition between ReaderTs --- CHANGELOG.md | 1 + .../monad/transformer/builtin/ReaderT.java | 12 ++++++++++++ .../monad/transformer/builtin/ReaderTTest.java | 16 ++++++++++------ 3 files changed, 23 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index db04728bb..0ae3bdcf8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/). - `Id#id` overload that accepts an argument and returns it - `MaybeT#or`, choose the first `MaybeT` that represents an effect around `just` a value - `StateMatcher, StateTMatcher, WriterTMatcher` +- `ReaderT#and`, category composition between `ReaderT` instances: `(a -> m b) -> (b -> m c) -> (a -> m c)` ## [5.1.0] - 2019-10-13 ### Changed diff --git a/src/main/java/com/jnape/palatable/lambda/monad/transformer/builtin/ReaderT.java b/src/main/java/com/jnape/palatable/lambda/monad/transformer/builtin/ReaderT.java index 2af18e16d..443470c6e 100644 --- a/src/main/java/com/jnape/palatable/lambda/monad/transformer/builtin/ReaderT.java +++ b/src/main/java/com/jnape/palatable/lambda/monad/transformer/builtin/ReaderT.java @@ -61,6 +61,18 @@ public , N extends MonadRec, B> ReaderT return readerT(r -> fn.apply(runReaderT(r).coerce())); } + /** + * Left-to-right composition between {@link ReaderT} instances running under the same effect and compatible between + * their inputs and outputs. + * + * @param amb the next {@link ReaderT} to run + * @param the final output type + * @return the composed {@link ReaderT} + */ + public ReaderT and(ReaderT amb) { + return readerT(r -> runReaderT(r).flatMap(amb::runReaderT)); + } + /** * {@inheritDoc} */ diff --git a/src/test/java/com/jnape/palatable/lambda/monad/transformer/builtin/ReaderTTest.java b/src/test/java/com/jnape/palatable/lambda/monad/transformer/builtin/ReaderTTest.java index 3ea0710ff..6972c9fb6 100644 --- a/src/test/java/com/jnape/palatable/lambda/monad/transformer/builtin/ReaderTTest.java +++ b/src/test/java/com/jnape/palatable/lambda/monad/transformer/builtin/ReaderTTest.java @@ -8,12 +8,7 @@ import com.jnape.palatable.traitor.runners.Traits; import org.junit.Test; import org.junit.runner.RunWith; -import testsupport.traits.ApplicativeLaws; -import testsupport.traits.Equivalence; -import testsupport.traits.FunctorLaws; -import testsupport.traits.MonadLaws; -import testsupport.traits.MonadReaderLaws; -import testsupport.traits.MonadRecLaws; +import testsupport.traits.*; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executors; @@ -58,6 +53,15 @@ public void mapReaderT() { .runReaderT("foo")); } + @Test + public void andComposesLeftToRight() { + ReaderT, Float> intToFloat = readerT(x -> new Identity<>(x.floatValue())); + ReaderT, Double> floatToDouble = readerT(f -> new Identity<>(f.doubleValue())); + + assertEquals(new Identity<>(1.), + intToFloat.and(floatToDouble).runReaderT(1)); + } + @Test public void staticPure() { ReaderT, Integer> readerT = From 39f79d2825da3ad4db4508b96d4e92275b597bc5 Mon Sep 17 00:00:00 2001 From: jnape Date: Tue, 28 Jan 2020 17:36:45 -0600 Subject: [PATCH 25/29] IterateT, ListT done right (https://wiki.haskell.org/ListT_done_right) --- CHANGELOG.md | 1 + .../{iteration => }/ImmutableQueue.java | 43 +- .../{iteration => }/ImmutableStack.java | 23 +- .../iteration/ConcatenatingIterable.java | 2 + .../iteration/TrampoliningIterator.java | 1 + .../monad/transformer/builtin/IterateT.java | 435 ++++++++++++++++++ .../transformer/builtin/IterateTTest.java | 230 +++++++++ .../testsupport/matchers/IterableMatcher.java | 16 +- .../testsupport/matchers/IterateTMatcher.java | 48 ++ 9 files changed, 765 insertions(+), 34 deletions(-) rename src/main/java/com/jnape/palatable/lambda/internal/{iteration => }/ImmutableQueue.java (70%) rename src/main/java/com/jnape/palatable/lambda/internal/{iteration => }/ImmutableStack.java (77%) create mode 100644 src/main/java/com/jnape/palatable/lambda/monad/transformer/builtin/IterateT.java create mode 100644 src/test/java/com/jnape/palatable/lambda/monad/transformer/builtin/IterateTTest.java create mode 100644 src/test/java/testsupport/matchers/IterateTMatcher.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 0ae3bdcf8..f53bb7d6b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/). - `MaybeT#or`, choose the first `MaybeT` that represents an effect around `just` a value - `StateMatcher, StateTMatcher, WriterTMatcher` - `ReaderT#and`, category composition between `ReaderT` instances: `(a -> m b) -> (b -> m c) -> (a -> m c)` +- `IterateT`, [`ListT` done right](https://wiki.haskell.org/ListT_done_right) ## [5.1.0] - 2019-10-13 ### Changed diff --git a/src/main/java/com/jnape/palatable/lambda/internal/iteration/ImmutableQueue.java b/src/main/java/com/jnape/palatable/lambda/internal/ImmutableQueue.java similarity index 70% rename from src/main/java/com/jnape/palatable/lambda/internal/iteration/ImmutableQueue.java rename to src/main/java/com/jnape/palatable/lambda/internal/ImmutableQueue.java index 1b683c870..dd605015e 100644 --- a/src/main/java/com/jnape/palatable/lambda/internal/iteration/ImmutableQueue.java +++ b/src/main/java/com/jnape/palatable/lambda/internal/ImmutableQueue.java @@ -1,4 +1,4 @@ -package com.jnape.palatable.lambda.internal.iteration; +package com.jnape.palatable.lambda.internal; import com.jnape.palatable.lambda.adt.Maybe; @@ -8,22 +8,29 @@ import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; import static com.jnape.palatable.lambda.functions.builtin.fn3.FoldLeft.foldLeft; -abstract class ImmutableQueue implements Iterable { +/** + * Internal API. Use at your own peril. + */ +public abstract class ImmutableQueue implements Iterable { - abstract ImmutableQueue pushFront(A a); + public abstract ImmutableQueue pushFront(A a); - abstract ImmutableQueue pushBack(A a); + public abstract ImmutableQueue pushBack(A a); - abstract Maybe head(); + public abstract Maybe head(); - abstract ImmutableQueue tail(); + public abstract ImmutableQueue tail(); - abstract ImmutableQueue concat(ImmutableQueue other); + public abstract ImmutableQueue concat(ImmutableQueue other); - final boolean isEmpty() { + public final boolean isEmpty() { return head().fmap(constantly(false)).orElse(true); } + public static ImmutableQueue singleton(A a) { + return new NonEmpty<>(ImmutableStack.empty().push(a), ImmutableStack.empty()); + } + @Override public Iterator iterator() { return new Iterator() { @@ -52,27 +59,27 @@ private static final class Empty extends ImmutableQueue { private static final Empty INSTANCE = new Empty<>(); @Override - ImmutableQueue pushFront(A a) { + public ImmutableQueue pushFront(A a) { return new NonEmpty<>(ImmutableStack.empty().push(a), ImmutableStack.empty()); } @Override - ImmutableQueue pushBack(A a) { + public ImmutableQueue pushBack(A a) { return pushFront(a); } @Override - ImmutableQueue concat(ImmutableQueue other) { + public ImmutableQueue concat(ImmutableQueue other) { return other; } @Override - Maybe head() { + public Maybe head() { return Maybe.nothing(); } @Override - ImmutableQueue tail() { + public ImmutableQueue tail() { return this; } } @@ -87,27 +94,27 @@ private NonEmpty(ImmutableStack outbound, ImmutableStack inbound) { } @Override - ImmutableQueue pushFront(A a) { + public ImmutableQueue pushFront(A a) { return new NonEmpty<>(outbound.push(a), inbound); } @Override - ImmutableQueue pushBack(A a) { + public ImmutableQueue pushBack(A a) { return new NonEmpty<>(outbound, inbound.push(a)); } @Override - ImmutableQueue concat(ImmutableQueue other) { + public ImmutableQueue concat(ImmutableQueue other) { return new NonEmpty<>(outbound, foldLeft(ImmutableStack::push, inbound, other)); } @Override - Maybe head() { + public Maybe head() { return outbound.head(); } @Override - ImmutableQueue tail() { + public ImmutableQueue tail() { ImmutableStack outTail = outbound.tail(); if (!outTail.isEmpty()) return new NonEmpty<>(outTail, inbound); diff --git a/src/main/java/com/jnape/palatable/lambda/internal/iteration/ImmutableStack.java b/src/main/java/com/jnape/palatable/lambda/internal/ImmutableStack.java similarity index 77% rename from src/main/java/com/jnape/palatable/lambda/internal/iteration/ImmutableStack.java rename to src/main/java/com/jnape/palatable/lambda/internal/ImmutableStack.java index a337ec21b..cc6e7175b 100644 --- a/src/main/java/com/jnape/palatable/lambda/internal/iteration/ImmutableStack.java +++ b/src/main/java/com/jnape/palatable/lambda/internal/ImmutableStack.java @@ -1,4 +1,4 @@ -package com.jnape.palatable.lambda.internal.iteration; +package com.jnape.palatable.lambda.internal; import com.jnape.palatable.lambda.adt.Maybe; @@ -7,17 +7,20 @@ import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; -abstract class ImmutableStack implements Iterable { +/** + * Internal API. Use at your own peril. + */ +public abstract class ImmutableStack implements Iterable { - final ImmutableStack push(A a) { + public final ImmutableStack push(A a) { return new Node<>(a, this); } - abstract Maybe head(); + public abstract Maybe head(); - abstract ImmutableStack tail(); + public abstract ImmutableStack tail(); - final boolean isEmpty() { + public final boolean isEmpty() { return head().fmap(constantly(false)).orElse(true); } @@ -49,12 +52,12 @@ private static final class Empty extends ImmutableStack { private static final Empty INSTANCE = new Empty<>(); @Override - Maybe head() { + public Maybe head() { return Maybe.nothing(); } @Override - ImmutableStack tail() { + public ImmutableStack tail() { return this; } } @@ -69,12 +72,12 @@ public Node(A head, ImmutableStack tail) { } @Override - Maybe head() { + public Maybe head() { return Maybe.just(head); } @Override - ImmutableStack tail() { + public ImmutableStack tail() { return tail; } } diff --git a/src/main/java/com/jnape/palatable/lambda/internal/iteration/ConcatenatingIterable.java b/src/main/java/com/jnape/palatable/lambda/internal/iteration/ConcatenatingIterable.java index 594be70b2..f1ff3a779 100644 --- a/src/main/java/com/jnape/palatable/lambda/internal/iteration/ConcatenatingIterable.java +++ b/src/main/java/com/jnape/palatable/lambda/internal/iteration/ConcatenatingIterable.java @@ -1,5 +1,7 @@ package com.jnape.palatable.lambda.internal.iteration; +import com.jnape.palatable.lambda.internal.ImmutableQueue; + import java.util.Iterator; import static com.jnape.palatable.lambda.functions.builtin.fn1.Flatten.flatten; diff --git a/src/main/java/com/jnape/palatable/lambda/internal/iteration/TrampoliningIterator.java b/src/main/java/com/jnape/palatable/lambda/internal/iteration/TrampoliningIterator.java index fa0350d08..3b2a7b37f 100644 --- a/src/main/java/com/jnape/palatable/lambda/internal/iteration/TrampoliningIterator.java +++ b/src/main/java/com/jnape/palatable/lambda/internal/iteration/TrampoliningIterator.java @@ -3,6 +3,7 @@ import com.jnape.palatable.lambda.functions.Fn0; import com.jnape.palatable.lambda.functions.Fn1; import com.jnape.palatable.lambda.functions.recursion.RecursiveResult; +import com.jnape.palatable.lambda.internal.ImmutableQueue; import java.util.Iterator; import java.util.NoSuchElementException; 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 new file mode 100644 index 000000000..82fe5ee67 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/monad/transformer/builtin/IterateT.java @@ -0,0 +1,435 @@ +package com.jnape.palatable.lambda.monad.transformer.builtin; + +import com.jnape.palatable.lambda.adt.Maybe; +import com.jnape.palatable.lambda.adt.Unit; +import com.jnape.palatable.lambda.adt.choice.Choice2; +import com.jnape.palatable.lambda.adt.hlist.Tuple2; +import com.jnape.palatable.lambda.functions.Fn0; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.Fn2; +import com.jnape.palatable.lambda.functions.recursion.RecursiveResult; +import com.jnape.palatable.lambda.functions.specialized.Pure; +import com.jnape.palatable.lambda.functor.Applicative; +import com.jnape.palatable.lambda.functor.builtin.Lazy; +import com.jnape.palatable.lambda.internal.ImmutableQueue; +import com.jnape.palatable.lambda.io.IO; +import com.jnape.palatable.lambda.monad.Monad; +import com.jnape.palatable.lambda.monad.MonadRec; +import com.jnape.palatable.lambda.monad.transformer.MonadT; + +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.Set; + +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.adt.choice.Choice2.a; +import static com.jnape.palatable.lambda.adt.choice.Choice2.b; +import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Into.into; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Tupler2.tupler; +import static com.jnape.palatable.lambda.functions.builtin.fn3.FoldLeft.foldLeft; +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.io.IO.io; +import static com.jnape.palatable.lambda.monad.Monad.join; +import static com.jnape.palatable.lambda.monad.transformer.builtin.MaybeT.maybeT; +import static java.util.Arrays.asList; + +/** + * A {@link MonadT monad transformer} over a co-inductive, singly-linked spine of values embedded in effects. This is + * analogous to Haskell's ListT (done right). All append + * operations ({@link IterateT#cons(MonadRec) cons}, {@link IterateT#snoc(MonadRec) snoc}, etc.) are O(1) space/time + * complexity. + *

+ * Due to its singly-linked embedded design, {@link IterateT} is a canonical example of purely-functional streaming + * computation. For example, to lazily print all lines from a file descriptor, an initial implementation using + * {@link IterateT} might take the following form: + *


+ * String filePath = "/tmp/a_tale_of_two_cities.txt";
+ * IterateT<IO<?>, String> streamLines = IterateT.unfold(
+ *         reader -> io(() -> maybe(reader.readLine()).fmap(line -> tuple(line, reader))),
+ *         io(() -> Files.newBufferedReader(Paths.get(filePath))));
+ *
+ * // iterative read and print lines without retaining references
+ * IO<Unit> printLines = streamLines.forEach(line -> io(() -> System.out.println(line)));
+ * printLines.unsafePerformIO(); // prints "It was the best of times, it was the worst of times, [...]"
+ * 
+ * + * @param the effect type + * @param the element type + */ +public class IterateT, A> implements + MonadT, IterateT> { + + private final Pure pureM; + private final ImmutableQueue> conses; + private final ImmutableQueue>>, M>>, IterateT>> middles; + private final ImmutableQueue> snocs; + + private IterateT(Pure pureM, + ImmutableQueue> conses, + ImmutableQueue>>, M>>, IterateT>> middles, + ImmutableQueue> snocs) { + this.pureM = pureM; + this.conses = conses; + this.middles = middles; + this.snocs = snocs; + } + + /** + * Recover the full structure of the embedded {@link Monad}. + * + * @param the witnessed target type + * @return the embedded {@link Monad} + */ + public >>, M>> MMTA runIterateT() { + return pureM., MonadRec, M>>apply(this).trampolineM(IterateT::resume).coerce(); + } + + /** + * Add an element inside an effect to the front of this {@link IterateT}. + * + * @param head the element + * @return the cons'ed {@link IterateT} + */ + public final IterateT cons(MonadRec head) { + return new IterateT<>(pureM, conses.pushFront(head), middles, snocs); + } + + /** + * Add an element inside an effect to the back of this {@link IterateT}. + * + * @param last the element + * @return the snoc'ed {@link IterateT} + */ + public final IterateT snoc(MonadRec last) { + return new IterateT<>(pureM, conses, middles, snocs.pushBack(last)); + } + + /** + * Concat this {@link IterateT} in front of the other {@link IterateT}. + * + * @param other the other {@link IterateT} + * @return the concatenated {@link IterateT} + */ + public IterateT concat(IterateT other) { + return new IterateT<>(pureM, + conses, + middles.pushBack(b(new IterateT<>(pureM, + snocs.concat(other.conses), + other.middles, + other.snocs))), + ImmutableQueue.empty()); + } + + /** + * Monolithically fold the spine of this {@link IterateT} by {@link MonadRec#trampolineM(Fn1) trampolining} the + * underlying effects (for iterative folding, use {@link IterateT#trampolineM(Fn1) trampolineM} directly). + * + * @param fn the folding function + * @param acc the starting accumulation effect + * @param the accumulation type + * @param the witnessed target result type + * @return the folded effect result + */ + public > MB fold(Fn2> fn, + MonadRec acc) { + return acc.fmap(tupler(this)) + .trampolineM(into((as, b) -> maybeT(as.runIterateT()) + .flatMap(into((a, aas) -> maybeT(fn.apply(b, a).fmap(tupler(aas)).fmap(Maybe::just)))) + .runMaybeT() + .fmap(maybeRecur -> maybeRecur.match(constantly(terminate(b)), RecursiveResult::recurse)))) + .coerce(); + } + + /** + * Convenience method for {@link IterateT#fold(Fn2, MonadRec) folding} the spine of this {@link IterateT} with + * an action to perform on each element without accumulating any results. + * + * @param fn the action to perform on each element + * @param the witnessed target result type + * @return the folded effect result + */ + public > MU forEach(Fn1> fn) { + return fold((__, a) -> fn.apply(a), runIterateT().pure(UNIT)); + } + + /** + * {@inheritDoc} + */ + @Override + public > IterateT lift(MonadRec nb) { + return singleton(nb); + } + + /** + * {@inheritDoc} + */ + @Override + public IterateT trampolineM( + Fn1, IterateT>> fn) { + return trampolineM(fn, ImmutableQueue.>>empty().pushBack(flatMap(fn))); + } + + /** + * {@inheritDoc} + */ + @Override + public IterateT flatMap(Fn1>> f) { + return suspended(() -> maybeT(runIterateT()) + .flatMap(into((a, as) -> maybeT(f.apply(a) + .>coerce() + .concat(as.flatMap(f)) + .runIterateT()))) + .runMaybeT(), pureM); + } + + /** + * {@inheritDoc} + */ + @Override + public IterateT fmap(Fn1 fn) { + return MonadT.super.fmap(fn).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public IterateT pure(B b) { + return singleton(runIterateT().pure(b)); + } + + /** + * Force the underlying spine of this {@link IterateT} into a {@link Collection} of type C inside the + * context of the monadic effect, using the provided cFn0 to construct the initial instance. + *

+ * Note that this is a fundamentally monolithic operation - meaning that incremental progress is not possible - and + * as such, calling this on an infinite {@link IterateT} will result in either heap exhaustion (e.g. in the case of + * {@link List lists}) or non-termination (e.g. in the case of {@link Set sets}). + * + * @param the {@link Collection} type + * @param the witnessed target type + * @return the {@link List} inside of the effect + */ + public , MAS extends MonadRec> MAS toCollection(Fn0 cFn0) { + MonadRec>>, M> mmta = runIterateT(); + return fold((c, a) -> { + c.add(a); + return mmta.pure(c); + }, mmta.pure(cFn0.apply())); + } + + /** + * {@inheritDoc} + */ + @Override + public IterateT zip(Applicative, IterateT> appFn) { + return suspended(() -> { + MonadRec>>, M> mmta = runIterateT(); + return join(maybeT(mmta).zip( + maybeT(appFn.>>coerce().runIterateT()) + .fmap(into((f, fs) -> into((a, as) -> maybeT( + as.fmap(f) + .cons(mmta.pure(f.apply(a))) + .concat(as.cons(mmta.pure(a)).zip(fs)) + .runIterateT())))))) + .runMaybeT(); + }, pureM); + } + + /** + * {@inheritDoc} + */ + @Override + public Lazy> lazyZip( + Lazy, IterateT>> lazyAppFn) { + return lazyAppFn.fmap(this::zip); + } + + /** + * {@inheritDoc} + */ + @Override + public IterateT discardL(Applicative> appB) { + return MonadT.super.discardL(appB).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public IterateT discardR(Applicative> appB) { + return MonadT.super.discardR(appB).coerce(); + } + + private MonadRec, Maybe>>>, M> resume() { + return conses.head().match( + __ -> middles.head().match( + ___ -> snocs.head().match( + ____ -> pureM.apply(terminate(nothing())), + ma -> ma.fmap(a -> terminate(just(tuple(a, new IterateT<>(pureM, + snocs.tail(), + ImmutableQueue.empty(), + ImmutableQueue.empty())))))), + lazyOrStrict -> lazyOrStrict.match( + lazy -> lazy.apply().fmap(maybeRes -> maybeRes.match( + ___ -> recurse(new IterateT<>(pureM, ImmutableQueue.empty(), middles.tail(), snocs)), + into((a, as) -> recurse(new IterateT<>(pureM, + ImmutableQueue.singleton(pureM.apply(a)), + ImmutableQueue.singleton(b(migrateForward(as))), + ImmutableQueue.empty()))) + )), + strict -> pureM.apply(recurse(migrateForward(strict))))), + ma -> ma.fmap(a -> terminate(just(tuple(a, new IterateT<>(pureM, conses.tail(), middles, snocs)))))); + } + + private IterateT migrateForward(IterateT as) { + if (middles.tail().isEmpty()) { + return new IterateT<>(pureM, conses.concat(as.conses), as.middles, as.snocs.concat(snocs)); + } + + IterateT lasts = new IterateT<>(pureM, as.snocs, middles.tail(), snocs); + return new IterateT<>(pureM, as.conses, as.middles.pushBack(b(lasts)), ImmutableQueue.empty()); + } + + private IterateT trampolineM(Fn1, IterateT>> fn, + ImmutableQueue>> queued) { + return suspended(() -> { + MonadRec>>, M> pureQueue = pureM.apply(queued); + return pureQueue.trampolineM( + q -> q.head().match( + __ -> pureM.apply(terminate(nothing())), + next -> next.runIterateT().flatMap(maybeMore -> maybeMore.match( + __ -> pureM.apply(terminate(nothing())), + t -> t.into((aOrB, rest) -> aOrB.match( + a -> pureM.apply(recurse(q.pushFront(fn.apply(a).coerce()))), + b -> trampolineM(fn, q.tail().pushFront(rest)) + .cons(pureM.apply(b)) + .runIterateT() + .fmap(RecursiveResult::terminate))))))); + }, pureM); + } + + /** + * Static factory method for creating an empty {@link IterateT}. + * + * @param pureM the {@link Pure} method for the effect + * @param the effect type + * @param the element type + * @return the empty {@link IterateT} + */ + public static , A> IterateT empty(Pure pureM) { + return new IterateT<>(pureM, ImmutableQueue.empty(), ImmutableQueue.empty(), ImmutableQueue.empty()); + } + + /** + * Static factory method for creating an {@link IterateT} from a single element. + * + * @param ma the element + * @param the effect type + * @param the element type + * @return the singleton {@link IterateT} + */ + public static , A> IterateT singleton(MonadRec ma) { + return new IterateT<>(Pure.of(ma), + ImmutableQueue.>empty().pushFront(ma), + ImmutableQueue.empty(), + ImmutableQueue.empty()); + } + + /** + * Static factory method for wrapping an uncons of an {@link IterateT} in an {@link IterateT}. + * + * @param unwrapped the uncons + * @param the effect type + * @param the element type + * @return the wrapped {@link IterateT} + */ + public static , A> IterateT iterateT( + MonadRec>>, M> unwrapped) { + return new IterateT<>( + Pure.of(unwrapped), + ImmutableQueue.empty(), + ImmutableQueue.>>, M>>, IterateT>>empty() + .pushFront(a(() -> unwrapped)), + ImmutableQueue.empty()); + } + + /** + * Static factory method for creating an {@link IterateT} from a spine represented by one or more elements. + * + * @param ma the head element + * @param mas the tail elements + * @param the effect type + * @param the element type + * @return the {@link IterateT} + */ + @SafeVarargs + public static , A> IterateT of( + MonadRec ma, MonadRec... mas) { + @SuppressWarnings("varargs") + List> as = asList(mas); + return foldLeft(IterateT::snoc, + empty(Pure.of(ma)), + com.jnape.palatable.lambda.functions.builtin.fn2.Cons.cons(ma, as)); + } + + /** + * Lazily unfold an {@link IterateT} from an unfolding function fn and a starting seed value + * mb by successively applying fn to the latest seed value, producing {@link Maybe maybe} + * a value to yield out and the next seed value for the subsequent computation. + * + * @param fn the unfolding function + * @param mb the starting seed value + * @param the effect type + * @param the element type + * @param the seed type + * @return the lazily unfolding {@link IterateT} + */ + public static , A, B> IterateT unfold( + Fn1>, M>> fn, MonadRec mb) { + return suspended(() -> maybeT(mb.flatMap(fn)) + .fmap(ab -> ab.fmap(b -> unfold(fn, mb.pure(b)))) + .runMaybeT(), Pure.of(mb)); + } + + /** + * Create an {@link IterateT} from a suspended computation that yields the spine of the {@link IterateT} inside the + * effect. + * + * @param thunk the suspended computation + * @param pureM the {@link Pure} method for the effect + * @param the effect type + * @param the element type + * @return the {@link IterateT} + */ + public static , A> IterateT suspended( + Fn0>>, M>> thunk, Pure pureM) { + return new IterateT<>(pureM, + ImmutableQueue.empty(), + ImmutableQueue + .>>, M>>, IterateT>>empty() + .pushFront(a(thunk)), + ImmutableQueue.empty()); + } + + /** + * Lazily unfold an {@link IterateT} from an {@link Iterator} inside {@link IO}. + * + * @param as the {@link Iterator} + * @param the element type + * @return the {@link IterateT} + */ + public static IterateT, A> fromIterator(Iterator as) { + return unfold(it -> io(() -> { + if (as.hasNext()) + return just(tuple(as.next(), as)); + return nothing(); + }), io(() -> as)); + } +} 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 new file mode 100644 index 000000000..2cfc24ae0 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/monad/transformer/builtin/IterateTTest.java @@ -0,0 +1,230 @@ +package com.jnape.palatable.lambda.monad.transformer.builtin; + +import com.jnape.palatable.lambda.adt.Maybe; +import com.jnape.palatable.lambda.adt.Unit; +import com.jnape.palatable.lambda.adt.hlist.Tuple2; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functor.builtin.Identity; +import com.jnape.palatable.lambda.functor.builtin.Lazy; +import com.jnape.palatable.lambda.functor.builtin.Writer; +import com.jnape.palatable.lambda.io.IO; +import com.jnape.palatable.traitor.annotations.TestTraits; +import com.jnape.palatable.traitor.framework.Subjects; +import com.jnape.palatable.traitor.runners.Traits; +import org.junit.Test; +import org.junit.runner.RunWith; +import testsupport.traits.ApplicativeLaws; +import testsupport.traits.Equivalence; +import testsupport.traits.FunctorLaws; +import testsupport.traits.MonadLaws; +import testsupport.traits.MonadRecLaws; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.CountDownLatch; + +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.adt.hlist.HList.tuple; +import static com.jnape.palatable.lambda.functions.builtin.fn2.LTE.lte; +import static com.jnape.palatable.lambda.functions.builtin.fn3.Times.times; +import static com.jnape.palatable.lambda.functor.builtin.Identity.pureIdentity; +import static com.jnape.palatable.lambda.functor.builtin.Lazy.lazy; +import static com.jnape.palatable.lambda.functor.builtin.Writer.listen; +import static com.jnape.palatable.lambda.functor.builtin.Writer.pureWriter; +import static com.jnape.palatable.lambda.functor.builtin.Writer.tell; +import static com.jnape.palatable.lambda.functor.builtin.Writer.writer; +import static com.jnape.palatable.lambda.io.IO.io; +import static com.jnape.palatable.lambda.monad.transformer.builtin.IterateT.empty; +import static com.jnape.palatable.lambda.monad.transformer.builtin.IterateT.singleton; +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.traitor.framework.Subjects.subjects; +import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; +import static org.hamcrest.core.IsEqual.equalTo; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; +import static testsupport.Constants.STACK_EXPLODING_NUMBER; +import static testsupport.matchers.IOMatcher.yieldsValue; +import static testsupport.matchers.IterateTMatcher.isEmpty; +import static testsupport.matchers.IterateTMatcher.iterates; +import static testsupport.matchers.IterateTMatcher.iteratesAll; +import static testsupport.traits.Equivalence.equivalence; + +@RunWith(Traits.class) +public class IterateTTest { + + @TestTraits({FunctorLaws.class, ApplicativeLaws.class, MonadLaws.class, MonadRecLaws.class}) + public Subjects, Integer>>> testSubjects() { + Fn1, ?>, Object> toCollection = iterateT -> iterateT + ., Identity>>fold( + (as, a) -> { + as.add(a); + return new Identity<>(as); + }, + new Identity<>(new ArrayList<>())) + .runIdentity(); + return subjects(equivalence(empty(pureIdentity()), toCollection), + equivalence(singleton(new Identity<>(0)), toCollection), + equivalence(IterateT., Integer>empty(pureIdentity()).cons(new Identity<>(1)), + toCollection), + equivalence(IterateT., Integer>empty(pureIdentity()).snoc(new Identity<>(1)), + toCollection), + equivalence(singleton(new Identity<>(0)).concat(singleton(new Identity<>(1))), + toCollection), + equivalence(unfold(x -> new Identity<>(x <= 100 ? just(tuple(x, x + 1)) : nothing()), + new Identity<>(0)), + toCollection) + ); + } + + @Test + public void emptyHasNoElements() { + assertEquals(new Identity<>(nothing()), + IterateT., Integer>empty(pureIdentity()) + ., Integer>>>>>runIterateT()); + } + + @Test + public void singletonHasOneElement() { + assertThat(singleton(new Identity<>(1)), iterates(1)); + } + + @Test + public void unfolding() { + assertThat(unfold(x -> new Identity<>(just(x).filter(lte(3)).fmap(y -> tuple(y, y + 1))), new Identity<>(1)), + iteratesAll(asList(1, 2, 3))); + } + + @Test + public void consAddsElementToFront() { + assertThat(singleton(new Identity<>(1)).cons(new Identity<>(0)), iterates(0, 1)); + } + + @Test + public void snocAddsElementToBack() { + assertThat(singleton(new Identity<>(1)).snoc(new Identity<>(2)), iterates(1, 2)); + } + + @Test + public void concatsTwoIterateTs() { + IterateT, Integer> front = singleton(new Identity<>(0)).snoc(new Identity<>(1)); + IterateT, Integer> back = singleton(new Identity<>(2)).snoc(new Identity<>(3)); + + assertThat(front.concat(back), iterates(0, 1, 2, 3)); + assertThat(IterateT., Integer>empty(pureIdentity()).concat(back), iterates(2, 3)); + assertThat(front.concat(empty(pureIdentity())), iterates(0, 1)); + assertThat(IterateT., Integer>empty(pureIdentity()).concat(empty(pureIdentity())), isEmpty()); + assertThat(singleton(new Identity<>(1)) + .concat(unfold(x -> new Identity<>(nothing()), new Identity<>(0))) + .concat(singleton(new Identity<>(2))), + iterates(1, 2)); + } + + @Test + public void ofIteratesElements() { + assertEquals(tuple(6, asList(1, 2, 3)), + IterateT., ?>, Integer>of(listen(1), listen(2), listen(3)) + ., Integer>>fold( + (x, y) -> writer(tuple(x + y, singletonList(y))), listen(0)) + .runWriter(addAll(ArrayList::new))); + } + + @Test + public void fromIterator() { + IterateT, Integer> it = IterateT.fromIterator(asList(1, 2, 3).iterator()); + assertThat(it., IO>>toCollection(ArrayList::new), + yieldsValue(equalTo(asList(1, 2, 3)))); + assertThat(it., IO>>toCollection(ArrayList::new), + yieldsValue(equalTo(emptyList()))); + } + + @Test + public void fold() { + assertEquals(tuple(6, asList(1, 2, 3)), + IterateT., ?>, Integer>of(listen(1), listen(2), listen(3)) + ., Integer>>fold( + (x, y) -> writer(tuple(x + y, singletonList(y))), listen(0)) + .runWriter(addAll(ArrayList::new))); + } + + @Test + public void zipUsesCartesianProduct() { + assertThat(IterateT.of(new Identity<>(1), new Identity<>(2), new Identity<>(3)) + .zip(IterateT.of(new Identity<>(x -> x + 1), new Identity<>(x -> x - 1))), + iterates(2, 3, 4, 0, 1, 2)); + } + + @Test(timeout = 1000) + public void zipsInParallel() { + CountDownLatch latch = new CountDownLatch(2); + singleton(io(() -> { + latch.countDown(); + latch.await(); + return 0; + })).zip(singleton(io(() -> { + latch.countDown(); + latch.await(); + return x -> x + 1; + })))., Integer>>>>>runIterateT() + .unsafePerformAsyncIO() + .join(); + } + + @Test + public void toCollection() { + assertEquals(asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10), + unfold(x -> new Identity<>(x <= 10 ? just(tuple(x, x + 1)) : nothing()), new Identity<>(1)) + ., Identity>>toCollection(ArrayList::new) + .runIdentity()); + } + + @Test + public void forEach() { + assertEquals(tuple(UNIT, asList(1, 2, 3)), + IterateT., ?>, Integer>empty(pureWriter()) + .cons(listen(3)) + .cons(listen(2)) + .cons(listen(1)) + ., Unit>>forEach(x -> tell(singletonList(x))) + .runWriter(addAll(ArrayList::new))); + } + + @Test + public void foldLargeNumberOfElements() { + IterateT, Integer> largeIterateT = times(STACK_EXPLODING_NUMBER, + it -> it.cons(new Identity<>(1)), + empty(pureIdentity())); + assertEquals(new Identity<>(STACK_EXPLODING_NUMBER), + largeIterateT.fold((x, y) -> new Identity<>(x + y), new Identity<>(0))); + } + + @Test + public void stackSafetyForStrictMonads() { + IterateT, Integer> hugeStrictIterateT = + unfold(x -> new Identity<>(x <= STACK_EXPLODING_NUMBER ? just(tuple(x, x + 1)) : nothing()), + new Identity<>(1)); + Identity fold = hugeStrictIterateT.fold((x, y) -> new Identity<>(x + y), new Identity<>(0)); + assertEquals(new Identity<>(1250025000), fold); + } + + @Test + public void stackSafetyForNonStrictMonads() { + IterateT, Integer> hugeNonStrictIterateT = + unfold(x -> lazy(() -> x <= 50_000 ? just(tuple(x, x + 1)) : nothing()), lazy(0)); + Lazy fold = hugeNonStrictIterateT.fold((x, y) -> lazy(() -> x + y), lazy(0)); + assertEquals((Integer) 1250025000, fold.value()); + } + + @Test + public void concatIsStackSafe() { + IterateT, Integer> bigIterateT = times(10_000, xs -> xs.concat(singleton(new Identity<>(1))), + singleton(new Identity<>(0))); + assertEquals(new Identity<>(10_000), + bigIterateT.fold((x, y) -> new Identity<>(x + y), new Identity<>(0))); + } +} \ No newline at end of file diff --git a/src/test/java/testsupport/matchers/IterableMatcher.java b/src/test/java/testsupport/matchers/IterableMatcher.java index f5f196aba..ac1faee71 100644 --- a/src/test/java/testsupport/matchers/IterableMatcher.java +++ b/src/test/java/testsupport/matchers/IterableMatcher.java @@ -19,7 +19,7 @@ private IterableMatcher(Iterable expected) { @Override public boolean matches(Object actual) { - return actual instanceof Iterable && iterablesIterateSameElementsInOrder(expected, (Iterable) actual); + return actual instanceof Iterable && iterablesIterateSameElementsInOrder(expected, (Iterable) actual); } @Override @@ -32,18 +32,18 @@ public void describeMismatch(Object item, Description description) { if (item instanceof Iterable) { if (description.toString().endsWith("but: ")) description.appendText("was "); - description.appendText("<").appendText(stringify((Iterable) item)).appendText(">"); + description.appendText("<").appendText(stringify((Iterable) item)).appendText(">"); } else super.describeMismatch(item, description); } private boolean iterablesIterateSameElementsInOrder(Iterable expected, Iterable actual) { - Iterator actualIterator = actual.iterator(); + Iterator actualIterator = actual.iterator(); Iterator expectedIterator = expected.iterator(); while (expectedIterator.hasNext() && actualIterator.hasNext()) { Object nextExpected = expectedIterator.next(); - Object nextActual = actualIterator.next(); + Object nextActual = actualIterator.next(); if (nextExpected instanceof Iterable && nextActual instanceof Iterable) { if (!iterablesIterateSameElementsInOrder((Iterable) nextExpected, (Iterable) nextActual)) @@ -57,11 +57,11 @@ private boolean iterablesIterateSameElementsInOrder(Iterable expected, Iterab private String stringify(Iterable iterable) { StringBuilder stringBuilder = new StringBuilder().append("["); - Iterator iterator = iterable.iterator(); + Iterator iterator = iterable.iterator(); while (iterator.hasNext()) { Object next = iterator.next(); if (next instanceof Iterable) - stringBuilder.append(stringify((Iterable) next)); + stringBuilder.append(stringify((Iterable) next)); else stringBuilder.append(next); if (iterator.hasNext()) @@ -76,6 +76,10 @@ public static IterableMatcher iterates(E... es) { return new IterableMatcher<>(asList(es)); } + public static IterableMatcher iteratesAll(Iterable es) { + return new IterableMatcher<>(es); + } + public static IterableMatcher isEmpty() { return new IterableMatcher<>(new ArrayList<>()); } diff --git a/src/test/java/testsupport/matchers/IterateTMatcher.java b/src/test/java/testsupport/matchers/IterateTMatcher.java new file mode 100644 index 000000000..894fa6edd --- /dev/null +++ b/src/test/java/testsupport/matchers/IterateTMatcher.java @@ -0,0 +1,48 @@ +package testsupport.matchers; + +import com.jnape.palatable.lambda.functor.builtin.Identity; +import com.jnape.palatable.lambda.monad.transformer.builtin.IterateT; +import org.hamcrest.Description; +import org.hamcrest.TypeSafeMatcher; + +import java.util.LinkedList; + +import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; + +public final class IterateTMatcher extends TypeSafeMatcher, A>> { + private final Iterable as; + + private IterateTMatcher(Iterable as) { + this.as = as; + } + + @Override + protected boolean matchesSafely(IterateT, A> iterateT) { + Identity> fold = iterateT.fold((as, a) -> { + as.add(a); + return new Identity<>(as); + }, new Identity<>(new LinkedList<>())); + LinkedList as = fold.runIdentity(); + return IterableMatcher.iteratesAll(this.as).matches(as); + } + + @Override + public void describeTo(Description description) { + description.appendText("an IterateT iterating " + as.toString() + " inside Identity"); + } + + public static IterateTMatcher iteratesAll(Iterable as) { + return new IterateTMatcher<>(as); + } + + public static IterateTMatcher isEmpty() { + return new IterateTMatcher<>(emptyList()); + } + + @SafeVarargs + @SuppressWarnings("varargs") + public static IterateTMatcher iterates(A... as) { + return iteratesAll(asList(as)); + } +} From ac2a7cd8d51e0e1398cd2de676d7a48774bd02e8 Mon Sep 17 00:00:00 2001 From: jnape Date: Sat, 8 Feb 2020 16:49:19 -0600 Subject: [PATCH 26/29] Updating CHANGELOG --- CHANGELOG.md | 4 +++ .../ordering/ComparisonRelation.java | 31 +++++++++++++------ 2 files changed, 26 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f53bb7d6b..8561f1f8f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,9 +13,13 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/). - `MergeHMaps`, a `Monoid` that merges `HMap`s by merging the values via key-specified `Semigroup`s - `Id#id` overload that accepts an argument and returns it - `MaybeT#or`, choose the first `MaybeT` that represents an effect around `just` a value +- `MaybeT#filter`, filter a `Maybe` inside an effect - `StateMatcher, StateTMatcher, WriterTMatcher` - `ReaderT#and`, category composition between `ReaderT` instances: `(a -> m b) -> (b -> m c) -> (a -> m c)` - `IterateT`, [`ListT` done right](https://wiki.haskell.org/ListT_done_right) +- `Comparison`, a type-safe sum of `LT`, `EQ`, and `GT` orderings +- `Compare`, a function taking a `Comparator` and returning a `Comparison` +- `Min/Max/...With` variants for inequality testing with a `Comparator` ## [5.1.0] - 2019-10-13 ### Changed diff --git a/src/main/java/com/jnape/palatable/lambda/functions/ordering/ComparisonRelation.java b/src/main/java/com/jnape/palatable/lambda/functions/ordering/ComparisonRelation.java index 3878f385a..0c4bb93c3 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/ordering/ComparisonRelation.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/ordering/ComparisonRelation.java @@ -13,8 +13,14 @@ * @see Compare */ public abstract class ComparisonRelation - implements CoProduct3 { - private ComparisonRelation() { } + implements CoProduct3< + ComparisonRelation.LessThan, + ComparisonRelation.Equal, + ComparisonRelation.GreaterThan, + ComparisonRelation> { + + private ComparisonRelation() { + } /** * Return a comparison relation from the result of a {@link Comparator} or {@link Comparable} result @@ -38,7 +44,7 @@ public static Equal equal() { return Equal.INSTANCE; } - public final static class LessThan extends ComparisonRelation { + public static final class LessThan extends ComparisonRelation { private static final LessThan INSTANCE = new LessThan(); private LessThan() { @@ -50,12 +56,14 @@ public String toString() { } @Override - public R match(Fn1 aFn, Fn1 bFn, Fn1 cFn) { + public R match(Fn1 aFn, + Fn1 bFn, + Fn1 cFn) { return aFn.apply(this); } } - public final static class Equal extends ComparisonRelation { + public static final class Equal extends ComparisonRelation { private static final Equal INSTANCE = new Equal(); private Equal() { @@ -66,15 +74,18 @@ public String toString() { } @Override - public R match(Fn1 aFn, Fn1 bFn, Fn1 cFn) { + public R match(Fn1 aFn, + Fn1 bFn, + Fn1 cFn) { return bFn.apply(this); } } - public final static class GreaterThan extends ComparisonRelation { + public static final class GreaterThan extends ComparisonRelation { private static final GreaterThan INSTANCE = new GreaterThan(); - private GreaterThan() { } + private GreaterThan() { + } @Override public String toString() { @@ -82,7 +93,9 @@ public String toString() { } @Override - public R match(Fn1 aFn, Fn1 bFn, Fn1 cFn) { + public R match(Fn1 aFn, + Fn1 bFn, + Fn1 cFn) { return cFn.apply(this); } } From 90b5c8a7bb68dc453720149654cdb6ca6a419c66 Mon Sep 17 00:00:00 2001 From: jnape Date: Sat, 8 Feb 2020 17:30:13 -0600 Subject: [PATCH 27/29] Adding IterateT#foldCut for folding with early termination --- .../monad/transformer/builtin/IterateT.java | 24 ++++++++++++++++--- .../transformer/builtin/IterateTTest.java | 15 ++++++++++++ 2 files changed, 36 insertions(+), 3 deletions(-) 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 82fe5ee67..1b85133e1 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 @@ -28,7 +28,6 @@ import static com.jnape.palatable.lambda.adt.choice.Choice2.a; import static com.jnape.palatable.lambda.adt.choice.Choice2.b; import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; -import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; import static com.jnape.palatable.lambda.functions.builtin.fn2.Into.into; import static com.jnape.palatable.lambda.functions.builtin.fn2.Tupler2.tupler; import static com.jnape.palatable.lambda.functions.builtin.fn3.FoldLeft.foldLeft; @@ -138,11 +137,30 @@ public IterateT concat(IterateT other) { */ public > MB fold(Fn2> fn, MonadRec acc) { + return foldCut((b, a) -> fn.apply(b, a).fmap(RecursiveResult::recurse), acc); + } + + /** + * Monolithically fold the spine of this {@link IterateT} (with the possibility of early termination) by + * {@link MonadRec#trampolineM(Fn1) trampolining} the underlying effects (for iterative folding, use + * {@link IterateT#trampolineM(Fn1) trampolineM} directly). + * + * @param fn the folding function + * @param acc the starting accumulation effect + * @param the accumulation type + * @param the witnessed target result type + * @return the folded effect result + */ + public > MB foldCut( + Fn2, M>> fn, + MonadRec acc) { return acc.fmap(tupler(this)) .trampolineM(into((as, b) -> maybeT(as.runIterateT()) - .flatMap(into((a, aas) -> maybeT(fn.apply(b, a).fmap(tupler(aas)).fmap(Maybe::just)))) + .flatMap(into((a, aas) -> maybeT(fn.apply(b, a).fmap(Maybe::just)).fmap(tupler(aas)))) .runMaybeT() - .fmap(maybeRecur -> maybeRecur.match(constantly(terminate(b)), RecursiveResult::recurse)))) + .fmap(maybeR -> maybeR.match( + __ -> terminate(b), + into((rest, rr) -> rr.biMapL(tupler(rest))))))) .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 2cfc24ae0..00768b0c3 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 @@ -30,6 +30,8 @@ import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; import static com.jnape.palatable.lambda.functions.builtin.fn2.LTE.lte; import static com.jnape.palatable.lambda.functions.builtin.fn3.Times.times; +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.functor.builtin.Identity.pureIdentity; import static com.jnape.palatable.lambda.functor.builtin.Lazy.lazy; import static com.jnape.palatable.lambda.functor.builtin.Writer.listen; @@ -41,6 +43,7 @@ import static com.jnape.palatable.lambda.monad.transformer.builtin.IterateT.singleton; 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; import static com.jnape.palatable.traitor.framework.Subjects.subjects; import static java.util.Arrays.asList; import static java.util.Collections.emptyList; @@ -152,6 +155,18 @@ public void fold() { .runWriter(addAll(ArrayList::new))); } + @Test + public void foldCut() { + assertEquals(tuple(3, "012"), + IterateT.of(writer(tuple(1, "1")), + writer(tuple(2, "2")), + writer(tuple(3, "3"))) + .>foldCut( + (x, y) -> listen(y == 2 ? terminate(x + y) : recurse(x + y)), + writer(tuple(0, "0"))) + .runWriter(join())); + } + @Test public void zipUsesCartesianProduct() { assertThat(IterateT.of(new Identity<>(1), new Identity<>(2), new Identity<>(3)) From 2dbc6f5cf923a9418aca21a9033a02eaf65f7c2d Mon Sep 17 00:00:00 2001 From: jnape Date: Sat, 8 Feb 2020 17:46:19 -0600 Subject: [PATCH 28/29] Fixing Javadocs --- .../lambda/functions/builtin/fn3/CmpEqWith.java | 16 ++++++++-------- .../lambda/functions/builtin/fn3/Compare.java | 10 +++++----- .../lambda/functions/builtin/fn3/GTEWith.java | 12 ++++++------ .../lambda/functions/builtin/fn3/LTEWith.java | 12 ++++++------ .../palatable/lambda/functor/builtin/Writer.java | 3 +++ .../monad/transformer/builtin/IterateT.java | 1 + 6 files changed, 29 insertions(+), 25 deletions(-) diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/CmpEqWith.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/CmpEqWith.java index 0a0c1f1ce..ff95cd2fc 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/CmpEqWith.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/CmpEqWith.java @@ -11,9 +11,9 @@ import static com.jnape.palatable.lambda.functions.specialized.Predicate.predicate; /** - * Given a {@link Comparator} from some type A and two values of type A, return true - * if the first value is strictly equal to the second value (according to {@link Comparator#compare(A, A)} - * otherwise, return false. + * Given a {@link Comparator} from some type A and two values of type A, return + * true if the first value is strictly equal to the second value (according to + * {@link Comparator#compare(Object, Object)} otherwise, return false. * * @param the value type * @see CmpEqBy @@ -38,6 +38,11 @@ public Predicate apply(Comparator compareFn, A x) { return predicate(Fn3.super.apply(compareFn, x)); } + @Override + public Boolean checkedApply(Comparator comparator, A a, A a2) { + return compare(comparator, a, a2).equals(equal()); + } + @SuppressWarnings("unchecked") public static CmpEqWith cmpEqWith() { return (CmpEqWith) INSTANCE; @@ -54,9 +59,4 @@ public static Predicate cmpEqWith(Comparator comparator, A x) { public static Boolean cmpEqWith(Comparator comparator, A x, A y) { return cmpEqWith(comparator, x).apply(y); } - - @Override - public Boolean checkedApply(Comparator comparator, A a, A a2) { - return compare(comparator, a, a2).equals(equal()); - } } diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/Compare.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/Compare.java index 4f0bea533..b388feca9 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/Compare.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/Compare.java @@ -9,9 +9,9 @@ /** * Given a {@link Comparator} from some type A and two values of type A, return a - * {@link ComparisonRelation} of the first value with reference to the second value (according to {@link Comparator#compare(A, A)}. - * The order of parameters is flipped with respect to {@link Comparator#compare(A, A)} for more idiomatic partial application. - * + * {@link ComparisonRelation} of the first value with reference to the second value (according to + * {@link Comparator#compare(Object, Object)}. The order of parameters is flipped with respect to + * {@link Comparator#compare(Object, Object)} for more idiomatic partial application. *

* Example: *

@@ -21,7 +21,6 @@
  *  Compare.compare(naturalOrder(), 1, 1); // ComparisonRelation.Equal
  * }
  * 
- *

* * @param
the value type * @see Comparator @@ -30,7 +29,8 @@ public final class Compare implements Fn3, A, A, ComparisonRelation> { private static final Compare INSTANCE = new Compare<>(); - private Compare() { } + private Compare() { + } @Override public ComparisonRelation checkedApply(Comparator aComparator, A a, A a2) throws Throwable { diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/GTEWith.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/GTEWith.java index 7a3c8cc6d..29a5dfe82 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/GTEWith.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/GTEWith.java @@ -13,7 +13,7 @@ /** * Given a {@link Comparator} from some type A and two values of type A, * return true if the second value is greater than or equal to the first value in - * terms of their mapped B results according to {@link Comparator#compare(A, A)}; + * terms of their mapped B results according to {@link Comparator#compare(Object, Object)}; * otherwise, return false. * * @param the value type @@ -38,6 +38,11 @@ public Predicate apply(Comparator compareFn, A x) { return predicate(Fn3.super.apply(compareFn, x)); } + @Override + public Boolean checkedApply(Comparator comparator, A a, A a2) { + return !ltWith(comparator, a, a2); + } + @SuppressWarnings("unchecked") public static GTEWith gteWith() { return (GTEWith) INSTANCE; @@ -54,9 +59,4 @@ public static Predicate gteWith(Comparator comparator, A y) { public static Boolean gteWith(Comparator comparator, A y, A x) { return gteWith(comparator, y).apply(x); } - - @Override - public Boolean checkedApply(Comparator comparator, A a, A a2) { - return !ltWith(comparator, a, a2); - } } diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/LTEWith.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/LTEWith.java index 06e621d3f..403f5c15f 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/LTEWith.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/LTEWith.java @@ -13,7 +13,7 @@ /** * Given a {@link Comparator} from some type A and two values of type A, * return true if the second value is less than or equal to the first value in - * terms of their mapped B results according to {@link Comparator#compare(A, A)}; + * terms of their mapped B results according to {@link Comparator#compare(Object, Object)}; * otherwise, return false. * * @param the value type @@ -38,6 +38,11 @@ public Predicate apply(Comparator compareFn, A x) { return predicate(Fn3.super.apply(compareFn, x)); } + @Override + public Boolean checkedApply(Comparator comparator, A a, A a2) { + return !gtWith(comparator, a, a2); + } + @SuppressWarnings("unchecked") public static LTEWith lteWith() { return (LTEWith) INSTANCE; @@ -54,9 +59,4 @@ public static Predicate lteWith(Comparator comparator, A y) { public static Boolean lteWith(Comparator comparator, A y, A x) { return lteWith(comparator, y).apply(x); } - - @Override - public Boolean checkedApply(Comparator comparator, A a, A a2) { - return !gtWith(comparator, a, a2); - } } diff --git a/src/main/java/com/jnape/palatable/lambda/functor/builtin/Writer.java b/src/main/java/com/jnape/palatable/lambda/functor/builtin/Writer.java index bb00cb402..c4c6d752d 100644 --- a/src/main/java/com/jnape/palatable/lambda/functor/builtin/Writer.java +++ b/src/main/java/com/jnape/palatable/lambda/functor/builtin/Writer.java @@ -136,6 +136,7 @@ public Writer discardR(Applicative> appB) { /** * Construct a {@link Writer} from an accumulation. * + * @param w the accumulation * @param the accumulation type * @return the {@link Writer} */ @@ -146,6 +147,7 @@ public static Writer tell(W w) { /** * Construct a {@link Writer} from a value. * + * @param a the output value * @param the accumulation type * @param the value type * @return the {@link Writer} @@ -157,6 +159,7 @@ public static Writer listen(A a) { /** * Construct a {@link Writer} from an accumulation and a value. * + * @param aw the output value and accumulation * @param the accumulation type * @param the value type * @return the {@link WriterT} 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 1b85133e1..606e7d894 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 @@ -230,6 +230,7 @@ public IterateT pure(B b) { * as such, calling this on an infinite {@link IterateT} will result in either heap exhaustion (e.g. in the case of * {@link List lists}) or non-termination (e.g. in the case of {@link Set sets}). * + * @param cFn0 the {@link Collection} construction function * @param the {@link Collection} type * @param the witnessed target type * @return the {@link List} inside of the effect From 47eba89d04479d052608046054466f9d481de4a9 Mon Sep 17 00:00:00 2001 From: jnape Date: Sat, 8 Feb 2020 17:58:06 -0600 Subject: [PATCH 29/29] [maven-release-plugin] prepare release lambda-5.2.0 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 5f84c2dd2..f89d14be0 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ lambda - 5.1.1-SNAPSHOT + 5.2.0 jar Lambda