diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml index 2f7615ccb..296a02977 100644 --- a/.github/workflows/maven.yml +++ b/.github/workflows/maven.yml @@ -1,6 +1,8 @@ name: Java CI -on: [push] +on: + push: + pull_request: jobs: build-java-1_8: @@ -28,3 +30,16 @@ jobs: java-version: 11 - name: Build with Maven run: mvn clean verify + + build-java-14: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v1 + - name: Set up JDK 14 + uses: actions/setup-java@v1 + with: + java-version: 14 + - name: Build with Maven + run: mvn clean verify diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 39074daea..000000000 --- a/.travis.yml +++ /dev/null @@ -1,5 +0,0 @@ -dist: trusty -language: java -jdk: - - oraclejdk8 - - openjdk11 diff --git a/CHANGELOG.md b/CHANGELOG.md index 8561f1f8f..3aecf9b88 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,53 @@ # Change Log + All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/). ## [Unreleased] +### Added +- `ReaderT#ask`, a static factory method for returning an identity `ReaderT` + +### Fixed +- nested `DropWhile`s no longer incorrectly deforest using disjunction + +## [5.4.0] - 2021-09-17 + +### Changed +- `Absent` folds short-circuit on the first `nothing()` +- `EitherMatcher#isLeftThat/isRightThat` support contravariant bounds on their delegates + +### Added +- `IterateT#runStep`, a method used to run a single step of an IterateT without the contractual guarantee of emitting a + value or reaching the end +- `These#fromMaybes :: Maybe a -> Maybe b -> Maybe (These a b)` +- `EitherMatcher#isLeftOf/isRightOf` for asserting equality + +### Fixed +- `WriterT` now keeps an immediate reference to the embedded monad's `pure` + +## [5.3.0] - 2020-12-07 + +### Changed +- `IterateT#unfold` now only computes a single `Pure` for the given input +- `ReaderT#fmap` and `StateT#fmap` avoid unnecessary calls to `pure` +- `MaybeT` implements `MonadError` + +### Added +- `$`, function application represented as a higher-order `Fn2` +- `Fn1#withSelf`, a static method for constructing a self-referencing `Fn1` +- `HNil/SingletonHList/TupleX#snoc`, a method to add a new last element (append to a tuple) +- `Tuple2-8#init`, for populating a `TupleN` with all but the last element + +### Fixed +- `IterateT#trampolineM` now yields and stages all recursive result values, rather + than prematurely terminating on the first termination result +- `IterateT#flatMap` is now stack-safe regardless of how many consecutive empty `IterateT`s + are returned and regardless of whether the monad is strict or lazy or internally trampolined + +## [5.2.0] - 2020-02-12 + ### Changed - `HList#cons` static factory method auto-promotes to specialized `HList` if there is one - `EitherT` gains a `MonadError` instance @@ -557,7 +600,10 @@ 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.1.0...HEAD +[Unreleased]: https://github.com/palatable/lambda/compare/lambda-5.4.0...HEAD +[5.4.0]: https://github.com/palatable/lambda/compare/lambda-5.3.0...lambda-5.4.0 +[5.3.0]: https://github.com/palatable/lambda/compare/lambda-5.2.0...lambda-5.3.0 +[5.2.0]: https://github.com/palatable/lambda/compare/lambda-5.1.0...lambda-5.2.0 [5.1.0]: https://github.com/palatable/lambda/compare/lambda-5.0.0...lambda-5.1.0 [5.0.0]: https://github.com/palatable/lambda/compare/lambda-4.0.0...lambda-5.0.0 [4.0.0]: https://github.com/palatable/lambda/compare/lambda-3.3.0...lambda-4.0.0 diff --git a/README.md b/README.md index edaf4dde9..7d84c6295 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,8 @@ λ ====== -[![Build Status](https://travis-ci.com/palatable/lambda.svg?branch=master)](https://travis-ci.com/palatable/lambda) [![Actions Status](https://github.com/palatable/lambda/workflows/Java%20CI/badge.svg)](https://github.com/palatable/lambda/actions) [![Lambda](https://img.shields.io/maven-central/v/com.jnape.palatable/lambda.svg)](http://search.maven.org/#search%7Cga%7C1%7Ccom.jnape.palatable.lambda) -[![Join the chat at https://gitter.im/palatable/lambda](https://badges.gitter.im/palatable/lambda.svg)](https://gitter.im/palatable/lambda?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) +[Join the chat on Discord](https://discord.gg/wR7k8RAKM5) [![Floobits Status](https://floobits.com/jnape/lambda.svg)](https://floobits.com/jnape/lambda/redirect) Functional patterns for Java @@ -30,7 +29,7 @@ Functional patterns for Java - [Either](#either) - [Lenses](#lenses) - [Notes](#notes) - - [Community](#community) + - [Ecosystem](#ecosystem) - [License](#license) Background @@ -61,14 +60,14 @@ Add the following dependency to your: com.jnape.palatable lambda - 5.1.0 + 5.4.0 ``` `build.gradle` ([Gradle](https://docs.gradle.org/current/userguide/dependency_management.html)): ```gradle -compile group: 'com.jnape.palatable', name: 'lambda', version: '5.1.0' +compile group: 'com.jnape.palatable', name: 'lambda', version: '5.4.0' ``` Examples @@ -740,12 +739,23 @@ Wherever possible, _lambda_ maintains interface compatibility with similar, fami Unfortunately, due to Java's type hierarchy and inheritance inconsistencies, this is not always possible. One surprising example of this is how `Fn1` extends `j.u.f.Function`, but `Fn2` does not extend `j.u.f.BiFunction`. This is because `j.u.f.BiFunction` itself does not extend `j.u.f.Function`, but it does define methods that collide with `j.u.f.Function`. For this reason, both `Fn1` and `Fn2` cannot extend their Java counterparts without sacrificing their own inheritance hierarchy. These types of asymmetries are, unfortunately, not uncommon; however, wherever these situations arise, measures are taken to attempt to ease the transition in and out of core Java types (in the case of `Fn2`, a supplemental `#toBiFunction` method is added). I do not take these inconveniences for granted, and I'm regularly looking for ways to minimize the negative impact of this as much as possible. Suggestions and use cases that highlight particular pain points here are particularly appreciated. -Community +Ecosystem ----- -There are some open-sourced community projects that depend on _lambda_ for their own functionality: these projects are listed below (note that these projects are _not_ affiliated with lambda, and have their own maintainers). If you use _lambda_ in your own open-sourced project, feel free to create an issue and I'll be happy to review the project and add it to this section! + +### Official extension libraries: + +These are officially supported libraries that extend lambda's core functionality and are developed under the same governance and processes as lambda. + +- [Shōki](https://github.com/palatable/shoki) - Purely functional, persistent data structures for the JVM + +### Third-party community libraries: + +These are open-sourced community projects that rely on _lambda_ for significant functionality, but are not necessarily affiliated with lambda and have their own separate maintainers. If you use _lambda_ in your own open-sourced project, feel free to create an issue and I'll be happy to review the project and add it to this section! - [Enhanced Iterables](https://github.com/kschuetz/enhanced-iterables) - Kevin Schuetz [@kschuetz](https://github.com/kschuetz) - [Collection Views](https://github.com/kschuetz/collection-views) - Kevin Schuetz [@kschuetz](https://github.com/kschuetz) +- [WuWei](https://github.com/nomicflux/WuWei) - Michael Anderson [@nomicflux](https://github.com/nomicflux) - `ST` monad for safe mutability +- [Kraftwerk](https://github.com/kschuetz/kraftwerk) - Kevin Schuetz [@kschuetz](https://github.com/kschuetz) - random data generators and combinators License ------- diff --git a/pom.xml b/pom.xml index f89d14be0..bae03b1e3 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ lambda - 5.2.0 + 5.5.1-SNAPSHOT jar Lambda @@ -103,6 +103,13 @@ org.apache.maven.plugins maven-jar-plugin ${maven-jar-plugin.version} + + + + com.jnape.palatable.lambda + + + diff --git a/src/main/java/com/jnape/palatable/lambda/adt/These.java b/src/main/java/com/jnape/palatable/lambda/adt/These.java index 6721d33e8..4fc4cc27b 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/These.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/These.java @@ -189,6 +189,25 @@ public static These both(A a, B b) { return new Both<>(tuple(a, b)); } + /** + * Convenience method for converting a pair of {@link Maybe}s into a {@link Maybe} of {@link These}. If both + * {@link Maybe}s are {@link Maybe#just} then the result is a {@link Maybe#just} {@link These#both}. If only one + * {@link Maybe} is {@link Maybe#just} then it will be {@link Maybe#just} {@link These#a} or + * {@link Maybe#just} {@link These#b}. If both {@link Maybe}s are {@link Maybe#nothing} then the result will be + * {@link Maybe#nothing}. + * + * @param maybeA the first optional value + * @param maybeB the second optional value + * @param the first possible type + * @param the second possible type + * @return the wrapped values as a {@link Maybe}<{@link These}<A,B>> + */ + public static Maybe> fromMaybes(Maybe maybeA, Maybe maybeB) { + return maybeA.fmap(a -> maybeB.fmap(b -> both(a, b)).orElse(a(a))) + .fmap(Maybe::just) + .orElse(maybeB.fmap(These::b)); + } + /** * The canonical {@link Pure} instance for {@link These}. * diff --git a/src/main/java/com/jnape/palatable/lambda/adt/hlist/HList.java b/src/main/java/com/jnape/palatable/lambda/adt/hlist/HList.java index 421855a10..705e43bad 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 @@ -89,7 +89,6 @@ public static SingletonHList singletonHList(Head head) { * @return the 2-element HList * @see Tuple2 */ - @SuppressWarnings("JavaDoc") public static <_1, _2> Tuple2<_1, _2> tuple(_1 _1, _2 _2) { return singletonHList(_2).cons(_1); } @@ -106,7 +105,6 @@ public static <_1, _2> Tuple2<_1, _2> tuple(_1 _1, _2 _2) { * @return the 3-element HList * @see Tuple3 */ - @SuppressWarnings("JavaDoc") public static <_1, _2, _3> Tuple3<_1, _2, _3> tuple(_1 _1, _2 _2, _3 _3) { return tuple(_2, _3).cons(_1); } @@ -125,7 +123,6 @@ public static <_1, _2, _3> Tuple3<_1, _2, _3> tuple(_1 _1, _2 _2, _3 _3) { * @return the 4-element HList * @see Tuple4 */ - @SuppressWarnings("JavaDoc") public static <_1, _2, _3, _4> Tuple4<_1, _2, _3, _4> tuple(_1 _1, _2 _2, _3 _3, _4 _4) { return tuple(_2, _3, _4).cons(_1); } @@ -146,7 +143,6 @@ public static <_1, _2, _3, _4> Tuple4<_1, _2, _3, _4> tuple(_1 _1, _2 _2, _3 _3, * @return the 5-element HList * @see Tuple5 */ - @SuppressWarnings("JavaDoc") public static <_1, _2, _3, _4, _5> Tuple5<_1, _2, _3, _4, _5> tuple(_1 _1, _2 _2, _3 _3, _4 _4, _5 _5) { return tuple(_2, _3, _4, _5).cons(_1); } @@ -169,7 +165,6 @@ public static <_1, _2, _3, _4, _5> Tuple5<_1, _2, _3, _4, _5> tuple(_1 _1, _2 _2 * @return the 6-element HList * @see Tuple6 */ - @SuppressWarnings("JavaDoc") public static <_1, _2, _3, _4, _5, _6> Tuple6<_1, _2, _3, _4, _5, _6> tuple(_1 _1, _2 _2, _3 _3, _4 _4, _5 _5, _6 _6) { return tuple(_2, _3, _4, _5, _6).cons(_1); @@ -195,7 +190,6 @@ public static <_1, _2, _3, _4, _5, _6> Tuple6<_1, _2, _3, _4, _5, _6> tuple(_1 _ * @return the 7-element HList * @see Tuple7 */ - @SuppressWarnings("JavaDoc") public static <_1, _2, _3, _4, _5, _6, _7> Tuple7<_1, _2, _3, _4, _5, _6, _7> tuple(_1 _1, _2 _2, _3 _3, _4 _4, _5 _5, _6 _6, _7 _7) { return tuple(_2, _3, _4, _5, _6, _7).cons(_1); @@ -223,7 +217,6 @@ public static <_1, _2, _3, _4, _5, _6, _7> Tuple7<_1, _2, _3, _4, _5, _6, _7> tu * @return the 8-element HList * @see Tuple8 */ - @SuppressWarnings("JavaDoc") public static <_1, _2, _3, _4, _5, _6, _7, _8> Tuple8<_1, _2, _3, _4, _5, _6, _7, _8> tuple(_1 _1, _2 _2, _3 _3, _4 _4, _5 _5, _6 _6, _7 _7, _8 _8) { @@ -273,7 +266,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/main/java/com/jnape/palatable/lambda/adt/hlist/SingletonHList.java b/src/main/java/com/jnape/palatable/lambda/adt/hlist/SingletonHList.java index 927f27ae3..8e8516a47 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/hlist/SingletonHList.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/hlist/SingletonHList.java @@ -39,6 +39,18 @@ public <_0> Tuple2<_0, _1> cons(_0 _0) { return new Tuple2<>(_0, this); } + + /** + * Snoc an element onto the back of this {@link SingletonHList}. + * + * @param _2 the new last element + * @param <_2> the new last element type + * @return the new {@link Tuple2} + */ + public <_2> Tuple2<_1, _2> snoc(_2 _2) { + return tuple(head(), _2); + } + /** * {@inheritDoc} */ diff --git a/src/main/java/com/jnape/palatable/lambda/adt/hlist/Tuple2.java b/src/main/java/com/jnape/palatable/lambda/adt/hlist/Tuple2.java index 726335f6d..aafa40b5e 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/hlist/Tuple2.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/hlist/Tuple2.java @@ -48,7 +48,7 @@ public class Tuple2<_1, _2> extends HCons<_1, SingletonHList<_2>> implements Tuple2(_1 _1, SingletonHList<_2> tail) { super(_1, tail); this._1 = _1; - _2 = tail.head(); + _2 = tail.head(); } /** @@ -75,6 +75,17 @@ public <_0> Tuple3<_0, _1, _2> cons(_0 _0) { return new Tuple3<>(_0, this); } + /** + * Snoc an element onto the back of this {@link Tuple2}. + * + * @param _3 the new last element + * @param <_3> the new last element type + * @return the new {@link Tuple3} + */ + public <_3> Tuple3<_1, _2, _3> snoc(_3 _3) { + return tuple(_1, _2, _3); + } + /** * {@inheritDoc} */ @@ -226,6 +237,15 @@ AppTrav extends Applicative> AppTrav traverse( return fn.apply(_2).fmap(_2Prime -> fmap(constantly(_2Prime))).fmap(Applicative::coerce).coerce(); } + /** + * Returns a {@link SingletonHList}<_1> of the first element. + * + * @return The {@link SingletonHList}<_1> + */ + public SingletonHList<_1> init() { + return invert().tail(); + } + /** * Static factory method for creating Tuple2s from {@link java.util.Map.Entry}s. * diff --git a/src/main/java/com/jnape/palatable/lambda/adt/hlist/Tuple3.java b/src/main/java/com/jnape/palatable/lambda/adt/hlist/Tuple3.java index e32eac589..b392ffe38 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/hlist/Tuple3.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/hlist/Tuple3.java @@ -44,8 +44,8 @@ public class Tuple3<_1, _2, _3> extends HCons<_1, Tuple2<_2, _3>> implements Tuple3(_1 _1, Tuple2<_2, _3> tail) { super(_1, tail); this._1 = _1; - _2 = tail._1(); - _3 = tail._2(); + _2 = tail._1(); + _3 = tail._2(); } /** @@ -56,6 +56,17 @@ public <_0> Tuple4<_0, _1, _2, _3> cons(_0 _0) { return new Tuple4<>(_0, this); } + /** + * Snoc an element onto the back of this {@link Tuple3}. + * + * @param _4 the new last element + * @param <_4> the new last element type + * @return the new {@link Tuple4} + */ + public <_4> Tuple4<_1, _2, _3, _4> snoc(_4 _4) { + return tuple(_1, _2, _3, _4); + } + /** * {@inheritDoc} */ @@ -208,6 +219,16 @@ AppTrav extends Applicative> AppTrav traverse( return fn.apply(_3).fmap(_3Prime -> fmap(constantly(_3Prime))).fmap(Applicative::coerce).coerce(); } + /** + * Returns a {@link Tuple2}<_1, _2> of all the elements of this + * {@link Tuple3}<_1, _2, _3> except the last. + * + * @return The {@link Tuple2}<_1, _2> representing all but the last element + */ + public Tuple2<_1, _2> init() { + return rotateR3().tail(); + } + /** * Given a value of type A, produced an instance of this tuple with each slot set to that value. * diff --git a/src/main/java/com/jnape/palatable/lambda/adt/hlist/Tuple4.java b/src/main/java/com/jnape/palatable/lambda/adt/hlist/Tuple4.java index 3e74da859..d8dc42868 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/hlist/Tuple4.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/hlist/Tuple4.java @@ -46,9 +46,9 @@ public class Tuple4<_1, _2, _3, _4> extends HCons<_1, Tuple3<_2, _3, _4>> implem Tuple4(_1 _1, Tuple3<_2, _3, _4> tail) { super(_1, tail); this._1 = _1; - _2 = tail._1(); - _3 = tail._2(); - _4 = tail._3(); + _2 = tail._1(); + _3 = tail._2(); + _4 = tail._3(); } /** @@ -59,6 +59,17 @@ public <_0> Tuple5<_0, _1, _2, _3, _4> cons(_0 _0) { return new Tuple5<>(_0, this); } + /** + * Snoc an element onto the back of this {@link Tuple4}. + * + * @param _5 the new last element + * @param <_5> the new last element type + * @return the new {@link Tuple5} + */ + public <_5> Tuple5<_1, _2, _3, _4, _5> snoc(_5 _5) { + return tuple(_1, _2, _3, _4, _5); + } + /** * {@inheritDoc} */ @@ -235,6 +246,16 @@ AppTrav extends Applicative> AppTrav traverse( return fn.apply(_4).fmap(_4Prime -> fmap(constantly(_4Prime))).fmap(Applicative::coerce).coerce(); } + /** + * Returns a {@link Tuple3}<_1, _2, _3> of all the elements of this + * {@link Tuple4}<_1, _2, _3, _4> except the last. + * + * @return The {@link Tuple3}<_1, _2, _3> representing all but the last element + */ + public Tuple3<_1, _2, _3> init() { + return rotateR4().tail(); + } + /** * Given a value of type A, produced an instance of this tuple with each slot set to that value. * diff --git a/src/main/java/com/jnape/palatable/lambda/adt/hlist/Tuple5.java b/src/main/java/com/jnape/palatable/lambda/adt/hlist/Tuple5.java index c87ee564b..5a6f20cbb 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/hlist/Tuple5.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/hlist/Tuple5.java @@ -48,10 +48,10 @@ public class Tuple5<_1, _2, _3, _4, _5> extends HCons<_1, Tuple4<_2, _3, _4, _5> Tuple5(_1 _1, Tuple4<_2, _3, _4, _5> tail) { super(_1, tail); this._1 = _1; - _2 = tail._1(); - _3 = tail._2(); - _4 = tail._3(); - _5 = tail._4(); + _2 = tail._1(); + _3 = tail._2(); + _4 = tail._3(); + _5 = tail._4(); } /** @@ -62,6 +62,17 @@ public <_0> Tuple6<_0, _1, _2, _3, _4, _5> cons(_0 _0) { return new Tuple6<>(_0, this); } + /** + * Snoc an element onto the back of this {@link Tuple5}. + * + * @param _6 the new last element + * @param <_6> the new last element type + * @return the new {@link Tuple6} + */ + public <_6> Tuple6<_1, _2, _3, _4, _5, _6> snoc(_6 _6) { + return tuple(_1, _2, _3, _4, _5, _6); + } + /** * {@inheritDoc} */ @@ -262,6 +273,16 @@ AppTrav extends Applicative> AppTrav traverse( return fn.apply(_5).fmap(_3Prime -> fmap(constantly(_3Prime))).fmap(Applicative::coerce).coerce(); } + /** + * Returns a {@link Tuple4}<_1, _2, _3, _4> of all the elements of this + * {@link Tuple5}<_1, _2, _3, _4, _5> except the last. + * + * @return The {@link Tuple4}<_1, _2, _3, _4> representing all but the last element + */ + public Tuple4<_1, _2, _3, _4> init() { + return rotateR5().tail(); + } + /** * Given a value of type A, produced an instance of this tuple with each slot set to that value. * diff --git a/src/main/java/com/jnape/palatable/lambda/adt/hlist/Tuple6.java b/src/main/java/com/jnape/palatable/lambda/adt/hlist/Tuple6.java index 6d07b503b..1034e3b6b 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/hlist/Tuple6.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/hlist/Tuple6.java @@ -51,11 +51,11 @@ public class Tuple6<_1, _2, _3, _4, _5, _6> extends HCons<_1, Tuple5<_2, _3, _4, Tuple6(_1 _1, Tuple5<_2, _3, _4, _5, _6> tail) { super(_1, tail); this._1 = _1; - _2 = tail._1(); - _3 = tail._2(); - _4 = tail._3(); - _5 = tail._4(); - _6 = tail._5(); + _2 = tail._1(); + _3 = tail._2(); + _4 = tail._3(); + _5 = tail._4(); + _6 = tail._5(); } /** @@ -66,6 +66,17 @@ public <_0> Tuple7<_0, _1, _2, _3, _4, _5, _6> cons(_0 _0) { return new Tuple7<>(_0, this); } + /** + * Snoc an element onto the back of this {@link Tuple6}. + * + * @param _7 the new last element + * @param <_7> the new last element type + * @return the new {@link Tuple7} + */ + public <_7> Tuple7<_1, _2, _3, _4, _5, _6, _7> snoc(_7 _7) { + return tuple(_1, _2, _3, _4, _5, _6, _7); + } + /** * {@inheritDoc} */ @@ -293,6 +304,16 @@ AppTrav extends Applicative> AppTrav traverse( return fn.apply(_6).fmap(_6Prime -> fmap(constantly(_6Prime))).fmap(Applicative::coerce).coerce(); } + /** + * Returns a {@link Tuple5}<_1, _2, _3, _4, _5> of all the elements of this + * {@link Tuple6}<_1, _2, _3, _4, _5, _6> except the last. + * + * @return The {@link Tuple5}<_1, _2, _3, _4, _5> representing all but the last element + */ + public Tuple5<_1, _2, _3, _4, _5> init() { + return rotateR6().tail(); + } + /** * Given a value of type A, produced an instance of this tuple with each slot set to that value. * diff --git a/src/main/java/com/jnape/palatable/lambda/adt/hlist/Tuple7.java b/src/main/java/com/jnape/palatable/lambda/adt/hlist/Tuple7.java index a3e162f73..cdfe552d6 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/hlist/Tuple7.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/hlist/Tuple7.java @@ -54,12 +54,12 @@ public class Tuple7<_1, _2, _3, _4, _5, _6, _7> extends HCons<_1, Tuple6<_2, _3, Tuple7(_1 _1, Tuple6<_2, _3, _4, _5, _6, _7> tail) { super(_1, tail); this._1 = _1; - _2 = tail._1(); - _3 = tail._2(); - _4 = tail._3(); - _5 = tail._4(); - _6 = tail._5(); - _7 = tail._6(); + _2 = tail._1(); + _3 = tail._2(); + _4 = tail._3(); + _5 = tail._4(); + _6 = tail._5(); + _7 = tail._6(); } /** @@ -70,6 +70,17 @@ public <_0> Tuple8<_0, _1, _2, _3, _4, _5, _6, _7> cons(_0 _0) { return new Tuple8<>(_0, this); } + /** + * Snoc an element onto the back of this {@link Tuple7}. + * + * @param _8 the new last element + * @param <_8> the new last element type + * @return the new {@link Tuple8} + */ + public <_8> Tuple8<_1, _2, _3, _4, _5, _6, _7, _8> snoc(_8 _8) { + return tuple(_1, _2, _3, _4, _5, _6, _7, _8); + } + /** * {@inheritDoc} */ @@ -323,6 +334,16 @@ AppTrav extends Applicative> AppTrav traverse( return fn.apply(_7).fmap(_7Prime -> fmap(constantly(_7Prime))).fmap(Applicative::coerce).coerce(); } + /** + * Returns a {@link Tuple6}<_1, _2, _3, _4, _5, _6> of all the elements of this + * {@link Tuple7}<_1, _2, _3, _4, _5, _6, _7> except the last. + * + * @return The {@link Tuple6}<_1, _2, _3, _4, _5, _6> representing all but the last element + */ + public Tuple6<_1, _2, _3, _4, _5, _6> init() { + return rotateR7().tail(); + } + /** * Given a value of type A, produced an instance of this tuple with each slot set to that value. * diff --git a/src/main/java/com/jnape/palatable/lambda/adt/hlist/Tuple8.java b/src/main/java/com/jnape/palatable/lambda/adt/hlist/Tuple8.java index 8a8001e24..079a30611 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/hlist/Tuple8.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/hlist/Tuple8.java @@ -57,13 +57,13 @@ public class Tuple8<_1, _2, _3, _4, _5, _6, _7, _8> extends HCons<_1, Tuple7<_2, Tuple8(_1 _1, Tuple7<_2, _3, _4, _5, _6, _7, _8> tail) { super(_1, tail); this._1 = _1; - _2 = tail._1(); - _3 = tail._2(); - _4 = tail._3(); - _5 = tail._4(); - _6 = tail._5(); - _7 = tail._6(); - _8 = tail._7(); + _2 = tail._1(); + _3 = tail._2(); + _4 = tail._3(); + _5 = tail._4(); + _6 = tail._5(); + _7 = tail._6(); + _8 = tail._7(); } /** @@ -74,6 +74,17 @@ public <_0> HCons<_0, Tuple8<_1, _2, _3, _4, _5, _6, _7, _8>> cons(_0 _0) { return new HCons<>(_0, this); } + /** + * Snoc an element onto the back of this {@link Tuple8}. + * + * @param _9 the new last element + * @param <_9> the new last element type + * @return the new {@link HCons consed} {@link Tuple8} + */ + public <_9> HCons<_1, Tuple8<_2, _3, _4, _5, _6, _7, _8, _9>> snoc(_9 _9) { + return singletonHList(_9).cons(_8).cons(_7).cons(_6).cons(_5).cons(_4).cons(_3).cons(_2).cons(_1); + } + /** * {@inheritDoc} */ @@ -353,6 +364,16 @@ AppTrav extends Applicative> AppTrav traverse( return fn.apply(_8).fmap(_8Prime -> fmap(constantly(_8Prime))).fmap(Applicative::coerce).coerce(); } + /** + * Returns a {@link Tuple7}<_1, _2, _3, _4, _5, _6, _7> of all the elements of this + * {@link Tuple8}<_1, _2, _3, _4, _5, _6, _7, _8> except the last. + * + * @return The {@link Tuple7}<_1, _2, _3, _4, _5, _6, _7> representing all but the last element + */ + public Tuple7<_1, _2, _3, _4, _5, _6, _7> init() { + return rotateR8().tail(); + } + /** * Given a value of type A, produced an instance of this tuple with each slot set to that value. * 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 f21e70e23..cb4904d35 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/Fn1.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/Fn1.java @@ -302,6 +302,10 @@ default Fn2 andThen(Fn2 after return (a, c) -> after.apply(apply(a), c); } + default Fn1 self() { + return this; + } + /** * Static factory method for avoid explicit casting when using method references as {@link Fn1}s. * @@ -335,4 +339,17 @@ static Fn1 fromFunction(Function function) static Pure> pureFn1() { return Constantly::constantly; } + + /** + * Construct an {@link Fn1} that has a reference to itself in scope at the time it is executed (presumably for + * recursive invocations). + * + * @param fn the body of the function, with access to itself + * @param the input type + * @param the output type + * @return the {@link Fn1} + */ + static Fn1 withSelf(Fn2, ? super A, ? extends B> fn) { + return a -> fn.apply(withSelf(fn), a); + } } diff --git a/src/main/java/com/jnape/palatable/lambda/functions/Fn7.java b/src/main/java/com/jnape/palatable/lambda/functions/Fn7.java index 2d27e6be2..15f7a3be9 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/Fn7.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/Fn7.java @@ -8,7 +8,7 @@ import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; /** - * A function taking six arguments. Defined in terms of {@link Fn6}, so similarly auto-curried. + * A function taking seven arguments. Defined in terms of {@link Fn6}, so similarly auto-curried. * * @param The first argument type * @param The second argument type @@ -258,7 +258,7 @@ static Fn7 fn7(Fn4 the first input argument type * @param the second input argument type * @param the third input argument type @@ -276,7 +276,7 @@ static Fn7 fn7(Fn5 the first input argument type * @param the second input argument type * @param the third input argument type diff --git a/src/main/java/com/jnape/palatable/lambda/functions/Fn8.java b/src/main/java/com/jnape/palatable/lambda/functions/Fn8.java index a38e74eb1..60f3a4a3c 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/Fn8.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/Fn8.java @@ -5,7 +5,7 @@ import com.jnape.palatable.lambda.internal.Runtime; /** - * A function taking six arguments. Defined in terms of {@link Fn7}, so similarly auto-curried. + * A function taking eight arguments. Defined in terms of {@link Fn7}, so similarly auto-curried. * * @param The first argument type * @param The second argument type @@ -274,7 +274,7 @@ static Fn8 fn8( /** * Static factory method for wrapping a curried {@link Fn5} in an {@link Fn8}. * - * @param curriedFn5 the curried fn4 to adapt + * @param curriedFn5 the curried fn5 to adapt * @param the first input argument type * @param the second input argument type * @param the third input argument type @@ -294,7 +294,7 @@ static Fn8 fn8( /** * Static factory method for wrapping a curried {@link Fn6} in an {@link Fn8}. * - * @param curriedFn6 the curried fn4 to adapt + * @param curriedFn6 the curried fn6 to adapt * @param the first input argument type * @param the second input argument type * @param the third input argument type @@ -314,7 +314,7 @@ static Fn8 fn8( /** * Static factory method for wrapping a curried {@link Fn7} in an {@link Fn8}. * - * @param curriedFn7 the curried fn4 to adapt + * @param curriedFn7 the curried fn7 to adapt * @param the first input argument type * @param the second input argument type * @param the third input argument type diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/$.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/$.java new file mode 100644 index 000000000..63c60c051 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/$.java @@ -0,0 +1,43 @@ +package com.jnape.palatable.lambda.functions.builtin.fn2; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.Fn2; + +/** + * Function application, represented as a higher-order {@link Fn2} that receives an {@link Fn1} and its argument, and + * applies it. Useful for treating application as a combinator, e.g.: + *
+ * {@code
+ * List> fns     = asList(x -> x + 1, x -> x, x -> x - 1);
+ * List               args    = asList(0, 1, 2);
+ * Iterable           results = zipWith($(), fns, args); // [1, 1, 1]
+ * }
+ * 
+ * + * @param
the applied {@link Fn1 Fn1's} input type + * @param the applied {@link Fn1 Fn1's} output type + */ +public final class $ implements Fn2, A, B> { + private static final $ INSTANCE = new $<>(); + + private $() { + } + + @Override + public B checkedApply(Fn1 fn, A a) { + return fn.apply(a); + } + + @SuppressWarnings("unchecked") + public static $ $() { + return ($) INSTANCE; + } + + public static Fn1 $(Fn1 fn) { + return $.$().apply(fn); + } + + public static B $(Fn1 fn, A a) { + return $.$(fn).apply(a); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/internal/iteration/PredicatedDroppingIterable.java b/src/main/java/com/jnape/palatable/lambda/internal/iteration/PredicatedDroppingIterable.java index d87cf0e09..ea7b68bc7 100644 --- a/src/main/java/com/jnape/palatable/lambda/internal/iteration/PredicatedDroppingIterable.java +++ b/src/main/java/com/jnape/palatable/lambda/internal/iteration/PredicatedDroppingIterable.java @@ -1,24 +1,20 @@ package com.jnape.palatable.lambda.internal.iteration; import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.internal.ImmutableQueue; -import java.util.ArrayList; import java.util.Iterator; -import java.util.List; - -import static com.jnape.palatable.lambda.functions.builtin.fn2.Any.any; -import static java.util.Collections.singletonList; public final class PredicatedDroppingIterable implements Iterable { - private final List> predicates; - private final Iterable as; + private final ImmutableQueue> predicates; + private final Iterable as; public PredicatedDroppingIterable(Fn1 predicate, Iterable as) { - List> predicates = new ArrayList<>(singletonList(predicate)); + ImmutableQueue> predicates = ImmutableQueue.singleton(predicate); while (as instanceof PredicatedDroppingIterable) { PredicatedDroppingIterable nested = (PredicatedDroppingIterable) as; as = nested.as; - predicates.addAll(0, nested.predicates); + predicates = nested.predicates.concat(predicates); } this.predicates = predicates; this.as = as; @@ -26,7 +22,6 @@ public PredicatedDroppingIterable(Fn1 predicate, I @Override public Iterator iterator() { - Fn1 metaPredicate = a -> any(p -> p.apply(a), predicates); - return new PredicatedDroppingIterator<>(metaPredicate, as.iterator()); + return new PredicatedDroppingIterator<>(predicates, as.iterator()); } } diff --git a/src/main/java/com/jnape/palatable/lambda/internal/iteration/PredicatedDroppingIterator.java b/src/main/java/com/jnape/palatable/lambda/internal/iteration/PredicatedDroppingIterator.java index 7add62a45..a97b513ac 100644 --- a/src/main/java/com/jnape/palatable/lambda/internal/iteration/PredicatedDroppingIterator.java +++ b/src/main/java/com/jnape/palatable/lambda/internal/iteration/PredicatedDroppingIterator.java @@ -1,19 +1,18 @@ package com.jnape.palatable.lambda.internal.iteration; import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.internal.ImmutableQueue; import java.util.Iterator; import java.util.NoSuchElementException; public final class PredicatedDroppingIterator extends ImmutableIterator { - private final Fn1 predicate; - private final RewindableIterator rewindableIterator; - private boolean finishedDropping; + private final Iterator> predicates; + private final RewindableIterator rewindableIterator; - public PredicatedDroppingIterator(Fn1 predicate, Iterator asIterator) { - this.predicate = predicate; + public PredicatedDroppingIterator(ImmutableQueue> predicates, Iterator asIterator) { + this.predicates = predicates.iterator(); rewindableIterator = new RewindableIterator<>(asIterator); - finishedDropping = false; } @Override @@ -31,11 +30,17 @@ public A next() { } private void dropElementsIfNecessary() { - while (rewindableIterator.hasNext() && !finishedDropping) { - if (!predicate.apply(rewindableIterator.next())) { - rewindableIterator.rewind(); - finishedDropping = true; + while (predicates.hasNext() && rewindableIterator.hasNext()) { + Fn1 predicate = predicates.next(); + boolean predicateDone = false; + + while (rewindableIterator.hasNext() && !predicateDone) { + if (!predicate.apply(rewindableIterator.next())) { + rewindableIterator.rewind(); + predicateDone = true; + } } + } } } diff --git a/src/main/java/com/jnape/palatable/lambda/internal/iteration/RewindableIterator.java b/src/main/java/com/jnape/palatable/lambda/internal/iteration/RewindableIterator.java index 5bdde4312..092e7e98b 100644 --- a/src/main/java/com/jnape/palatable/lambda/internal/iteration/RewindableIterator.java +++ b/src/main/java/com/jnape/palatable/lambda/internal/iteration/RewindableIterator.java @@ -54,9 +54,7 @@ public A retrieve() { if (cache == null) throw new NoSuchElementException("Cache is empty."); - A cache = this.cache; - this.cache = null; - return cache; + return this.cache; } public boolean isEmpty() { 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 606e7d894..cb719060b 100644 --- a/src/main/java/com/jnape/palatable/lambda/monad/transformer/builtin/IterateT.java +++ b/src/main/java/com/jnape/palatable/lambda/monad/transformer/builtin/IterateT.java @@ -8,6 +8,7 @@ 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.Lift; import com.jnape.palatable.lambda.functions.specialized.Pure; import com.jnape.palatable.lambda.functor.Applicative; import com.jnape.palatable.lambda.functor.builtin.Lazy; @@ -28,6 +29,9 @@ 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.Fn0.fn0; +import static com.jnape.palatable.lambda.functions.Fn1.withSelf; +import static com.jnape.palatable.lambda.functions.builtin.fn2.$.$; 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; @@ -61,22 +65,15 @@ * @param the effect type * @param the element type */ -public class IterateT, A> implements - MonadT, IterateT> { +public class IterateT, A> implements MonadT, IterateT> { private final Pure pureM; - private final ImmutableQueue> conses; - private final ImmutableQueue>>, M>>, IterateT>> middles; - private final ImmutableQueue> snocs; + private final ImmutableQueue>>, M>>, MonadRec>> spine; private IterateT(Pure pureM, - ImmutableQueue> conses, - ImmutableQueue>>, M>>, IterateT>> middles, - ImmutableQueue> snocs) { + ImmutableQueue>>, M>>, MonadRec>> spine) { this.pureM = pureM; - this.conses = conses; - this.middles = middles; - this.snocs = snocs; + this.spine = spine; } /** @@ -86,7 +83,35 @@ private IterateT(Pure pureM, * @return the embedded {@link Monad} */ public >>, M>> MMTA runIterateT() { - return pureM., MonadRec, M>>apply(this).trampolineM(IterateT::resume).coerce(); + return pureM., MonadRec, M>>apply(this) + .>>>trampolineM(iterateT -> iterateT.runStep() + .fmap(maybeMore -> maybeMore.match( + fn0(() -> terminate(nothing())), + t -> t.into((Maybe maybeA, IterateT as) -> maybeA.match( + fn0(() -> recurse(as)), + a -> terminate(just(tuple(a, as)))))))) + .coerce(); + } + + /** + * Run a single step of this {@link IterateT}, where a step is the smallest amount of work that could possibly be + * productive in advancing through the {@link IterateT}. Useful for implementing interleaving algorithms that + * require {@link IterateT IterateTs} to yield, emit, or terminate as soon as possible, regardless of whether the + * next element is readily available. + * + * @param the witnessed target type of the step + * @return the step + */ + public , IterateT>>, M>> MStep runStep() { + return spine.head().match( + fn0(() -> pureM., IterateT>>, MStep>apply(nothing())), + thunkOrReal -> thunkOrReal.match( + thunk -> thunk.apply()., IterateT>>>fmap(m -> m.match( + fn0(() -> just(tuple(nothing(), new IterateT<>(pureM, spine.tail())))), + t -> just(t.biMap(Maybe::just, + as -> new IterateT<>(pureM, as.spine.concat(spine.tail())))))) + .coerce(), + ma -> ma.fmap(a -> just(tuple(just(a), new IterateT<>(pureM, spine.tail())))).coerce())); } /** @@ -96,7 +121,7 @@ public >>, M>> MMTA runIter * @return the cons'ed {@link IterateT} */ public final IterateT cons(MonadRec head) { - return new IterateT<>(pureM, conses.pushFront(head), middles, snocs); + return new IterateT<>(pureM, spine.pushFront(b(head))); } /** @@ -106,7 +131,7 @@ public final IterateT cons(MonadRec head) { * @return the snoc'ed {@link IterateT} */ public final IterateT snoc(MonadRec last) { - return new IterateT<>(pureM, conses, middles, snocs.pushBack(last)); + return new IterateT<>(pureM, spine.pushBack(b(last))); } /** @@ -116,13 +141,7 @@ public final IterateT snoc(MonadRec last) { * @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()); + return new IterateT<>(pureM, spine.concat(other.spine)); } /** @@ -188,9 +207,19 @@ public > IterateT lift(MonadRec nb) { * {@inheritDoc} */ @Override + @SuppressWarnings("RedundantTypeArguments") public IterateT trampolineM( Fn1, IterateT>> fn) { - return trampolineM(fn, ImmutableQueue.>>empty().pushBack(flatMap(fn))); + return $(withSelf( + (self, queued) -> suspended( + () -> pureM.>, MonadRec>, M>>apply(queued) + .trampolineM(q -> q.runIterateT().>, Maybe>>>>fmap(m -> m.match( + __ -> terminate(nothing()), + into((rr, tail) -> rr.biMap( + a -> fn.apply(a).>>coerce().concat(tail), + b -> just(tuple(b, self.apply(tail)))))))), + pureM)), + flatMap(fn)); } /** @@ -199,11 +228,15 @@ public IterateT trampolineM( @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); + .trampolineM(into((a, as) -> maybeT( + f.apply(a).>coerce().runIterateT() + .flatMap(maybePair -> maybePair.match( + fn0(() -> as.runIterateT() + .fmap(maybeResult -> maybeResult.fmap(RecursiveResult::recurse))), + t -> pureM.apply(just(terminate(t.fmap(mb -> mb.concat(as.flatMap(f)))))) + ))))) + .runMaybeT(), + pureM); } /** @@ -219,7 +252,7 @@ public IterateT fmap(Fn1 fn) { */ @Override public IterateT pure(B b) { - return singleton(runIterateT().pure(b)); + return singleton(pureM.>apply(b)); } /** @@ -286,54 +319,6 @@ 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}. * @@ -343,7 +328,7 @@ private IterateT trampolineM(Fn1, A> IterateT empty(Pure pureM) { - return new IterateT<>(pureM, ImmutableQueue.empty(), ImmutableQueue.empty(), ImmutableQueue.empty()); + return new IterateT<>(pureM, ImmutableQueue.empty()); } /** @@ -355,10 +340,7 @@ public static , A> IterateT empty(Pure pureM) * @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()); + return IterateT.empty(Pure.of(ma)).cons(ma); } /** @@ -371,12 +353,7 @@ public static , A> IterateT singleton(MonadRec, A> IterateT iterateT( MonadRec>>, M> unwrapped) { - return new IterateT<>( - Pure.of(unwrapped), - ImmutableQueue.empty(), - ImmutableQueue.>>, M>>, IterateT>>empty() - .pushFront(a(() -> unwrapped)), - ImmutableQueue.empty()); + return suspended(() -> unwrapped, Pure.of(unwrapped)); } /** @@ -393,9 +370,7 @@ 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)); + return foldLeft(IterateT::snoc, singleton(ma), as); } /** @@ -412,9 +387,10 @@ public static , A> IterateT of( */ 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)); + Pure pureM = Pure.of(mb); + return $(withSelf((self, mmb) -> suspended(() -> maybeT(mmb.flatMap(fn)) + .fmap(ab -> ab.>fmap(b -> self.apply(pureM.apply(b)))) + .runMaybeT(), pureM)), mb); } /** @@ -429,12 +405,7 @@ public static , A, B> IterateT unfold( */ 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()); + return new IterateT<>(pureM, ImmutableQueue.singleton(a(thunk))); } /** @@ -451,4 +422,29 @@ public static IterateT, A> fromIterator(Iterator as) { return nothing(); }), io(() -> as)); } + + /** + * The canonical {@link Pure} instance for {@link IterateT}. + * + * @param pureM the argument {@link Monad} {@link Pure} + * @param the argument {@link Monad} witness + * @return the {@link Pure} instance + */ + public static > Pure> pureIterateT(Pure pureM) { + return new Pure>() { + @Override + public IterateT checkedApply(A a) { + return liftIterateT().apply(pureM.>apply(a)); + } + }; + } + + /** + * {@link Lift} for {@link IterateT}. + * + * @return the {@link Monad} lifted into {@link IterateT} + */ + public static Lift> liftIterateT() { + return IterateT::singleton; + } } 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 c2e02fd94..95dcdfd21 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 @@ -1,6 +1,7 @@ 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.functions.Fn1; import com.jnape.palatable.lambda.functions.recursion.RecursiveResult; import com.jnape.palatable.lambda.functions.specialized.Lift; @@ -9,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; @@ -16,6 +18,8 @@ import static com.jnape.palatable.lambda.adt.Maybe.just; import static com.jnape.palatable.lambda.adt.Maybe.nothing; +import static com.jnape.palatable.lambda.adt.Unit.UNIT; +import static com.jnape.palatable.lambda.functions.Fn0.fn0; import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; import static com.jnape.palatable.lambda.functions.recursion.RecursiveResult.terminate; @@ -26,7 +30,7 @@ * @param the carrier type */ public final class MaybeT, A> implements - MonadT, MaybeT> { + MonadT, MaybeT>, MonadError> { private final MonadRec, M> mma; @@ -67,6 +71,24 @@ public MaybeT or(MaybeT other) { a -> mMaybeA.pure(just(a))))); } + /** + * {@inheritDoc} + */ + @Override + public MaybeT throwError(Unit unit) { + return maybeT(mma.pure(nothing())); + } + + /** + * {@inheritDoc} + */ + @Override + public MaybeT catchError(Fn1>> recoveryFn) { + return maybeT(mma.flatMap(maybeA -> maybeA.match( + fn0(() -> recoveryFn.apply(UNIT).>coerce().runMaybeT()), + a -> mma.pure(just(a))))); + } + /** * {@inheritDoc} */ 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 443470c6e..e4c8ec5f0 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 @@ -120,7 +120,7 @@ public ReaderT pure(B b) { */ @Override public ReaderT fmap(Fn1 fn) { - return MonadT.super.fmap(fn).coerce(); + return readerT(r -> runReaderT(r).fmap(fn)); } /** @@ -204,6 +204,19 @@ public ReaderT> carry() { return (ReaderT>) Cartesian.super.carry(); } + /** + * Given a {@link Pure} ask will give you access to the input within the monadic embedding + * + * @param pureM the {@link Pure} instance for the given {@link Monad} + * @param the input and output type of the returned ReaderT + * @param the returned {@link Monad} + * @return the {@link ReaderT} + */ + public static > ReaderT ask(Pure pureM) { + //noinspection Convert2MethodRef + return readerT(a -> pureM.apply(a)); + } + /** * Lift a {@link Fn1 function} (R -> {@link Monad}<A, M>) into a {@link ReaderT} instance. * diff --git a/src/main/java/com/jnape/palatable/lambda/monad/transformer/builtin/StateT.java b/src/main/java/com/jnape/palatable/lambda/monad/transformer/builtin/StateT.java index 071ce0dc5..219ad3e03 100644 --- a/src/main/java/com/jnape/palatable/lambda/monad/transformer/builtin/StateT.java +++ b/src/main/java/com/jnape/palatable/lambda/monad/transformer/builtin/StateT.java @@ -144,7 +144,7 @@ public StateT pure(B b) { */ @Override public StateT fmap(Fn1 fn) { - return MonadT.super.fmap(fn).coerce(); + return stateT(s -> runStateT(s).fmap(t -> t.biMapL(fn))); } /** @@ -292,7 +292,7 @@ public static , A> StateT stateT( public static > Pure> pureStateT(Pure pureM) { return new Pure>() { @Override - public StateT checkedApply(A a) throws Throwable { + public StateT checkedApply(A a) { return stateT(pureM.>apply(a)); } }; diff --git a/src/main/java/com/jnape/palatable/lambda/monad/transformer/builtin/WriterT.java b/src/main/java/com/jnape/palatable/lambda/monad/transformer/builtin/WriterT.java index 7a0205543..c8625bbf8 100644 --- a/src/main/java/com/jnape/palatable/lambda/monad/transformer/builtin/WriterT.java +++ b/src/main/java/com/jnape/palatable/lambda/monad/transformer/builtin/WriterT.java @@ -34,9 +34,12 @@ public final class WriterT, A> implements MonadWriter>, MonadT, WriterT> { + private final Pure pureM; private final Fn1, ? extends MonadRec, M>> writerFn; - private WriterT(Fn1, ? extends MonadRec, M>> writerFn) { + private WriterT(Pure pureM, + Fn1, ? extends MonadRec, M>> writerFn) { + this.pureM = pureM; this.writerFn = writerFn; } @@ -82,7 +85,7 @@ public > MW execWriterT(Monoid monoid) { */ @Override public WriterT> listens(Fn1 fn) { - return new WriterT<>(writerFn.fmap(m -> m.fmap(into((a, w) -> both(both(constantly(a), fn), id(), w))))); + return new WriterT<>(pureM, writerFn.fmap(m -> m.fmap(into((a, w) -> both(both(constantly(a), fn), id(), w))))); } /** @@ -90,7 +93,7 @@ public WriterT> listens(Fn1 fn) { */ @Override public WriterT censor(Fn1 fn) { - return new WriterT<>(writerFn.fmap(mt -> mt.fmap(t -> t.fmap(fn)))); + return new WriterT<>(pureM, writerFn.fmap(mt -> mt.fmap(t -> t.fmap(fn)))); } /** @@ -107,7 +110,7 @@ public > WriterT lift(MonadRec mb) { @Override public WriterT trampolineM( Fn1, WriterT>> fn) { - return new WriterT<>(monoid -> runWriterT(monoid).trampolineM(into((a, w) -> fn.apply(a) + return new WriterT<>(pureM, monoid -> runWriterT(monoid).trampolineM(into((a, w) -> fn.apply(a) .>>coerce() .runWriterT(monoid).fmap(t -> t.fmap(monoid.apply(w))) .fmap(into((aOrB, w_) -> aOrB.biMap(a_ -> tuple(a_, w_), b -> tuple(b, w_))))))); @@ -126,7 +129,7 @@ public WriterT fmap(Fn1 fn) { */ @Override public WriterT pure(B b) { - return new WriterT<>(m -> runWriterT(m).pure(tuple(b, m.identity()))); + return new WriterT<>(pureM, m -> pureM.apply(tuple(b, m.identity()))); } /** @@ -134,7 +137,7 @@ public WriterT pure(B b) { */ @Override public WriterT flatMap(Fn1>> f) { - return new WriterT<>(monoid -> writerFn.apply(monoid) + return new WriterT<>(pureM, monoid -> writerFn.apply(monoid) .flatMap(into((a, w) -> f.apply(a).>coerce().runWriterT(monoid) .fmap(t -> t.fmap(monoid.apply(w)))))); } @@ -144,7 +147,7 @@ public WriterT flatMap(Fn1 WriterT zip(Applicative, WriterT> appFn) { - return new WriterT<>(monoid -> runWriterT(monoid) + return new WriterT<>(pureM, monoid -> runWriterT(monoid) .zip(appFn.>>coerce().runWriterT(monoid) .fmap(into((f, y) -> into((a, x) -> tuple(f.apply(a), monoid.apply(x, y))))))); } @@ -196,7 +199,7 @@ public static > WriterT tell(MonadRec, A> WriterT listen(MonadRec ma) { - return new WriterT<>(monoid -> ma.fmap(a -> tuple(a, monoid.identity()))); + return new WriterT<>(Pure.of(ma), monoid -> ma.fmap(a -> tuple(a, monoid.identity()))); } /** @@ -209,7 +212,7 @@ public static , A> WriterT listen(MonadRec< * @return the {@link WriterT} */ public static , A> WriterT writerT(MonadRec, M> maw) { - return new WriterT<>(constantly(maw)); + return new WriterT<>(Pure.of(maw), constantly(maw)); } /** diff --git a/src/main/java/com/jnape/palatable/lambda/monoid/Monoid.java b/src/main/java/com/jnape/palatable/lambda/monoid/Monoid.java index 8ecf83775..59f4ce1c7 100644 --- a/src/main/java/com/jnape/palatable/lambda/monoid/Monoid.java +++ b/src/main/java/com/jnape/palatable/lambda/monoid/Monoid.java @@ -82,7 +82,7 @@ default A foldLeft(A a, Iterable as) { */ @Override default Lazy foldRight(A a, Iterable as) { - return lazy(() -> flip().foldMap(id(), reverse(cons(a, as)))); + return lazy(() -> flip().foldMap(id(), cons(a, reverse(as)))); } /** diff --git a/src/main/java/com/jnape/palatable/lambda/semigroup/Semigroup.java b/src/main/java/com/jnape/palatable/lambda/semigroup/Semigroup.java index a061328ce..d4cc1e6b4 100644 --- a/src/main/java/com/jnape/palatable/lambda/semigroup/Semigroup.java +++ b/src/main/java/com/jnape/palatable/lambda/semigroup/Semigroup.java @@ -39,7 +39,7 @@ default A foldLeft(A a, Iterable as) { * @see FoldRight */ default Lazy foldRight(A a, Iterable as) { - return FoldRight.foldRight((y, lazyX) -> lazyX.fmap(x -> apply(x, y)), lazy(a), as); + return FoldRight.foldRight((y, lazyX) -> lazyX.fmap(x -> apply(y, x)), lazy(a), as); } /** diff --git a/src/main/java/com/jnape/palatable/lambda/semigroup/builtin/Absent.java b/src/main/java/com/jnape/palatable/lambda/semigroup/builtin/Absent.java index a692eac77..222a5e7dc 100644 --- a/src/main/java/com/jnape/palatable/lambda/semigroup/builtin/Absent.java +++ b/src/main/java/com/jnape/palatable/lambda/semigroup/builtin/Absent.java @@ -2,11 +2,21 @@ import com.jnape.palatable.lambda.adt.Maybe; import com.jnape.palatable.lambda.functions.Fn1; -import com.jnape.palatable.lambda.functions.builtin.fn3.LiftA2; +import com.jnape.palatable.lambda.functions.builtin.fn3.FoldRight; import com.jnape.palatable.lambda.functions.specialized.SemigroupFactory; +import com.jnape.palatable.lambda.functor.builtin.Lazy; import com.jnape.palatable.lambda.monoid.builtin.Present; import com.jnape.palatable.lambda.semigroup.Semigroup; +import static com.jnape.palatable.lambda.adt.Maybe.nothing; +import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Into.into; +import static com.jnape.palatable.lambda.functions.builtin.fn3.LiftA2.liftA2; +import static com.jnape.palatable.lambda.functions.recursion.RecursiveResult.recurse; +import static com.jnape.palatable.lambda.functions.recursion.RecursiveResult.terminate; +import static com.jnape.palatable.lambda.functions.recursion.Trampoline.trampoline; +import static com.jnape.palatable.lambda.functor.builtin.Lazy.lazy; + /** * A {@link Semigroup} instance formed by {@link Maybe}<A> and a semigroup over A. The * application to two {@link Maybe} values is absence-biased, such that for a given {@link Maybe} x @@ -32,7 +42,7 @@ private Absent() { @Override public Semigroup> checkedApply(Semigroup aSemigroup) { - return LiftA2., Maybe>liftA2(aSemigroup)::apply; + return shortCircuitSemigroup(aSemigroup); } @SuppressWarnings("unchecked") @@ -40,8 +50,8 @@ public static Absent absent() { return (Absent) INSTANCE; } - public static Semigroup> absent(Semigroup semigroup) { - return Absent.absent().apply(semigroup); + public static Semigroup> absent(Semigroup aSemigroup) { + return shortCircuitSemigroup(aSemigroup); } public static Fn1, Maybe> absent(Semigroup aSemigroup, Maybe x) { @@ -51,4 +61,34 @@ public static Fn1, Maybe> absent(Semigroup aSemigroup, Maybe< public static Maybe absent(Semigroup semigroup, Maybe x, Maybe y) { return absent(semigroup, x).apply(y); } + + private static Semigroup> shortCircuitSemigroup(Semigroup aSemigroup) { + return new Semigroup>() { + @Override + public Maybe checkedApply(Maybe maybeX, Maybe maybeY) { + return liftA2(aSemigroup, maybeX, maybeY); + } + + @Override + public Maybe foldLeft(Maybe acc, Iterable> maybes) { + return trampoline( + into((res, it) -> res.equals(nothing()) || !it.hasNext() + ? terminate(res) + : recurse(tuple(liftA2(aSemigroup, res, it.next()), it))), + tuple(acc, maybes.iterator())); + } + + @Override + public Lazy> foldRight(Maybe accumulation, Iterable> as) { + boolean shouldShortCircuit = accumulation == nothing(); + if (shouldShortCircuit) + return lazy(accumulation); + return FoldRight.foldRight( + (maybeX, acc) -> maybeX.lazyZip(acc.fmap(maybeY -> maybeY.fmap(aSemigroup.flip()))), + lazy(accumulation), + as + ); + } + }; + } } diff --git a/src/test/java/com/jnape/palatable/lambda/adt/TheseTest.java b/src/test/java/com/jnape/palatable/lambda/adt/TheseTest.java index 36a195011..e9f764d49 100644 --- a/src/test/java/com/jnape/palatable/lambda/adt/TheseTest.java +++ b/src/test/java/com/jnape/palatable/lambda/adt/TheseTest.java @@ -12,9 +12,12 @@ import testsupport.traits.MonadRecLaws; import testsupport.traits.TraversableLaws; +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.adt.Maybe.nothing; import static com.jnape.palatable.lambda.adt.These.a; import static com.jnape.palatable.lambda.adt.These.b; import static com.jnape.palatable.lambda.adt.These.both; +import static com.jnape.palatable.lambda.adt.These.fromMaybes; import static com.jnape.palatable.lambda.functor.builtin.Lazy.lazy; import static com.jnape.palatable.traitor.framework.Subjects.subjects; import static org.junit.Assert.assertEquals; @@ -48,4 +51,12 @@ public void staticPure() { These these = These.pureThese().apply(1); assertEquals(b(1), these); } + + @Test + public void fromMaybesPermutations() { + assertEquals(nothing(), fromMaybes(nothing(), nothing())); + assertEquals(just(These.a(1)), fromMaybes(just(1), nothing())); + assertEquals(just(These.b(1)), fromMaybes(nothing(), just(1))); + assertEquals(just(These.both(1, "hello")), fromMaybes(just(1), just("hello"))); + } } \ No newline at end of file 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 1f75ce450..3e3513a3a 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,9 +2,15 @@ import org.junit.Test; -import static com.jnape.palatable.lambda.adt.hlist.HList.*; +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.hamcrest.CoreMatchers.instanceOf; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertThat; public class HListTest { @@ -48,16 +54,15 @@ public void nilReusesInstance() { } @Test - @SuppressWarnings({"EqualsWithItself", "EqualsBetweenInconvertibleTypes"}) public void equality() { - assertTrue(nil().equals(nil())); - assertTrue(cons(1, nil()).equals(cons(1, nil()))); + assertEquals(nil(), nil()); + assertEquals(cons(1, nil()), cons(1, nil())); - assertFalse(cons(1, nil()).equals(nil())); - assertFalse(nil().equals(cons(1, nil()))); + assertNotEquals(cons(1, nil()), nil()); + assertNotEquals(nil(), cons(1, nil())); - assertFalse(cons(1, cons(2, nil())).equals(cons(1, nil()))); - assertFalse(cons(1, nil()).equals(cons(1, cons(2, nil())))); + assertNotEquals(cons(1, cons(2, nil())), cons(1, nil())); + assertNotEquals(cons(1, nil()), cons(1, cons(2, nil()))); } @Test diff --git a/src/test/java/com/jnape/palatable/lambda/adt/hlist/SingletonHListTest.java b/src/test/java/com/jnape/palatable/lambda/adt/hlist/SingletonHListTest.java index 57a9f85b7..7a577f2ce 100644 --- a/src/test/java/com/jnape/palatable/lambda/adt/hlist/SingletonHListTest.java +++ b/src/test/java/com/jnape/palatable/lambda/adt/hlist/SingletonHListTest.java @@ -13,6 +13,7 @@ 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 com.jnape.palatable.lambda.adt.hlist.SingletonHList.pureSingletonHList; import static org.junit.Assert.assertEquals; @@ -46,6 +47,11 @@ public void cons() { assertEquals(new Tuple2<>("0", singletonHList), singletonHList.cons("0")); } + @Test + public void snoc() { + assertEquals(tuple((byte) 127, 'x'), singletonHList((byte) 127).snoc('x')); + } + @Test public void intoAppliesHeadToFn() { assertEquals("FOO", singletonHList("foo").into(String::toUpperCase)); diff --git a/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple2Test.java b/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple2Test.java index 32483a168..6637386ce 100644 --- a/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple2Test.java +++ b/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple2Test.java @@ -63,11 +63,21 @@ public void tail() { assertEquals(new SingletonHList<>(2), tuple2.tail()); } + @Test + public void init() { + assertEquals(new SingletonHList<>(1), tuple2.init()); + } + @Test public void cons() { assertEquals(new Tuple3<>(0, tuple2), tuple2.cons(0)); } + @Test + public void snoc() { + assertEquals(tuple(Long.MAX_VALUE, 123, "hi"), tuple(Long.MAX_VALUE, 123).snoc("hi")); + } + @Test public void accessors() { assertEquals((Integer) 1, tuple2._1()); diff --git a/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple3Test.java b/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple3Test.java index 9ac57fcfb..a4cad8198 100644 --- a/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple3Test.java +++ b/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple3Test.java @@ -18,6 +18,7 @@ import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; import static com.jnape.palatable.lambda.adt.hlist.Tuple3.pureTuple; import static com.jnape.palatable.lambda.functions.builtin.fn1.Repeat.repeat; +import static java.time.Duration.ofSeconds; import static java.util.Collections.emptyList; import static java.util.Collections.singletonList; import static org.junit.Assert.assertEquals; @@ -62,6 +63,12 @@ public void cons() { assertEquals(new Tuple4<>(0, tuple3), tuple3.cons(0)); } + @Test + public void snoc() { + assertEquals(tuple("qux", Long.MIN_VALUE, 7, ofSeconds(13)), + tuple("qux", Long.MIN_VALUE, 7).snoc(ofSeconds(13))); + } + @Test public void accessors() { assertEquals((Integer) 1, tuple3._1()); @@ -119,4 +126,10 @@ public void staticPure() { Tuple3 tuple = pureTuple(1, "2").apply('3'); assertEquals(tuple(1, "2", '3'), tuple); } + + @Test + public void init() { + assertEquals(tuple(1, 2), + tuple(1, 2, 3).init()); + } } diff --git a/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple4Test.java b/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple4Test.java index b2a1d6319..13d5da838 100644 --- a/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple4Test.java +++ b/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple4Test.java @@ -62,6 +62,11 @@ public void cons() { assertEquals(new Tuple5<>(0, tuple4), tuple4.cons(0)); } + @Test + public void snoc() { + assertEquals(tuple("qux", 7, "foo", 13L, 17), tuple("qux", 7, "foo", 13L).snoc(17)); + } + @Test public void accessors() { assertEquals((Integer) 1, tuple4._1()); @@ -122,4 +127,10 @@ public void staticPure() { Tuple4 tuple = pureTuple(1, "2", '3').apply(true); assertEquals(tuple(1, "2", '3', true), tuple); } + + @Test + public void init() { + assertEquals(tuple(1, 2, 3), + tuple(1, 2, 3, 4).init()); + } } \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple5Test.java b/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple5Test.java index 83b300b97..7b81a2190 100644 --- a/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple5Test.java +++ b/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple5Test.java @@ -63,6 +63,11 @@ public void cons() { assertEquals(new HCons<>(0, tuple5), tuple5.cons(0)); } + @Test + public void snoc() { + assertEquals(tuple("a", 5, "b", 7, "c", 11), tuple("a", 5, "b", 7, "c").snoc(11)); + } + @Test public void accessors() { assertEquals((Integer) 1, tuple5._1()); @@ -128,4 +133,10 @@ public void staticPure() { Tuple5 tuple = pureTuple(1, "2", '3', true).apply(5f); assertEquals(tuple(1, "2", '3', true, 5f), tuple); } + + @Test + public void init() { + assertEquals(tuple(1, 2, 3, 4), + tuple(1, 2, 3, 4, 5).init()); + } } \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple6Test.java b/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple6Test.java index 902b4b254..880eee1e4 100644 --- a/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple6Test.java +++ b/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple6Test.java @@ -64,6 +64,11 @@ public void cons() { assertEquals(new HCons<>(0, tuple6), tuple6.cons(0)); } + @Test + public void snoc() { + assertEquals(tuple(5L, "a", 7, "b", 11, "c", 13), tuple(5L, "a", 7, "b", 11, "c").snoc(13)); + } + @Test public void accessors() { assertEquals((Float) 2.0f, tuple6._1()); @@ -132,4 +137,10 @@ public void staticPure() { Tuple6 tuple = pureTuple(1, "2", '3', true, 5f).apply((byte) 6); assertEquals(tuple(1, "2", '3', true, 5f, (byte) 6), tuple); } + + @Test + public void init() { + assertEquals(tuple(1, 2, 3, 4, 5), + tuple(1, 2, 3, 4, 5, 6).init()); + } } \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple7Test.java b/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple7Test.java index 0e8a68aec..d5b13fd24 100644 --- a/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple7Test.java +++ b/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple7Test.java @@ -64,6 +64,11 @@ public void cons() { assertEquals(new HCons<>(0, tuple7), tuple7.cons(0)); } + @Test + public void snoc() { + assertEquals(tuple("b", 7L, "c", 11, "d", 13, "e", 'f'), tuple("b", 7L, "c", 11, "d", 13, "e").snoc('f')); + } + @Test public void accessors() { assertEquals((Byte) (byte) 127, tuple7._1()); @@ -136,4 +141,10 @@ public void staticPure() { pureTuple((byte) 1, (short) 2, 3, 4L, 5F, 6D).apply(true); assertEquals(tuple((byte) 1, (short) 2, 3, 4L, 5F, 6D, true), tuple); } + + @Test + public void init() { + assertEquals(tuple(1, 2, 3, 4, 5, 6), + tuple(1, 2, 3, 4, 5, 6, 7).init()); + } } \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple8Test.java b/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple8Test.java index 4d2a8cb76..ced5531b5 100644 --- a/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple8Test.java +++ b/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple8Test.java @@ -14,6 +14,8 @@ import testsupport.traits.MonadRecLaws; import testsupport.traits.TraversableLaws; +import java.time.LocalDate; + 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.hlist.HList.tuple; @@ -67,6 +69,15 @@ public void cons() { assertEquals(new HCons<>(0, tuple8), tuple8.cons(0)); } + @Test + public void snoc() { + LocalDate last = LocalDate.of(2020, 4, 14); + HCons> actual = + tuple("b", 7L, "c", 11, "d", 13, "e", 15L).snoc(last); + assertEquals("b", actual.head()); + assertEquals(actual.tail(), tuple(7L, "c", 11, "d", 13, "e", 15L, last)); + } + @Test public void accessors() { assertEquals((Short) (short) 65535, tuple8._1()); @@ -147,4 +158,10 @@ public void staticPure() { pureTuple((byte) 1, (short) 2, 3, 4L, 5F, 6D, true).apply('8'); assertEquals(tuple((byte) 1, (short) 2, 3, 4L, 5F, 6D, true, '8'), tuple); } + + @Test + public void init() { + assertEquals(tuple(1, 2, 3, 4, 5, 6, 7), + tuple(1, 2, 3, 4, 5, 6, 7, 8).init()); + } } \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/Fn1Test.java b/src/test/java/com/jnape/palatable/lambda/functions/Fn1Test.java index 39400c6b0..047aea1db 100644 --- a/src/test/java/com/jnape/palatable/lambda/functions/Fn1Test.java +++ b/src/test/java/com/jnape/palatable/lambda/functions/Fn1Test.java @@ -8,8 +8,8 @@ import testsupport.traits.Equivalence; import testsupport.traits.FunctorLaws; import testsupport.traits.MonadLaws; -import testsupport.traits.MonadRecLaws; import testsupport.traits.MonadReaderLaws; +import testsupport.traits.MonadRecLaws; import testsupport.traits.MonadWriterLaws; import java.util.function.Function; @@ -105,4 +105,9 @@ public void staticPure() { Fn1 fn1 = Fn1.pureFn1().apply(1); assertEquals((Integer) 1, fn1.apply("anything")); } + + @Test + public void withSelf() { + assertEquals((Integer) 15, Fn1.withSelf((f, x) -> x > 1 ? x + f.apply(x - 1) : x).apply(5)); + } } diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/$Test.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/$Test.java new file mode 100644 index 000000000..166e99f29 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/$Test.java @@ -0,0 +1,21 @@ +package com.jnape.palatable.lambda.functions.builtin.fn2; + +import org.junit.Test; + +import static com.jnape.palatable.lambda.functions.Fn2.fn2; +import static com.jnape.palatable.lambda.functions.builtin.fn2.$.$; +import static org.junit.Assert.assertEquals; + +public class $Test { + + @Test + public void application() { + assertEquals((Integer) 1, $(x -> x + 1, 0)); + assertEquals((Integer) 1, $.$(x -> x + 1).apply(0)); + } + + @Test + public void curryingInference() { + assertEquals((Integer) 1, $($(fn2(Integer::sum), 0), 1)); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/DropWhileTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/DropWhileTest.java index 83903f451..ff4551a11 100644 --- a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/DropWhileTest.java +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/DropWhileTest.java @@ -56,7 +56,13 @@ public void deforestingExecutesPredicatesInOrder() { innerInvocations.add(x); return x > 2; }, asList(1, 2, 3))).forEach(__ -> {}); - assertThat(innerInvocations, iterates(1, 2, 3)); - assertThat(outerInvocations, iterates(1, 2)); + assertThat(innerInvocations, iterates(1)); + assertThat(outerInvocations, iterates(1, 2, 3)); + } + + @Test + public void eachLayerIsAppliedOnce() { + assertThat(dropWhile(i -> i % 2 == 0, dropWhile(i -> i % 2 == 1, asList(1, 2, 3))), + iterates(3)); } } diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/FoldRightTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/FoldRightTest.java index 912ee9b4a..62f6d43c9 100644 --- a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/FoldRightTest.java +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/FoldRightTest.java @@ -29,7 +29,7 @@ public Fn1, Iterable> createTestSubject() { @Test public void foldRightAccumulatesRightToLeft() { - assertThat(foldRight((a, lazyB) -> lazyB.fmap(b -> explainFold().apply(a, b)), + assertThat(foldRight((a, lazyAcc) -> lazyAcc.fmap(acc -> explainFold().apply(a, acc)), lazy("5"), asList("1", "2", "3", "4")) .value(), diff --git a/src/test/java/com/jnape/palatable/lambda/internal/iteration/PredicatedDroppingIteratorTest.java b/src/test/java/com/jnape/palatable/lambda/internal/iteration/PredicatedDroppingIteratorTest.java index 7fb0613c3..44fcde9f1 100644 --- a/src/test/java/com/jnape/palatable/lambda/internal/iteration/PredicatedDroppingIteratorTest.java +++ b/src/test/java/com/jnape/palatable/lambda/internal/iteration/PredicatedDroppingIteratorTest.java @@ -1,15 +1,18 @@ package com.jnape.palatable.lambda.internal.iteration; import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.internal.ImmutableQueue; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; +import java.util.Collections; import java.util.Iterator; import java.util.NoSuchElementException; +import static java.util.Collections.singletonList; import static org.hamcrest.core.Is.is; import static org.junit.Assert.assertThat; import static testsupport.Mocking.mockIteratorToHaveValues; @@ -25,7 +28,7 @@ public class PredicatedDroppingIteratorTest { @Before public void setUp() { - predicatedDroppingIterator = new PredicatedDroppingIterator<>(EVEN, iterator); + predicatedDroppingIterator = new PredicatedDroppingIterator<>(ImmutableQueue.singleton(EVEN), iterator); } @Test diff --git a/src/test/java/com/jnape/palatable/lambda/internal/iteration/RewindableIteratorTest.java b/src/test/java/com/jnape/palatable/lambda/internal/iteration/RewindableIteratorTest.java index 33bda1ad4..f07fe55a5 100644 --- a/src/test/java/com/jnape/palatable/lambda/internal/iteration/RewindableIteratorTest.java +++ b/src/test/java/com/jnape/palatable/lambda/internal/iteration/RewindableIteratorTest.java @@ -10,6 +10,7 @@ import java.util.NoSuchElementException; import static org.hamcrest.core.Is.is; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThat; import static testsupport.Mocking.mockIteratorToHaveValues; @@ -51,13 +52,14 @@ public void cannotRewindIfNoValuesIterated() { rewindableIterator.rewind(); } - @Test(expected = NoSuchElementException.class) - public void cannotRewindTheSameElementTwice() { + @Test + public void canRewindTheSameElementTwice() { mockIteratorToHaveValues(iterator, 1, 2, 3); rewindableIterator.next(); rewindableIterator.rewind(); rewindableIterator.next(); rewindableIterator.rewind(); + assertEquals(1, rewindableIterator.next()); } @Test 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 00768b0c3..7c2997ca6 100644 --- a/src/test/java/com/jnape/palatable/lambda/monad/transformer/builtin/IterateTTest.java +++ b/src/test/java/com/jnape/palatable/lambda/monad/transformer/builtin/IterateTTest.java @@ -8,6 +8,7 @@ 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.lambda.monoid.Monoid; import com.jnape.palatable.traitor.annotations.TestTraits; import com.jnape.palatable.traitor.framework.Subjects; import com.jnape.palatable.traitor.runners.Traits; @@ -23,11 +24,13 @@ import java.util.Collection; import java.util.List; import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicInteger; 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.fn1.Constantly.constantly; 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; @@ -40,7 +43,12 @@ 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.iterateT; +import static com.jnape.palatable.lambda.monad.transformer.builtin.IterateT.liftIterateT; +import static com.jnape.palatable.lambda.monad.transformer.builtin.IterateT.of; +import static com.jnape.palatable.lambda.monad.transformer.builtin.IterateT.pureIterateT; import static com.jnape.palatable.lambda.monad.transformer.builtin.IterateT.singleton; +import static com.jnape.palatable.lambda.monad.transformer.builtin.IterateT.suspended; import static com.jnape.palatable.lambda.monad.transformer.builtin.IterateT.unfold; import static com.jnape.palatable.lambda.monoid.builtin.AddAll.addAll; import static com.jnape.palatable.lambda.monoid.builtin.Join.join; @@ -242,4 +250,103 @@ public void concatIsStackSafe() { assertEquals(new Identity<>(10_000), bigIterateT.fold((x, y) -> new Identity<>(x + y), new Identity<>(0))); } + + @Test + public void staticPure() { + assertEquals(new Identity<>(singletonList(1)), + pureIterateT(pureIdentity()) + ., Integer>>apply(1) + ., Identity>>toCollection(ArrayList::new)); + } + + @Test + public void staticLift() { + assertEquals(new Identity<>(singletonList(1)), + liftIterateT() + ., IterateT, Integer>>apply(new Identity<>(1)) + ., Identity>>toCollection(ArrayList::new)); + } + + @Test + public void trampolineMRecursesBreadth() { + IterateT, Integer> firstFour = of(new Identity<>(1), new Identity<>(2), new Identity<>(3), new Identity<>(4)); + IterateT, Integer> trampolined = firstFour + .trampolineM(x -> (x % 3 == 0 && (x < 30)) + ? of(new Identity<>(terminate(x + 10)), new Identity<>(recurse(x + 11)), new Identity<>(recurse(x + 12)), new Identity<>(recurse(x + 13))) + : singleton(new Identity<>(terminate(x)))); + assertThat(trampolined, iterates(1, 2, 13, 14, 25, 26, 37, 38, 39, 40, 28, 16, 4)); + } + + @Test + public void flatMapToEmptyStackSafety() { + assertEquals(new Identity<>(UNIT), + unfold(x -> new Identity<>(x <= STACK_EXPLODING_NUMBER ? just(tuple(x, x + 1)) : nothing()), + new Identity<>(1)) + .flatMap(constantly(iterateT(new Identity<>(nothing())))) + .forEach(constantly(new Identity<>(UNIT)))); + + assertEquals((Integer) 1_250_025_000, + unfold(x -> listen(x <= STACK_EXPLODING_NUMBER ? just(tuple(x, x + 1)) : nothing()), + Writer.listen(1)) + .flatMap(x -> iterateT(writer(tuple(nothing(), x)))) + .>forEach(constantly(listen(UNIT))) + .runWriter(Monoid.monoid(Integer::sum, 0)) + ._2()); + } + + @Test + public void flatMapCostsNoMoreEffortThanRequiredToYieldFirstValue() { + AtomicInteger flatMapCost = new AtomicInteger(0); + AtomicInteger unfoldCost = new AtomicInteger(0); + assertEquals(just(1), + unfold(x -> { + unfoldCost.incrementAndGet(); + return new Identity<>(x <= 10 ? just(tuple(x, x + 1)) : nothing()); + }, + new Identity<>(1)) + .flatMap(x -> { + flatMapCost.incrementAndGet(); + return singleton(new Identity<>(x)); + }) + ., Integer>>>>>runIterateT() + .runIdentity() + .fmap(Tuple2::_1)); + assertEquals(1, flatMapCost.get()); + assertEquals(1, unfoldCost.get()); + } + + @Test + public void runStep() { + assertEquals(new Identity<>(nothing()), + IterateT., Integer>empty(pureIdentity()) + ., IterateT, Integer>>>>>runStep()); + + Tuple2, IterateT, Integer>> singletonStep = + singleton(new Identity<>(1)) + ., IterateT, Integer>>>>>runStep() + .runIdentity() + .orElseThrow(AssertionError::new); + assertEquals(just(1), singletonStep._1()); + assertThat(singletonStep._2(), isEmpty()); + + Tuple2, IterateT, Integer>> emptySuspendedStep = + IterateT., Integer>suspended(() -> new Identity<>(nothing()), + pureIdentity()) + ., IterateT, Integer>>>>>runStep() + .runIdentity() + .orElseThrow(AssertionError::new); + + assertEquals(nothing(), emptySuspendedStep._1()); + assertThat(emptySuspendedStep._2(), isEmpty()); + + Tuple2, IterateT, Integer>> nonEmptySuspendedStep = + suspended(() -> new Identity<>(just(tuple(1, empty(pureIdentity())))), + pureIdentity()) + ., IterateT, Integer>>>>>runStep() + .runIdentity() + .orElseThrow(AssertionError::new); + + assertEquals(just(1), nonEmptySuspendedStep._1()); + assertThat(nonEmptySuspendedStep._2(), isEmpty()); + } } \ No newline at end of file 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 2c578fbc6..1a81e1c57 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,14 +22,18 @@ 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.adt.Unit.UNIT; 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; -import static com.jnape.palatable.lambda.monad.transformer.builtin.MaybeT.*; +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.traitor.framework.Subjects.subjects; import static org.junit.Assert.assertEquals; +import static testsupport.assertion.MonadErrorAssert.assertLaws; @RunWith(Traits.class) public class MaybeTTest { @@ -41,6 +45,13 @@ public class MaybeTTest { maybeT(left("foo"))); } + @Test + public void monadError() { + assertLaws(subjects(maybeT(new Identity<>(nothing())), maybeT(new Identity<>(just(1)))), + UNIT, + e -> maybeT(new Identity<>(just(2)))); + } + @Test public void lazyZip() { assertEquals(maybeT(right(just(2))), 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 6972c9fb6..983e7113a 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 @@ -2,6 +2,7 @@ import com.jnape.palatable.lambda.adt.Maybe; import com.jnape.palatable.lambda.adt.Unit; +import com.jnape.palatable.lambda.functions.Fn1; import com.jnape.palatable.lambda.functor.builtin.Identity; import com.jnape.palatable.lambda.io.IO; import com.jnape.palatable.traitor.annotations.TestTraits; @@ -12,6 +13,7 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicInteger; import static com.jnape.palatable.lambda.adt.Maybe.just; import static com.jnape.palatable.lambda.adt.Unit.UNIT; @@ -82,4 +84,24 @@ public void composedZip() { .unsafePerformAsyncIO(Executors.newFixedThreadPool(2)) .join(); } + + @Test + public void fmapInteractions() { + AtomicInteger invocations = new AtomicInteger(0); + ReaderT, Integer> readerT = readerT(i -> { + invocations.incrementAndGet(); + return new Identity<>(i); + }); + + Fn1 plusOne = x -> x + 1; + readerT.fmap(plusOne).fmap(plusOne).fmap(plusOne).runReaderT(0); + assertEquals(1, invocations.get()); + } + + @Test + public void askRetrievesInput() { + assertEquals(new Identity<>(1), + ReaderT.>ask(pureIdentity()) + .>runReaderT(1)); + } } \ 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 b51888c9d..785165952 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 @@ -16,15 +16,18 @@ import java.util.ArrayList; import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; import static com.jnape.palatable.lambda.adt.Maybe.just; 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.functor.builtin.Identity.pureIdentity; 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.hamcrest.MatcherAssert.assertThat; +import static org.junit.Assert.assertEquals; import static testsupport.matchers.StateTMatcher.whenEvaluated; import static testsupport.matchers.StateTMatcher.whenExecuted; import static testsupport.matchers.StateTMatcher.whenRun; @@ -119,4 +122,18 @@ public void staticLift() { assertThat(StateT.liftStateT().apply(new Identity<>(1)), whenRun("foo", new Identity<>(tuple(1, "foo")))); } + + @Test + public void fmapInteractions() { + AtomicInteger invocations = new AtomicInteger(0); + StateT., Integer>gets(x -> { + invocations.incrementAndGet(); + return new Identity<>(x); + }) + .fmap(id()) + .fmap(id()) + .fmap(id()) + .>>runStateT(0); + assertEquals(1, invocations.get()); + } } \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/monoid/MonoidTest.java b/src/test/java/com/jnape/palatable/lambda/monoid/MonoidTest.java index 87cfced3e..135d87195 100644 --- a/src/test/java/com/jnape/palatable/lambda/monoid/MonoidTest.java +++ b/src/test/java/com/jnape/palatable/lambda/monoid/MonoidTest.java @@ -1,6 +1,7 @@ package com.jnape.palatable.lambda.monoid; import com.jnape.palatable.lambda.adt.Maybe; +import com.jnape.palatable.lambda.functor.builtin.Lazy; import org.junit.Test; import java.util.List; @@ -10,6 +11,7 @@ import static com.jnape.palatable.lambda.monoid.Monoid.monoid; import static java.util.Arrays.asList; import static org.junit.Assert.assertEquals; +import static testsupport.functions.ExplainFold.explainFold; public class MonoidTest { @@ -25,6 +27,13 @@ public void reduceRight() { assertEquals((Integer) 6, sum.reduceRight(asList(1, 2, 3))); } + @Test + public void foldRight() { + Lazy lazyString = monoid(explainFold()::apply, "0") + .foldRight("4", asList("1", "2", "3")); + assertEquals("(1 + (2 + (3 + (4 + 0))))", lazyString.value()); + } + @Test public void foldMap() { Monoid sum = monoid(Integer::sum, 0); diff --git a/src/test/java/com/jnape/palatable/lambda/semigroup/SemigroupTest.java b/src/test/java/com/jnape/palatable/lambda/semigroup/SemigroupTest.java index 3291b6b1b..712c09e02 100644 --- a/src/test/java/com/jnape/palatable/lambda/semigroup/SemigroupTest.java +++ b/src/test/java/com/jnape/palatable/lambda/semigroup/SemigroupTest.java @@ -4,18 +4,19 @@ import static java.util.Arrays.asList; import static org.junit.Assert.assertEquals; +import static testsupport.functions.ExplainFold.explainFold; public class SemigroupTest { @Test public void foldLeft() { - Semigroup sum = (x, y) -> x + y; - assertEquals((Integer) 6, sum.foldLeft(0, asList(1, 2, 3))); + Semigroup foldFn = explainFold()::apply; + assertEquals("(((0 + 1) + 2) + 3)", foldFn.foldLeft("0", asList("1", "2", "3"))); } @Test public void foldRight() { - Semigroup sum = (x, y) -> x + y; - assertEquals((Integer) 6, sum.foldRight(0, asList(1, 2, 3)).value()); + Semigroup foldFn = explainFold()::apply; + assertEquals("(1 + (2 + (3 + 0)))", foldFn.foldRight("0", asList("1", "2", "3")).value()); } } \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/semigroup/builtin/AbsentTest.java b/src/test/java/com/jnape/palatable/lambda/semigroup/builtin/AbsentTest.java index 5ef25395e..9d3dc4524 100644 --- a/src/test/java/com/jnape/palatable/lambda/semigroup/builtin/AbsentTest.java +++ b/src/test/java/com/jnape/palatable/lambda/semigroup/builtin/AbsentTest.java @@ -1,10 +1,17 @@ package com.jnape.palatable.lambda.semigroup.builtin; +import com.jnape.palatable.lambda.adt.Maybe; +import com.jnape.palatable.lambda.adt.Unit; +import com.jnape.palatable.lambda.functions.builtin.fn1.Constantly; import com.jnape.palatable.lambda.semigroup.Semigroup; import org.junit.Test; +import java.util.Arrays; + import static com.jnape.palatable.lambda.adt.Maybe.just; import static com.jnape.palatable.lambda.adt.Maybe.nothing; +import static com.jnape.palatable.lambda.adt.Unit.UNIT; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Repeat.repeat; import static com.jnape.palatable.lambda.semigroup.builtin.Absent.absent; import static org.junit.Assert.assertEquals; @@ -12,12 +19,84 @@ public class AbsentTest { @Test public void semigroup() { + Semigroup addition = Integer::sum; + + assertEquals(just(3), absent(addition, just(1), just(2))); + assertEquals(nothing(), absent(addition, nothing(), just(1))); + assertEquals(nothing(), absent(addition, just(1), nothing())); + assertEquals(nothing(), absent(addition, nothing(), nothing())); + } + + @Test + public void foldRight() { + Absent absent = absent(); + Semigroup addition = Integer::sum; + + assertEquals(just(3), absent.apply(addition).foldRight(just(0), Arrays.asList(just(1), just(2))).value()); + assertEquals(nothing(), absent.apply(addition).foldRight(just(0), Arrays.asList(nothing(), just(1))).value()); + assertEquals(nothing(), absent.apply(addition).foldRight(just(0), Arrays.asList(just(1), nothing())).value()); + assertEquals(nothing(), absent.apply(addition).foldRight(just(0), Arrays.asList(nothing(), nothing())).value()); + } + + @Test + public void foldLeft() { Absent absent = absent(); Semigroup addition = Integer::sum; - assertEquals(just(3), absent.apply(addition, just(1), just(2))); - assertEquals(nothing(), absent.apply(addition, nothing(), just(1))); - assertEquals(nothing(), absent.apply(addition, just(1), nothing())); - assertEquals(nothing(), absent.apply(addition, nothing(), nothing())); + assertEquals(just(3), absent.apply(addition).foldLeft(just(0), Arrays.asList(just(1), just(2)))); + assertEquals(nothing(), absent.apply(addition).foldLeft(just(0), Arrays.asList(nothing(), just(1)))); + assertEquals(nothing(), absent.apply(addition).foldLeft(just(0), Arrays.asList(just(1), nothing()))); + assertEquals(nothing(), absent.apply(addition).foldLeft(just(0), Arrays.asList(nothing(), nothing()))); + } + + @Test(timeout = 200) + public void foldRightShortCircuit() { + Maybe result = Absent.absent(Constantly::constantly) + .foldRight(just(UNIT), repeat(nothing())).value(); + assertEquals(nothing(), result); + + result = Absent.absent(Constantly::constantly) + .foldRight(nothing(), repeat(just(UNIT))).value(); + assertEquals(nothing(), result); + } + + @Test(timeout = 200) + public void foldLeftShortCircuit() { + Maybe result = Absent.absent(Constantly::constantly) + .foldLeft(just(UNIT), repeat(nothing())); + assertEquals(nothing(), result); + + result = Absent.absent(Constantly::constantly) + .foldLeft(nothing(), repeat(just(UNIT))); + assertEquals(nothing(), result); + } + + @Test + public void foldLeftWorksForJusts() { + Maybe result = Absent.absent(Constantly::constantly) + .foldLeft(just(UNIT), Arrays.asList(just(UNIT), just(UNIT))); + assertEquals(just(UNIT), result); + } + + @Test(timeout = 200) + public void checkedApplyFoldRightShortCircuit() { + Maybe result = Absent.absent().checkedApply(Constantly::constantly) + .foldRight(just(UNIT), repeat(nothing())).value(); + assertEquals(nothing(), result); + + result = Absent.absent().checkedApply(Constantly::constantly) + .foldRight(nothing(), repeat(just(UNIT))).value(); + assertEquals(nothing(), result); + } + + @Test(timeout = 200) + public void checkedApplyFoldLeftShortCircuit() { + Maybe result = Absent.absent().checkedApply(Constantly::constantly) + .foldLeft(just(UNIT), repeat(nothing())); + assertEquals(nothing(), result); + + result = Absent.absent().checkedApply(Constantly::constantly) + .foldLeft(nothing(), repeat(just(UNIT))); + assertEquals(nothing(), result); } } \ No newline at end of file diff --git a/src/test/java/testsupport/functions/ExplainFold.java b/src/test/java/testsupport/functions/ExplainFold.java index d59a2378b..ba5c29527 100644 --- a/src/test/java/testsupport/functions/ExplainFold.java +++ b/src/test/java/testsupport/functions/ExplainFold.java @@ -7,6 +7,6 @@ public class ExplainFold { public static Fn2 explainFold() { - return (acc, x) -> format("(%s + %s)", acc, x); + return (x, y) -> format("(%s + %s)", x, y); } } diff --git a/src/test/java/testsupport/matchers/EitherMatcher.java b/src/test/java/testsupport/matchers/EitherMatcher.java index 748d01be2..b18f68af6 100644 --- a/src/test/java/testsupport/matchers/EitherMatcher.java +++ b/src/test/java/testsupport/matchers/EitherMatcher.java @@ -9,11 +9,13 @@ import static com.jnape.palatable.lambda.adt.Either.right; import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; import static com.jnape.palatable.lambda.io.IO.io; +import static org.hamcrest.CoreMatchers.anything; +import static org.hamcrest.CoreMatchers.equalTo; public final class EitherMatcher extends TypeSafeMatcher> { - private final Either, Matcher> matcher; + private final Either, Matcher> matcher; - private EitherMatcher(Either, Matcher> matcher) { + private EitherMatcher(Either, Matcher> matcher) { this.matcher = matcher; } @@ -44,11 +46,27 @@ public void describeTo(Description description) { .unsafePerformIO(); } - public static EitherMatcher isLeftThat(Matcher lMatcher) { + public static EitherMatcher isLeftThat(Matcher lMatcher) { return new EitherMatcher<>(left(lMatcher)); } - public static EitherMatcher isRightThat(Matcher rMatcher) { + public static EitherMatcher isLeft() { + return isLeftThat(anything()); + } + + public static EitherMatcher isLeftOf(L l) { + return isLeftThat(equalTo(l)); + } + + public static EitherMatcher isRightThat(Matcher rMatcher) { return new EitherMatcher<>(right(rMatcher)); } + + public static EitherMatcher isRight() { + return isRightThat(anything()); + } + + public static EitherMatcher isRightOf(R r) { + return isRightThat(equalTo(r)); + } }