From 186ee7eabb4063ef89485b01b56cb205394a5ab0 Mon Sep 17 00:00:00 2001 From: jnape Date: Tue, 16 Jan 2018 23:07:55 -0600 Subject: [PATCH 01/52] [maven-release-plugin] prepare for next development iteration --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 94b3a6847..a0c32680e 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ lambda - 2.1.1 + 2.1.2-SNAPSHOT jar Lambda From fb59df557b0ccb0b3879a41dceea1c0790a71d97 Mon Sep 17 00:00:00 2001 From: jnape Date: Tue, 16 Jan 2018 23:15:35 -0600 Subject: [PATCH 02/52] Updating README and CHANGELOG to new version --- CHANGELOG.md | 5 ++++- README.md | 4 ++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 60491f794..e5533a33e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/). ## [Unreleased] + +## [2.1.1] - 2018-01-16 ### Changed - ***Breaking Change***: Moved `Trampoline` and `RecursiveResult` to better package @@ -254,7 +256,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/). - `Monadic/Dyadic/TriadicFunction`, `Predicate`, `Tuple2`, `Tuple3` - `Functor`, `BiFunctor`, `ProFunctor` -[Unreleased]: https://github.com/palatable/lambda/compare/lambda-2.1.0...HEAD +[Unreleased]: https://github.com/palatable/lambda/compare/lambda-2.1.1...HEAD +[2.1.1]: https://github.com/palatable/lambda/compare/lambda-2.1.0...lambda-2.1.1 [2.1.0]: https://github.com/palatable/lambda/compare/lambda-2.0.0...lambda-2.1.0 [2.0.0]: https://github.com/palatable/lambda/compare/lambda-1.6.3...lambda-2.0.0 [1.6.3]: https://github.com/palatable/lambda/compare/lambda-1.6.2...lambda-1.6.3 diff --git a/README.md b/README.md index 4ff8f81e0..24a49ae3d 100644 --- a/README.md +++ b/README.md @@ -57,14 +57,14 @@ Add the following dependency to your: com.jnape.palatable lambda - 2.1.0 + 2.1.1 ``` `build.gradle` ([Gradle](https://docs.gradle.org/current/userguide/dependency_management.html)): ```gradle -compile group: 'com.jnape.palatable', name: 'lambda', version: '2.1.0' +compile group: 'com.jnape.palatable', name: 'lambda', version: '2.1.1' ``` Examples From 810773b03cf6a84ff09202b7ec5fa201bc746b21 Mon Sep 17 00:00:00 2001 From: jnape Date: Thu, 18 Jan 2018 14:15:58 -0600 Subject: [PATCH 03/52] Replacing explicit casts with #coerce calls --- src/main/java/com/jnape/palatable/lambda/adt/Maybe.java | 2 +- src/main/java/com/jnape/palatable/lambda/monad/Monad.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/jnape/palatable/lambda/adt/Maybe.java b/src/main/java/com/jnape/palatable/lambda/adt/Maybe.java index 9bfc7eed0..993a26936 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/Maybe.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/Maybe.java @@ -262,7 +262,7 @@ public Maybe flatMap(Function> f) { public Applicative, App> traverse( Function> fn, Function, ? extends Applicative, App>> pure) { - return (Applicative, App>) pure.apply(nothing()); + return pure.apply(nothing()).fmap(x -> (Maybe) x); } @Override diff --git a/src/main/java/com/jnape/palatable/lambda/monad/Monad.java b/src/main/java/com/jnape/palatable/lambda/monad/Monad.java index 770566cbf..180211979 100644 --- a/src/main/java/com/jnape/palatable/lambda/monad/Monad.java +++ b/src/main/java/com/jnape/palatable/lambda/monad/Monad.java @@ -63,7 +63,7 @@ default Monad zip(Applicative, M> app */ @Override default Monad discardL(Applicative appB) { - return (Monad) Applicative.super.discardL(appB); + return Applicative.super.discardL(appB).coerce(); } /** @@ -71,6 +71,6 @@ default Monad discardL(Applicative appB) { */ @Override default Monad discardR(Applicative appB) { - return (Monad) Applicative.super.discardR(appB); + return Applicative.super.discardR(appB).coerce(); } } \ No newline at end of file From f4e44826b3eb9a9319093e32db504227702e588f Mon Sep 17 00:00:00 2001 From: jnape Date: Mon, 22 Jan 2018 18:07:59 -0600 Subject: [PATCH 04/52] cleaner mapS and mapB implementations --- src/main/java/com/jnape/palatable/lambda/lens/Lens.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/jnape/palatable/lambda/lens/Lens.java b/src/main/java/com/jnape/palatable/lambda/lens/Lens.java index 7042b603d..fca2e37ec 100644 --- a/src/main/java/com/jnape/palatable/lambda/lens/Lens.java +++ b/src/main/java/com/jnape/palatable/lambda/lens/Lens.java @@ -9,7 +9,6 @@ import java.util.function.BiFunction; import java.util.function.Function; -import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; import static com.jnape.palatable.lambda.lens.Lens.Simple.adapt; import static com.jnape.palatable.lambda.lens.functions.Over.over; import static com.jnape.palatable.lambda.lens.functions.Set.set; @@ -214,7 +213,7 @@ default Lens contraMap(Function fn) { * @return the new lens */ default Lens mapS(Function fn) { - return compose(lens(fn, (r, t) -> t)); + return lens(view(this).compose(fn), (r, b) -> set(this, b, fn.apply(r))); } /** @@ -247,7 +246,7 @@ default Lens mapA(Function fn) { * @return the new lens */ default Lens mapB(Function fn) { - return andThen(lens(id(), (a, z) -> fn.apply(z))); + return lens(view(this), (s, z) -> set(this, fn.apply(z), s)); } /** From 5b9864c06f1d9c40f690e168e539d17ef4609b49 Mon Sep 17 00:00:00 2001 From: jnape Date: Sun, 28 Jan 2018 19:00:04 -0800 Subject: [PATCH 05/52] Adding BoundedBifunctor, Try, Unit, and CheckedRunnable --- CHANGELOG.md | 12 + .../jnape/palatable/lambda/adt/Either.java | 10 +- .../com/jnape/palatable/lambda/adt/Try.java | 302 ++++++++++++++++++ .../com/jnape/palatable/lambda/adt/Unit.java | 21 ++ .../lambda/functions/builtin/fn2/Peek2.java | 19 +- .../specialized/checked/CheckedFn1.java | 11 +- .../specialized/checked/CheckedRunnable.java | 25 ++ .../specialized/checked/CheckedSupplier.java | 15 +- .../specialized/checked/Runtime.java | 4 +- .../palatable/lambda/functor/Bifunctor.java | 2 +- .../lambda/functor/BoundedBifunctor.java | 58 ++++ .../palatable/lambda/adt/EitherTest.java | 18 -- .../jnape/palatable/lambda/adt/TryTest.java | 138 ++++++++ 13 files changed, 586 insertions(+), 49 deletions(-) create mode 100644 src/main/java/com/jnape/palatable/lambda/adt/Try.java create mode 100644 src/main/java/com/jnape/palatable/lambda/adt/Unit.java create mode 100644 src/main/java/com/jnape/palatable/lambda/functions/specialized/checked/CheckedRunnable.java create mode 100644 src/main/java/com/jnape/palatable/lambda/functor/BoundedBifunctor.java create mode 100644 src/test/java/com/jnape/palatable/lambda/adt/TryTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md index e5533a33e..f54cd6b2a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,18 @@ 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 +- `BoundedBifunctor`, a `Bifunctor` super type that offers upper bounds for both parameters +- `Try`, a `Monad` representing an expression-like analog of `try/catch/finally` +- `CheckedRunnable`, the `Runnable` counterpart to `CheckedSupplier` that can throw checked exceptions +- `Unit`, the lambda analog to `Void`, except actually inhabited by a singleton instance + +### Changed +- `Bifunctor` is now a `BoundedBifunctor` where both parameter upper bounds are `Object` +- `Peek2` now accepts the more general `BoundedBifunctor` + +### Deprecated +- `Either#trying` in favor of `Try#trying` ## [2.1.1] - 2018-01-16 ### Changed diff --git a/src/main/java/com/jnape/palatable/lambda/adt/Either.java b/src/main/java/com/jnape/palatable/lambda/adt/Either.java index b9fe4fe9b..89374cbc6 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/Either.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/Either.java @@ -286,15 +286,13 @@ public static Either fromMaybe(Maybe maybe, Supplier leftFn) * @param the left parameter type * @param the right parameter type * @return the supplier result as a right value, or leftFn's mapping result as a left value + * @deprecated in favor of {@link Try#trying(CheckedSupplier)} */ + @Deprecated @SuppressWarnings("unchecked") public static Either trying(CheckedSupplier supplier, Function leftFn) { - try { - return right(supplier.get()); - } catch (Exception e) { - return left(leftFn.apply((E) e)); - } + return Try.trying(supplier).toEither(leftFn).biMap(id(), id()); } /** @@ -305,7 +303,9 @@ public static Either trying(CheckedSupplier the left parameter type (the most contravariant exception that supplier might throw) * @param the right parameter type * @return the supplier result as a right value, or a left value of the thrown exception + * @deprecated in favor of {@link Try#trying(CheckedSupplier)} */ + @Deprecated public static Either trying(CheckedSupplier supplier) { return trying(supplier, id()); } diff --git a/src/main/java/com/jnape/palatable/lambda/adt/Try.java b/src/main/java/com/jnape/palatable/lambda/adt/Try.java new file mode 100644 index 000000000..255e25b7b --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/adt/Try.java @@ -0,0 +1,302 @@ +package com.jnape.palatable.lambda.adt; + +import com.jnape.palatable.lambda.adt.coproduct.CoProduct2; +import com.jnape.palatable.lambda.functions.specialized.checked.CheckedRunnable; +import com.jnape.palatable.lambda.functions.specialized.checked.CheckedSupplier; +import com.jnape.palatable.lambda.functor.Applicative; +import com.jnape.palatable.lambda.functor.BoundedBifunctor; +import com.jnape.palatable.lambda.monad.Monad; +import com.jnape.palatable.lambda.traversable.Traversable; + +import java.util.Objects; +import java.util.function.Function; + +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.Constantly.constantly; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Peek2.peek2; + +/** + * A {@link Monad} of the evaluation outcome of an expression that might throw. Try/catch/finally semantics map to + * trying/catching/ensuring, respectively. + * + * @param the {@link Throwable} type that may have been thrown by the expression + * @param the possibly successful expression result + * @see Either + */ +public abstract class Try implements Monad>, Traversable>, BoundedBifunctor>, CoProduct2> { + + private Try() { + } + + /** + * Catch any instance of throwableType and map it to a success value. + * + * @param throwableType the {@link Throwable} (sub)type to be caught + * @param recoveryFn the function mapping the {@link Throwable} to the result + * @param the {@link Throwable} (sub)type + * @return a new {@link Try} instance around either the original successful result or the mapped result + */ + @SuppressWarnings("unchecked") + public final Try catching(Class throwableType, Function recoveryFn) { + return catching(throwableType::isInstance, t -> recoveryFn.apply((S) t)); + } + + /** + * Catch any thrown T satisfying predicate and map it to a success value. + * + * @param predicate the predicate + * @param recoveryFn the function mapping the {@link Throwable} to the result + * @return a new {@link Try} instance around either the original successful result or the mapped result + */ + public final Try catching(Function predicate, + Function recoveryFn) { + return match(t -> predicate.apply(t) ? success(recoveryFn.apply(t)) : failure(t), Try::success); + } + + /** + * Run the provided runnable regardless of whether this is a success or a failure (the {@link Try} analog to + * finally. + *

+ * If the runnable runs successfully, the result is preserved as is. If the runnable itself throws, and the result + * was a success, the result becomes a failure over the newly-thrown {@link Throwable}. If the result was a failure + * over some {@link Throwable} t1, and the runnable throws a new {@link Throwable} t2, the + * result is a failure over t1 with t2 added to t1 as a suppressed exception. + * + * @param runnable the runnable block of code to execute + * @return the same {@link Try} instance if runnable completes successfully; otherwise, a {@link Try} conforming to + * rules above + */ + public final Try ensuring(CheckedRunnable runnable) { + return match(t -> peek2(t::addSuppressed, __ -> {}, trying(runnable)) + .biMapL(constantly(t)) + .flatMap(constantly(failure(t))), + a -> trying(runnable).fmap(constantly(a))); + } + + /** + * If this is a success, return the wrapped value. Otherwise, apply the {@link Throwable} to fn and + * return the result. + * + * @param fn the function mapping the potential {@link Throwable} T to A + * @return a success value + */ + public final A recover(Function fn) { + return match(fn, id()); + } + + /** + * If this is a failure, return the wrapped value. Otherwise, apply the success value to fn and return + * the result. + * + * @param fn the function mapping the potential A to T + * @return a failure value + */ + public final T forfeit(Function fn) { + return match(id(), fn); + } + + /** + * If this is a success, wrap the value in a {@link Maybe#just} and return it. Otherwise, return {@link + * Maybe#nothing()}. + * + * @return {@link Maybe} the success value + */ + public final Maybe toMaybe() { + return match(__ -> nothing(), Maybe::just); + } + + /** + * If this is a success, wrap the value in a {@link Either#right} and return it. Otherwise, return the {@link + * Throwable} in an {@link Either#left}. + * + * @return {@link Either} the success value or the {@link Throwable} + */ + public final Either toEither() { + return toEither(id()); + } + + /** + * If this is a success, wrap the value in a {@link Either#right} and return it. Otherwise, apply the mapping + * function to the failure {@link Throwable}, re-wrap it in an {@link Either#left}, and return it. + * + * @param fn the mapping function + * @param the {@link Either} left parameter type + * @return {@link Either} the success value or the mapped left value + */ + public final Either toEither(Function fn) { + return match(fn.andThen(Either::left), Either::right); + } + + @Override + public Try fmap(Function fn) { + return Monad.super.fmap(fn).coerce(); + } + + @Override + public Try flatMap(Function>> f) { + return match(Try::failure, a -> f.apply(a).coerce()); + } + + @Override + public Try pure(B b) { + return success(b); + } + + @Override + public Try zip(Applicative, Try> appFn) { + return Monad.super.zip(appFn).coerce(); + } + + @Override + public Try discardL(Applicative> appB) { + return Monad.super.discardL(appB).coerce(); + } + + @Override + public Try discardR(Applicative> appB) { + return Monad.super.discardR(appB).coerce(); + } + + @Override + @SuppressWarnings("unchecked") + public Applicative, App> traverse( + Function> fn, + Function>, ? extends Applicative>, App>> pure) { + return match(t -> pure.apply(failure(t)), + a -> fn.apply(a).fmap(Try::success)) + .fmap(x -> (Try) x); + } + + @Override + public Try biMap(Function lFn, + Function rFn) { + return match(t -> failure(lFn.apply(t)), a -> success(rFn.apply(a))); + } + + @Override + public Try biMapL(Function fn) { + return (Try) BoundedBifunctor.super.biMapL(fn); + } + + @Override + public Try biMapR(Function fn) { + return (Try) BoundedBifunctor.super.biMapR(fn); + } + + /** + * Static factory method for creating a success value. + * + * @param a the wrapped value + * @param the failure parameter type + * @param the success parameter type + * @return a success value of a + */ + public static Try success(A a) { + return new Success<>(a); + } + + /** + * Static factory method for creating a failure value. + * + * @param t the wrapped {@link Throwable} + * @param the failure parameter type + * @param the success parameter type + * @return a failure value of t + */ + public static Try failure(T t) { + return new Failure<>(t); + } + + /** + * Execute supplier, returning a success A or a failure of the thrown {@link Throwable}. + * + * @param supplier the supplier + * @param the possible {@link Throwable} type + * @param the possible success type + * @return a new {@link Try} around either a successful A result or the thrown {@link Throwable} + */ + @SuppressWarnings("unchecked") + public static Try trying(CheckedSupplier supplier) { + try { + return success(supplier.get()); + } catch (Throwable t) { + return failure((T) t); + } + } + + /** + * Execute runnable, returning a success {@link Unit} or a failure of the thrown {@link Throwable}. + * + * @param runnable the runnable + * @param the possible {@link Throwable} type + * @return a new {@link Try} around either a successful {@link Unit} result or the thrown {@link Throwable} + */ + public static Try trying(CheckedRunnable runnable) { + return trying(() -> { + runnable.run(); + return UNIT; + }); + } + + private static final class Failure extends Try { + private final T t; + + private Failure(T t) { + this.t = t; + } + + @Override + public R match(Function aFn, Function bFn) { + return aFn.apply(t); + } + + @Override + public boolean equals(Object other) { + return other instanceof Failure && Objects.equals(t, ((Failure) other).t); + } + + @Override + public int hashCode() { + return Objects.hash(t); + } + + @Override + public String toString() { + return "Failure{" + + "t=" + t + + '}'; + } + } + + private static final class Success extends Try { + private final A a; + + private Success(A a) { + this.a = a; + } + + @Override + public R match(Function aFn, Function bFn) { + return bFn.apply(a); + } + + @Override + public boolean equals(Object other) { + return other instanceof Success && Objects.equals(a, ((Success) other).a); + } + + @Override + public int hashCode() { + return Objects.hash(a); + } + + @Override + public String toString() { + return "Success{" + + "a=" + a + + '}'; + } + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/adt/Unit.java b/src/main/java/com/jnape/palatable/lambda/adt/Unit.java new file mode 100644 index 000000000..0373cdac7 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/adt/Unit.java @@ -0,0 +1,21 @@ +package com.jnape.palatable.lambda.adt; + +/** + * The empty return type. Unlike {@link Void}, this type is actually inhabited by a singleton instance that can be used, + * rather than having to resort to null. + */ +public final class Unit { + + /** + * The singleton instance. + */ + public static final Unit UNIT = new Unit(); + + private Unit() { + } + + @Override + public String toString() { + return "Unit{}"; + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Peek2.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Peek2.java index 83061c7c6..d544ac7d7 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Peek2.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Peek2.java @@ -4,6 +4,7 @@ import com.jnape.palatable.lambda.functions.Fn2; import com.jnape.palatable.lambda.functions.Fn3; import com.jnape.palatable.lambda.functor.Bifunctor; +import com.jnape.palatable.lambda.functor.BoundedBifunctor; import java.util.function.Consumer; import java.util.function.Function; @@ -17,7 +18,7 @@ * @param the bifunctor's second parameter type * @param the bifunctor type */ -public final class Peek2> implements Fn3, Consumer, FAB, FAB> { +public final class Peek2> implements Fn3, Consumer, FAB, FAB> { private static final Peek2 INSTANCE = new Peek2<>(); private Peek2() { @@ -36,23 +37,25 @@ public FAB apply(Consumer aConsumer, Consumer bConsumer, F } @SuppressWarnings("unchecked") - public static > Peek2 peek2() { + public static > Peek2 peek2() { return INSTANCE; } - public static > Fn2, FAB, FAB> peek2( + public static > Fn2, FAB, FAB> peek2( Consumer aConsumer) { return Peek2.peek2().apply(aConsumer); } - public static > Fn1 peek2(Consumer aConsumer, - Consumer bConsumer) { + public static > Fn1 peek2( + Consumer aConsumer, + Consumer bConsumer) { return Peek2.peek2(aConsumer).apply(bConsumer); } - public static > FAB peek2(Consumer aConsumer, - Consumer bConsumer, - FAB fab) { + public static > FAB peek2( + Consumer aConsumer, + Consumer bConsumer, + FAB fab) { return Peek2.peek2(aConsumer, bConsumer).apply(fab); } } diff --git a/src/main/java/com/jnape/palatable/lambda/functions/specialized/checked/CheckedFn1.java b/src/main/java/com/jnape/palatable/lambda/functions/specialized/checked/CheckedFn1.java index f91b83aa4..fb9710082 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/specialized/checked/CheckedFn1.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/specialized/checked/CheckedFn1.java @@ -5,11 +5,13 @@ import static com.jnape.palatable.lambda.functions.specialized.checked.Runtime.throwChecked; /** - * Specialized {@link Fn1} that can throw checked exceptions. + * Specialized {@link Fn1} that can throw any {@link Throwable}. * + * @param The {@link Throwable} type * @param The input type * @param The output type * @see CheckedSupplier + * @see CheckedRunnable * @see Fn1 */ @FunctionalInterface @@ -24,12 +26,5 @@ default B apply(A a) { } } - /** - * A version of {@link Fn1} that can throw checked exceptions. - * - * @param a the argument - * @return the result of the function application - * @throws T any Throwable thrown by the function application - */ B checkedApply(A a) throws T; } diff --git a/src/main/java/com/jnape/palatable/lambda/functions/specialized/checked/CheckedRunnable.java b/src/main/java/com/jnape/palatable/lambda/functions/specialized/checked/CheckedRunnable.java new file mode 100644 index 000000000..f8fb8734e --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/specialized/checked/CheckedRunnable.java @@ -0,0 +1,25 @@ +package com.jnape.palatable.lambda.functions.specialized.checked; + +import static com.jnape.palatable.lambda.functions.specialized.checked.Runtime.throwChecked; + +/** + * Specialized {@link Runnable} that can throw any {@link Throwable}. + * + * @param The {@link Throwable} type + * @see CheckedSupplier + * @see CheckedFn1 + */ +@FunctionalInterface +public interface CheckedRunnable extends Runnable { + + @Override + default void run() { + try { + checkedRun(); + } catch (Throwable t) { + throw throwChecked(t); + } + } + + void checkedRun() throws T; +} diff --git a/src/main/java/com/jnape/palatable/lambda/functions/specialized/checked/CheckedSupplier.java b/src/main/java/com/jnape/palatable/lambda/functions/specialized/checked/CheckedSupplier.java index dd38fc165..832bea3bc 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/specialized/checked/CheckedSupplier.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/specialized/checked/CheckedSupplier.java @@ -5,17 +5,18 @@ import static com.jnape.palatable.lambda.functions.specialized.checked.Runtime.throwChecked; /** - * Specialized {@link Supplier} that can throw checked exceptions. + * Specialized {@link Supplier} that can throw any {@link Throwable}. * - * @param The exception type - * @param The return type + * @param The {@link Throwable} type + * @param The return type * @see CheckedFn1 + * @see CheckedRunnable */ @FunctionalInterface -public interface CheckedSupplier extends Supplier { +public interface CheckedSupplier extends Supplier { @Override - default T get() { + default A get() { try { return checkedGet(); } catch (Throwable t) { @@ -27,7 +28,7 @@ default T get() { * A version of {@link Supplier#get()} that can throw checked exceptions. * * @return the supplied result - * @throws E any exception that can be thrown by this method + * @throws T any exception that can be thrown by this method */ - T checkedGet() throws E; + A checkedGet() throws T; } diff --git a/src/main/java/com/jnape/palatable/lambda/functions/specialized/checked/Runtime.java b/src/main/java/com/jnape/palatable/lambda/functions/specialized/checked/Runtime.java index 810528676..1546e811b 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/specialized/checked/Runtime.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/specialized/checked/Runtime.java @@ -3,7 +3,7 @@ class Runtime { @SuppressWarnings("unchecked") - public static RuntimeException throwChecked(Throwable ex) throws T { - throw (T) ex; + public static RuntimeException throwChecked(Throwable t) throws T { + throw (T) t; } } diff --git a/src/main/java/com/jnape/palatable/lambda/functor/Bifunctor.java b/src/main/java/com/jnape/palatable/lambda/functor/Bifunctor.java index b024bf37b..695c71a8f 100644 --- a/src/main/java/com/jnape/palatable/lambda/functor/Bifunctor.java +++ b/src/main/java/com/jnape/palatable/lambda/functor/Bifunctor.java @@ -17,7 +17,7 @@ * @see com.jnape.palatable.lambda.adt.hlist.Tuple2 */ @FunctionalInterface -public interface Bifunctor { +public interface Bifunctor extends BoundedBifunctor { /** * Covariantly map over the left parameter. diff --git a/src/main/java/com/jnape/palatable/lambda/functor/BoundedBifunctor.java b/src/main/java/com/jnape/palatable/lambda/functor/BoundedBifunctor.java new file mode 100644 index 000000000..02fc7775f --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functor/BoundedBifunctor.java @@ -0,0 +1,58 @@ +package com.jnape.palatable.lambda.functor; + +import java.util.function.Function; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; + +/** + * A {@link Bifunctor} that has both parameter types upper bounded; that is, neither parameters can be mapped to a value + * that is not covariant to their respective upper bounds + * + * @param The type of the left parameter + * @param The type of the right parameter + * @param The type of the left parameter upper type bound + * @param The type of the right parameter upper type bound + * @param The unification parameter + * @see Bifunctor + */ +@FunctionalInterface +public interface BoundedBifunctor { + + /** + * Covariantly map the left parameter into a value that is covariant to ContraA. + * + * @param fn the mapping function + * @param the new left parameter type + * @return a bifunctor of C (the new parameter type) and B (the same right parameter) + */ + default BoundedBifunctor biMapL( + Function fn) { + return biMap(fn, id()); + } + + /** + * Covariantly map the right parameter into a value that is covariant to ContraB. + * + * @param fn the mapping function + * @param the new right parameter type + * @return a bifunctor of A (the same left parameter) and C (the new parameter type) + */ + default BoundedBifunctor biMapR( + Function fn) { + return biMap(id(), fn); + } + + /** + * Dually covariantly map both the left and right parameters into values that are covariant to ContraA + * and ContraB, respectively. This is isomorphic to biMapL(lFn).biMapR(rFn). + * + * @param the new left parameter type + * @param the new right parameter type + * @param lFn the left parameter mapping function + * @param rFn the right parameter mapping function + * @return a bifunctor over C (the new left parameter type) and D (the new right parameter type) + */ + BoundedBifunctor biMap( + Function lFn, + Function rFn); +} diff --git a/src/test/java/com/jnape/palatable/lambda/adt/EitherTest.java b/src/test/java/com/jnape/palatable/lambda/adt/EitherTest.java index 407197a8d..c1af81677 100644 --- a/src/test/java/com/jnape/palatable/lambda/adt/EitherTest.java +++ b/src/test/java/com/jnape/palatable/lambda/adt/EitherTest.java @@ -168,24 +168,6 @@ public void fromMaybeDoesNotEvaluateLeftFnForRight() { assertThat(atomicInteger.get(), is(0)); } - @Test - public void monadicTryingLiftsCheckedSupplier() { - assertEquals(right(1), Either.trying(() -> 1)); - - Exception checkedException = new Exception("expected"); - assertEquals(left(checkedException), Either.trying(() -> { - throw checkedException; - })); - } - - @Test - public void dyadicTryingLiftsCheckedSupplierMappingAnyThrownExceptions() { - assertEquals(right(1), Either.trying(() -> 1, Throwable::getMessage)); - assertEquals(left("expected"), Either.trying(() -> { - throw new Exception("expected"); - }, Throwable::getMessage)); - } - @Test public void monadicPeekLiftsIOToTheRight() { Either left = left("foo"); diff --git a/src/test/java/com/jnape/palatable/lambda/adt/TryTest.java b/src/test/java/com/jnape/palatable/lambda/adt/TryTest.java new file mode 100644 index 000000000..ba874efe6 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/adt/TryTest.java @@ -0,0 +1,138 @@ +package com.jnape.palatable.lambda.adt; + +import com.jnape.palatable.traitor.annotations.TestTraits; +import com.jnape.palatable.traitor.framework.Subjects; +import com.jnape.palatable.traitor.runners.Traits; +import org.junit.Test; +import org.junit.runner.RunWith; +import testsupport.traits.ApplicativeLaws; +import testsupport.traits.FunctorLaws; +import testsupport.traits.MonadLaws; +import testsupport.traits.TraversableLaws; + +import java.util.concurrent.atomic.AtomicInteger; + +import static com.jnape.palatable.lambda.adt.Either.left; +import static com.jnape.palatable.lambda.adt.Either.right; +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.adt.Maybe.nothing; +import static com.jnape.palatable.lambda.adt.Try.failure; +import static com.jnape.palatable.lambda.adt.Try.success; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; +import static com.jnape.palatable.traitor.framework.Subjects.subjects; +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; +import static testsupport.matchers.LeftMatcher.isLeftThat; + +@RunWith(Traits.class) +public class TryTest { + + @TestTraits({FunctorLaws.class, ApplicativeLaws.class, MonadLaws.class, TraversableLaws.class}) + public Subjects> testSubject() { + return subjects(failure(new IllegalStateException()), success(1)); + } + + @Test + public void catchingWithGenericPredicate() { + Try caught = Try.failure(new RuntimeException()) + .catching(__ -> false, r -> "caught first") + .catching(__ -> true, r -> "caught second"); + + assertEquals(success("caught second"), caught); + } + + @Test + public void catchingIsANoOpForSuccess() { + Try caught = Try.success("success") + .catching(__ -> true, __ -> "caught"); + + assertEquals(success("success"), caught); + } + + @Test + public void firstMatchingCatchBlockWins() { + Try caught = Try.failure(new IllegalStateException()) + .catching(__ -> true, __ -> "first") + .catching(__ -> true, __ -> "second"); + + assertEquals(success("first"), caught); + } + + @Test + public void catchBasedOnExceptionType() { + Try caught = Try.failure(new IllegalStateException()) + .catching(IllegalArgumentException.class, __ -> "illegal argument exception") + .catching(IllegalStateException.class, __ -> "illegal state exception") + .catching(RuntimeException.class, __ -> "runtime exception"); + + assertEquals(success("illegal state exception"), caught); + } + + @Test + public void ensureIfSuccess() { + AtomicInteger invocations = new AtomicInteger(0); + Try.success(1).ensuring((invocations::incrementAndGet)); + assertEquals(1, invocations.get()); + } + + @Test + public void ensureIfFailure() { + AtomicInteger invocations = new AtomicInteger(0); + Try.failure(new IllegalStateException()).ensuring((invocations::incrementAndGet)); + assertEquals(1, invocations.get()); + } + + @Test + public void exceptionThrownInEnsuringBlockIsCaught() { + IllegalStateException expected = new IllegalStateException(); + assertEquals(Try.failure(expected), Try.success(1).ensuring(() -> {throw expected;})); + + Either actual = Try.failure(new IllegalArgumentException()) + .ensuring(() -> { throw expected;}) + .toEither(); + assertThat(actual, isLeftThat(instanceOf(IllegalArgumentException.class))); + assertEquals(left(expected), actual.biMapL(t -> t.getSuppressed()[0])); + } + + @Test + public void forfeitEnsuresFailure() { + IllegalStateException expected = new IllegalStateException(); + assertEquals(expected, Try.failure(expected).forfeit(__ -> new IllegalArgumentException())); + assertEquals(expected, Try.success(1).forfeit(__ -> expected)); + } + + @Test + public void recoverEnsuresSuccess() { + assertEquals((Integer) 1, Try.success(1).recover(constantly(1))); + assertEquals((Integer) 1, Try.failure(new IllegalArgumentException()).recover(constantly(1))); + } + + @Test + public void toMaybe() { + assertEquals(just("foo"), Try.success("foo").toMaybe()); + assertEquals(nothing(), Try.failure(new IllegalStateException()).toMaybe()); + } + + @Test + public void toEither() { + assertEquals(right("foo"), Try.success("foo").toEither()); + + IllegalStateException exception = new IllegalStateException(); + assertEquals(left(exception), Try.failure(exception).toEither()); + } + + @Test + public void toEitherWithLeftMappingFunction() { + assertEquals(right(1), Try.success(1).toEither(__ -> "fail")); + assertEquals(left("fail"), Try.failure(new IllegalStateException("fail")).toEither(Throwable::getMessage)); + } + + @Test + public void tryingCatchesAnyThrowableThrownDuringEvaluation() { + IllegalStateException expected = new IllegalStateException(); + assertEquals(failure(expected), Try.trying(() -> {throw expected;})); + + assertEquals(success("foo"), Try.trying(() -> "foo")); + } +} \ No newline at end of file From cc746cae9a26eddace184fa602f21e8c01804b44 Mon Sep 17 00:00:00 2001 From: jnape Date: Sun, 28 Jan 2018 19:23:42 -0800 Subject: [PATCH 06/52] Adding Kleisli arrows --- CHANGELOG.md | 2 + .../lambda/functions/specialized/Kleisli.java | 90 +++++++++++++++++++ .../lambda/functor/builtin/Compose.java | 7 ++ .../lambda/functor/builtin/Const.java | 29 +++--- .../lambda/functor/builtin/Identity.java | 7 ++ .../functions/specialized/KleisliTest.java | 26 ++++++ 6 files changed, 150 insertions(+), 11 deletions(-) create mode 100644 src/main/java/com/jnape/palatable/lambda/functions/specialized/Kleisli.java create mode 100644 src/test/java/com/jnape/palatable/lambda/functions/specialized/KleisliTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md index f54cd6b2a..d95e43f0e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,10 +9,12 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/). - `Try`, a `Monad` representing an expression-like analog of `try/catch/finally` - `CheckedRunnable`, the `Runnable` counterpart to `CheckedSupplier` that can throw checked exceptions - `Unit`, the lambda analog to `Void`, except actually inhabited by a singleton instance +- `Kleisli`, the abstract representation of a `Kleisli` arrow (`Monad#flatMap`) as an `Fn1` ### Changed - `Bifunctor` is now a `BoundedBifunctor` where both parameter upper bounds are `Object` - `Peek2` now accepts the more general `BoundedBifunctor` +- `Identity`, `Compose`, and `Const` functors all have better `toString` implementations ### Deprecated - `Either#trying` in favor of `Try#trying` diff --git a/src/main/java/com/jnape/palatable/lambda/functions/specialized/Kleisli.java b/src/main/java/com/jnape/palatable/lambda/functions/specialized/Kleisli.java new file mode 100644 index 000000000..433ef410f --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/specialized/Kleisli.java @@ -0,0 +1,90 @@ +package com.jnape.palatable.lambda.functions.specialized; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functor.Applicative; +import com.jnape.palatable.lambda.monad.Monad; + +import java.util.function.Function; + +/** + * The Kleisli arrow of a {@link Monad}, manifest as simply an {@link Fn1}<A, MB>. This can be + * thought of as a fixed, portable {@link Monad#flatMap(Function)}. + * + * @param the input argument type + * @param the {@link Monad} unification parameter + * @param the output {@link Monad} type + */ +@FunctionalInterface +public interface Kleisli> extends Fn1 { + + /** + * Left-to-right composition of two compatible {@link Kleisli} arrows, yielding a new {@link Kleisli} arrow. + * + * @param after the arrow to execute after this one + * @param the new return parameter type + * @param the {@link Monad} instance to return + * @return the composition of the two arrows as a new {@link Kleisli} arrow + */ + default > Kleisli andThen(Kleisli after) { + return a -> apply(a).flatMap(after).coerce(); + } + + /** + * Right-to-left composition of two compatible {@link Kleisli} arrows, yielding a new {@link Kleisli} arrow. + * + * @param before the arrow to execute before this one + * @param the new input argument type + * @param the {@link Monad} instance to flatMap with this arrow + * @return the composition of the two arrows as a new {@link Kleisli} arrow + */ + default > Kleisli compose(Kleisli before) { + return z -> before.apply(z).flatMap(this).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + default Kleisli compose(Function before) { + return Fn1.super.compose(before)::apply; + } + + /** + * {@inheritDoc} + */ + @Override + default Kleisli discardR(Applicative> appB) { + return Fn1.super.discardR(appB)::apply; + } + + /** + * {@inheritDoc} + */ + @Override + default Kleisli contraMap(Function fn) { + return Fn1.super.contraMap(fn)::apply; + } + + /** + * {@inheritDoc} + */ + @Override + default Kleisli diMapL(Function fn) { + return Fn1.super.diMapL(fn)::apply; + } + + /** + * Adapt a compatible function into a {@link Kleisli} arrow. + * + * @param fn the function + * @param the input argument type + * @param the output parameter type + * @param the {@link Monad} unification parameter + * @param the returned {@link Monad} instance + * @return the function adapted as a {@link Kleisli} arrow + */ + static > Kleisli kleisli( + Function fn) { + return fn::apply; + } +} \ No newline at end of file diff --git a/src/main/java/com/jnape/palatable/lambda/functor/builtin/Compose.java b/src/main/java/com/jnape/palatable/lambda/functor/builtin/Compose.java index a8b260bcf..568988a9d 100644 --- a/src/main/java/com/jnape/palatable/lambda/functor/builtin/Compose.java +++ b/src/main/java/com/jnape/palatable/lambda/functor/builtin/Compose.java @@ -59,4 +59,11 @@ public boolean equals(Object other) { public int hashCode() { return Objects.hash(fga); } + + @Override + public String toString() { + return "Compose{" + + "fga=" + fga + + '}'; + } } diff --git a/src/main/java/com/jnape/palatable/lambda/functor/builtin/Const.java b/src/main/java/com/jnape/palatable/lambda/functor/builtin/Const.java index 5eea967fb..807aab0e5 100644 --- a/src/main/java/com/jnape/palatable/lambda/functor/builtin/Const.java +++ b/src/main/java/com/jnape/palatable/lambda/functor/builtin/Const.java @@ -1,8 +1,8 @@ package com.jnape.palatable.lambda.functor.builtin; -import com.jnape.palatable.lambda.monad.Monad; import com.jnape.palatable.lambda.functor.Applicative; import com.jnape.palatable.lambda.functor.Bifunctor; +import com.jnape.palatable.lambda.monad.Monad; import com.jnape.palatable.lambda.traversable.Traversable; import java.util.Objects; @@ -34,16 +34,6 @@ public A runConst() { return a; } - @Override - public boolean equals(Object other) { - return other instanceof Const && Objects.equals(a, ((Const) other).a); - } - - @Override - public int hashCode() { - return Objects.hash(a); - } - /** * Map over the right parameter. Note that because B is never actually known quantity outside of a type * signature, this is effectively a no-op that serves only to alter Const's type signature. @@ -131,4 +121,21 @@ public Const biMap(Function lFn, Function rFn) { return new Const<>(lFn.apply(a)); } + + @Override + public boolean equals(Object other) { + return other instanceof Const && Objects.equals(a, ((Const) other).a); + } + + @Override + public int hashCode() { + return Objects.hash(a); + } + + @Override + public String toString() { + return "Const{" + + "a=" + a + + '}'; + } } diff --git a/src/main/java/com/jnape/palatable/lambda/functor/builtin/Identity.java b/src/main/java/com/jnape/palatable/lambda/functor/builtin/Identity.java index def56eea0..44a58a929 100644 --- a/src/main/java/com/jnape/palatable/lambda/functor/builtin/Identity.java +++ b/src/main/java/com/jnape/palatable/lambda/functor/builtin/Identity.java @@ -81,4 +81,11 @@ public boolean equals(Object other) { public int hashCode() { return Objects.hash(a); } + + @Override + public String toString() { + return "Identity{" + + "a=" + a + + '}'; + } } diff --git a/src/test/java/com/jnape/palatable/lambda/functions/specialized/KleisliTest.java b/src/test/java/com/jnape/palatable/lambda/functions/specialized/KleisliTest.java new file mode 100644 index 000000000..bd40b2dca --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/specialized/KleisliTest.java @@ -0,0 +1,26 @@ +package com.jnape.palatable.lambda.functions.specialized; + +import com.jnape.palatable.lambda.functor.builtin.Identity; +import org.junit.Test; + +import static com.jnape.palatable.lambda.functions.specialized.Kleisli.kleisli; +import static java.lang.Integer.parseInt; +import static org.junit.Assert.assertEquals; + +public class KleisliTest { + + private static final Kleisli> G = kleisli(i -> new Identity<>(i.toString())); + private static final Kleisli> F = kleisli(s -> new Identity<>(parseInt(s))); + + @Test + public void leftToRightComposition() { + assertEquals(new Identity<>(1), G.andThen(F).apply(1)); + assertEquals(new Identity<>("1"), F.andThen(G).apply("1")); + } + + @Test + public void rightToLeftComposition() { + assertEquals(new Identity<>("1"), G.compose(F).apply("1")); + assertEquals(new Identity<>(1), F.compose(G).apply(1)); + } +} \ No newline at end of file From 8d5fbf4949f3640713e282833c159bcec8f3d7de Mon Sep 17 00:00:00 2001 From: jnape Date: Sun, 28 Jan 2018 20:08:38 -0800 Subject: [PATCH 07/52] These, the coproduct of A, B, or Tuple2 --- CHANGELOG.md | 1 + .../com/jnape/palatable/lambda/adt/These.java | 243 ++++++++++++++++++ .../jnape/palatable/lambda/adt/TheseTest.java | 25 ++ 3 files changed, 269 insertions(+) create mode 100644 src/main/java/com/jnape/palatable/lambda/adt/These.java create mode 100644 src/test/java/com/jnape/palatable/lambda/adt/TheseTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md index d95e43f0e..d65ee612e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/). - `CheckedRunnable`, the `Runnable` counterpart to `CheckedSupplier` that can throw checked exceptions - `Unit`, the lambda analog to `Void`, except actually inhabited by a singleton instance - `Kleisli`, the abstract representation of a `Kleisli` arrow (`Monad#flatMap`) as an `Fn1` +- `These`, a `CoProduct3` of `A`, `B`, or `Tuple2` ### Changed - `Bifunctor` is now a `BoundedBifunctor` where both parameter upper bounds are `Object` diff --git a/src/main/java/com/jnape/palatable/lambda/adt/These.java b/src/main/java/com/jnape/palatable/lambda/adt/These.java new file mode 100644 index 000000000..c8927b103 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/adt/These.java @@ -0,0 +1,243 @@ +package com.jnape.palatable.lambda.adt; + +import com.jnape.palatable.lambda.adt.coproduct.CoProduct2; +import com.jnape.palatable.lambda.adt.coproduct.CoProduct3; +import com.jnape.palatable.lambda.adt.hlist.Tuple2; +import com.jnape.palatable.lambda.functor.Applicative; +import com.jnape.palatable.lambda.functor.Bifunctor; +import com.jnape.palatable.lambda.monad.Monad; +import com.jnape.palatable.lambda.traversable.Traversable; + +import java.util.Objects; +import java.util.function.Function; + +import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Into.into; + +/** + * The coproduct of a coproduct ({@link CoProduct2}<A, B>) and its product ({@link + * Tuple2}<A, B>), represented as a {@link CoProduct3}<A, B, {@link Tuple2}<A, + * B>>. + * + * @param the first possible type + * @param the second possible type + */ +public abstract class These implements CoProduct3, These>, Monad>, Bifunctor>, Traversable> { + + private These() { + } + + /** + * {@inheritDoc} + */ + @Override + public final These biMap(Function lFn, + Function rFn) { + return match(a -> a(lFn.apply(a)), b -> b(rFn.apply(b)), into((a, b) -> both(lFn.apply(a), rFn.apply(b)))); + } + + /** + * {@inheritDoc} + */ + @Override + public final These flatMap(Function>> f) { + return match(These::a, b -> f.apply(b).coerce(), into((a, b) -> f.apply(b).>coerce().biMapL(constantly(a)))); + } + + /** + * {@inheritDoc} + */ + @Override + public final These pure(C c) { + return match(a -> both(a, c), b -> b(c), into((a, b) -> both(a, c))); + } + + /** + * {@inheritDoc} + */ + @Override + public Applicative, App> traverse( + Function> fn, + Function>, ? extends Applicative>, App>> pure) { + return match(a -> pure.apply(a(a)).fmap(x -> (These) x), + b -> fn.apply(b).fmap(this::pure), + into((a, b) -> fn.apply(b).fmap(c -> both(a, c)))); + } + + /** + * {@inheritDoc} + */ + @Override + @SuppressWarnings("unchecked") + public final These biMapL(Function fn) { + return (These) Bifunctor.super.biMapL(fn); + } + + /** + * {@inheritDoc} + */ + @Override + @SuppressWarnings("unchecked") + public final These biMapR(Function fn) { + return (These) Bifunctor.super.biMapR(fn); + } + + /** + * {@inheritDoc} + */ + @Override + public final These fmap(Function fn) { + return Monad.super.fmap(fn).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public final These zip(Applicative, These> appFn) { + return Monad.super.zip(appFn).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public final These discardL(Applicative> appB) { + return Monad.super.discardL(appB).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public final These discardR(Applicative> appB) { + return Monad.super.discardR(appB).coerce(); + } + + /** + * Static factory method for wrapping a value of type A in a {@link These}. + * + * @param a the value + * @param the first possible type + * @param the second possible type + * @return the wrapped value as a {@link These}<A,B> + */ + public static These a(A a) { + return new _A<>(a); + } + + /** + * Static factory method for wrapping a value of type B in a {@link These}. + * + * @param b the value + * @param the first possible type + * @param the second possible type + * @return the wrapped value as a {@link These}<A,B> + */ + public static These b(B b) { + return new _B<>(b); + } + + /** + * Static factory method for wrapping a value of type A and a value of type B in a {@link + * These}. + * + * @param a the first value + * @param b the second value + * @param the first possible type + * @param the second possible type + * @return the wrapped values as a {@link These}<A,B> + */ + public static These both(A a, B b) { + return new Both<>(tuple(a, b)); + } + + private static final class _A extends These { + + private final A a; + + private _A(A a) { + this.a = a; + } + + @Override + public R match(Function aFn, Function bFn, + Function, ? extends R> cFn) { + return aFn.apply(a); + } + + @Override + public boolean equals(Object other) { + return other instanceof These._A && Objects.equals(a, ((_A) other).a); + } + + @Override + public int hashCode() { + return Objects.hash(a); + } + + @Override + public String toString() { + return "These{a=" + a + '}'; + } + } + + private static final class _B extends These { + private final B b; + + private _B(B b) { + this.b = b; + } + + @Override + public R match(Function aFn, Function bFn, + Function, ? extends R> cFn) { + return bFn.apply(b); + } + + @Override + public boolean equals(Object other) { + return other instanceof These._B && Objects.equals(b, ((_B) other).b); + } + + @Override + public int hashCode() { + return Objects.hash(b); + } + + @Override + public String toString() { + return "These{b=" + b + '}'; + } + } + + private static final class Both extends These { + private final Tuple2 both; + + private Both(Tuple2 tuple) { + this.both = tuple; + } + + @Override + public R match(Function aFn, Function bFn, + Function, ? extends R> cFn) { + return cFn.apply(both); + } + + @Override + public boolean equals(Object other) { + return other instanceof Both && Objects.equals(both, ((Both) other).both); + } + + @Override + public int hashCode() { + return Objects.hash(both); + } + + @Override + public String toString() { + return "These{both=" + both + '}'; + } + } +} diff --git a/src/test/java/com/jnape/palatable/lambda/adt/TheseTest.java b/src/test/java/com/jnape/palatable/lambda/adt/TheseTest.java new file mode 100644 index 000000000..ba44c9caf --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/adt/TheseTest.java @@ -0,0 +1,25 @@ +package com.jnape.palatable.lambda.adt; + +import com.jnape.palatable.traitor.annotations.TestTraits; +import com.jnape.palatable.traitor.framework.Subjects; +import com.jnape.palatable.traitor.runners.Traits; +import org.junit.runner.RunWith; +import testsupport.traits.ApplicativeLaws; +import testsupport.traits.BifunctorLaws; +import testsupport.traits.FunctorLaws; +import testsupport.traits.MonadLaws; +import testsupport.traits.TraversableLaws; + +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.traitor.framework.Subjects.subjects; + +@RunWith(Traits.class) +public class TheseTest { + + @TestTraits({FunctorLaws.class, ApplicativeLaws.class, MonadLaws.class, TraversableLaws.class, BifunctorLaws.class}) + public Subjects> testSubject() { + return subjects(a("foo"), b(1), both("foo", 1)); + } +} \ No newline at end of file From aeb902ed86939c86bbdaa4100d1e38c49ec29248 Mon Sep 17 00:00:00 2001 From: jnape Date: Sat, 3 Feb 2018 16:53:44 -0600 Subject: [PATCH 08/52] These, the coproduct of A, B, or Tuple2 --- CHANGELOG.md | 1 + .../lambda/functions/builtin/fn2/Into3.java | 10 +++++----- .../lambda/functions/builtin/fn2/Into4.java | 12 +++++++----- .../lambda/functions/builtin/fn2/Into5.java | 13 ++++++++----- .../lambda/functions/builtin/fn2/Into6.java | 14 +++++++++----- .../lambda/functions/builtin/fn2/Into7.java | 14 +++++++++----- .../lambda/functions/builtin/fn2/Into8.java | 15 +++++++++------ 7 files changed, 48 insertions(+), 31 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d65ee612e..95a08c940 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/). - `Bifunctor` is now a `BoundedBifunctor` where both parameter upper bounds are `Object` - `Peek2` now accepts the more general `BoundedBifunctor` - `Identity`, `Compose`, and `Const` functors all have better `toString` implementations +- `Into3-8` now supports functions with parameter variance ### Deprecated - `Either#trying` in favor of `Try#trying` diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Into3.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Into3.java index 8cbe3b75e..1ab7e302e 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Into3.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Into3.java @@ -14,12 +14,12 @@ * @param the third argument type * @param the result type */ -public final class Into3 implements Fn2, Tuple3, D> { +public final class Into3 implements Fn2, Tuple3, D> { private static final Into3 INSTANCE = new Into3(); @Override - public D apply(Fn3 fn, Tuple3 tuple) { + public D apply(Fn3 fn, Tuple3 tuple) { return tuple.into(fn); } @@ -28,11 +28,11 @@ public static Into3 into3() { return INSTANCE; } - public static Fn1, D> into3(Fn3 fn) { + public static Fn1, D> into3(Fn3 fn) { return Into3.into3().apply(fn); } - public static D into3(Fn3 fn, Tuple3 tuple) { - return into3(fn).apply(tuple); + public static D into3(Fn3 fn, Tuple3 tuple) { + return Into3.into3(fn).apply(tuple); } } diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Into4.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Into4.java index ec879df81..240c0eb58 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Into4.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Into4.java @@ -15,12 +15,12 @@ * @param the fourth argument type * @param the result type */ -public final class Into4 implements Fn2, Tuple4, E> { +public final class Into4 implements Fn2, Tuple4, E> { private static final Into4 INSTANCE = new Into4(); @Override - public E apply(Fn4 fn, Tuple4 tuple) { + public E apply(Fn4 fn, Tuple4 tuple) { return tuple.into(fn); } @@ -29,11 +29,13 @@ public static Into4 into4() { return INSTANCE; } - public static Fn1, E> into4(Fn4 fn) { + public static Fn1, E> into4( + Fn4 fn) { return Into4.into4().apply(fn); } - public static E into4(Fn4 fn, Tuple4 tuple) { - return into4(fn).apply(tuple); + public static E into4(Fn4 fn, + Tuple4 tuple) { + return Into4.into4(fn).apply(tuple); } } diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Into5.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Into5.java index 91fc50a3a..f74b6966c 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Into5.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Into5.java @@ -16,12 +16,13 @@ * @param the fifth argument type * @param the result type */ -public final class Into5 implements Fn2, Tuple5, F> { +public final class Into5 implements Fn2, Tuple5, F> { private static final Into5 INSTANCE = new Into5(); @Override - public F apply(Fn5 fn, Tuple5 tuple) { + public F apply(Fn5 fn, + Tuple5 tuple) { return tuple.into(fn); } @@ -30,11 +31,13 @@ public static Into5 into5() { return INSTANCE; } - public static Fn1, F> into5(Fn5 fn) { + public static Fn1, F> into5( + Fn5 fn) { return Into5.into5().apply(fn); } - public static F into5(Fn5 fn, Tuple5 tuple) { - return into5(fn).apply(tuple); + public static F into5(Fn5 fn, + Tuple5 tuple) { + return Into5.into5(fn).apply(tuple); } } diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Into6.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Into6.java index 9f9152574..50e68ef70 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Into6.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Into6.java @@ -18,12 +18,13 @@ * @param the sixth argument type * @param the result type */ -public final class Into6 implements Fn2, Tuple6, G> { +public final class Into6 implements Fn2, Tuple6, G> { private static final Into6 INSTANCE = new Into6(); @Override - public G apply(Fn6 fn, Tuple6 tuple) { + public G apply(Fn6 fn, + Tuple6 tuple) { return tuple.into(fn); } @@ -32,11 +33,14 @@ public static Into6 into6() { return INSTANCE; } - public static Fn1, G> into6(Fn6 fn) { + public static Fn1, G> into6( + Fn6 fn) { return Into6.into6().apply(fn); } - public static G into6(Fn6 fn, Tuple6 tuple) { - return into6(fn).apply(tuple); + public static G into6( + Fn6 fn, + Tuple6 tuple) { + return Into6.into6(fn).apply(tuple); } } diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Into7.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Into7.java index b2a527861..c814d4b3e 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Into7.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Into7.java @@ -19,12 +19,13 @@ * @param the seventh argument type * @param the result type */ -public final class Into7 implements Fn2, Tuple7, H> { +public final class Into7 implements Fn2, Tuple7, H> { private static final Into7 INSTANCE = new Into7(); @Override - public H apply(Fn7 fn, Tuple7 tuple) { + public H apply(Fn7 fn, + Tuple7 tuple) { return tuple.into(fn); } @@ -33,11 +34,14 @@ public static Into7 into7() { return INSTANCE; } - public static Fn1, H> into7(Fn7 fn) { + public static Fn1, H> into7( + Fn7 fn) { return Into7.into7().apply(fn); } - public static H into7(Fn7 fn, Tuple7 tuple) { - return into7(fn).apply(tuple); + public static H into7( + Fn7 fn, + Tuple7 tuple) { + return Into7.into7(fn).apply(tuple); } } diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Into8.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Into8.java index a454c0bcf..6ac77230b 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Into8.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Into8.java @@ -20,12 +20,14 @@ * @param the eighth argument type * @param the result type */ -public final class Into8 implements Fn2, Tuple8, I> { +public final class Into8 implements Fn2, Tuple8, I> { private static final Into8 INSTANCE = new Into8(); @Override - public I apply(Fn8 fn, Tuple8 tuple) { + public I apply( + Fn8 fn, + Tuple8 tuple) { return tuple.into(fn); } @@ -35,12 +37,13 @@ public static Into8 into8 } public static Fn1, I> into8( - Fn8 fn) { + Fn8 fn) { return Into8.into8().apply(fn); } - public static I into8(Fn8 fn, - Tuple8 tuple) { - return into8(fn).apply(tuple); + public static I into8( + Fn8 fn, + Tuple8 tuple) { + return Into8.into8(fn).apply(tuple); } } From 4bfbbda0557e38542f202a334e2175424e740195 Mon Sep 17 00:00:00 2001 From: jnape Date: Sat, 3 Feb 2018 16:59:11 -0600 Subject: [PATCH 09/52] Adding variance to Try#toEither --- src/main/java/com/jnape/palatable/lambda/adt/Either.java | 2 +- src/main/java/com/jnape/palatable/lambda/adt/Try.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/jnape/palatable/lambda/adt/Either.java b/src/main/java/com/jnape/palatable/lambda/adt/Either.java index 89374cbc6..9d304f94b 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/Either.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/Either.java @@ -292,7 +292,7 @@ public static Either fromMaybe(Maybe maybe, Supplier leftFn) @SuppressWarnings("unchecked") public static Either trying(CheckedSupplier supplier, Function leftFn) { - return Try.trying(supplier).toEither(leftFn).biMap(id(), id()); + return Try.trying(supplier::get).toEither(leftFn); } /** diff --git a/src/main/java/com/jnape/palatable/lambda/adt/Try.java b/src/main/java/com/jnape/palatable/lambda/adt/Try.java index 255e25b7b..88b49de70 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/Try.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/Try.java @@ -125,7 +125,7 @@ public final Either toEither() { * @param the {@link Either} left parameter type * @return {@link Either} the success value or the mapped left value */ - public final Either toEither(Function fn) { + public final Either toEither(Function fn) { return match(fn.andThen(Either::left), Either::right); } From d43695a3c027ee8513038135791a89600bb248d3 Mon Sep 17 00:00:00 2001 From: jnape Date: Sat, 10 Feb 2018 15:40:07 -0600 Subject: [PATCH 10/52] HListLens#tail is now covariant in Tail parameter --- CHANGELOG.md | 1 + .../java/com/jnape/palatable/lambda/lens/lenses/HListLens.java | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 95a08c940..d207c8caa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/). - `Peek2` now accepts the more general `BoundedBifunctor` - `Identity`, `Compose`, and `Const` functors all have better `toString` implementations - `Into3-8` now supports functions with parameter variance +- `HListLens#tail` is now covariant in `Tail` parameter ### Deprecated - `Either#trying` in favor of `Try#trying` diff --git a/src/main/java/com/jnape/palatable/lambda/lens/lenses/HListLens.java b/src/main/java/com/jnape/palatable/lambda/lens/lenses/HListLens.java index 085baeaf2..bc0ce452b 100644 --- a/src/main/java/com/jnape/palatable/lambda/lens/lenses/HListLens.java +++ b/src/main/java/com/jnape/palatable/lambda/lens/lenses/HListLens.java @@ -43,7 +43,7 @@ public final class HListLens { * @param the tail HList type * @return a lens that focuses on the tail of an HList */ - public static Lens.Simple, Tail> tail() { + public static Lens.Simple, Tail> tail() { return simpleLens(HCons::tail, (hCons, newTail) -> cons(hCons.head(), newTail)); } } From 68a20e08ed793ac5b7ef0a0aab0dafde623e0806 Mon Sep 17 00:00:00 2001 From: jnape Date: Sat, 17 Feb 2018 15:19:50 -0600 Subject: [PATCH 11/52] Sequence is now better at inferring the target type --- CHANGELOG.md | 15 ++--- .../functions/builtin/fn2/Sequence.java | 57 +++++++++++-------- .../functions/builtin/fn2/SequenceTest.java | 25 ++++++-- 3 files changed, 62 insertions(+), 35 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d207c8caa..5b79c69cc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,14 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/). ## [Unreleased] +### Changed +- ***Breaking Change***: `Sequence` now has two more type parameters to aid in inference +- `Bifunctor` is now a `BoundedBifunctor` where both parameter upper bounds are `Object` +- `Peek2` now accepts the more general `BoundedBifunctor` +- `Identity`, `Compose`, and `Const` functors all have better `toString` implementations +- `Into3-8` now supports functions with parameter variance +- `HListLens#tail` is now covariant in `Tail` parameter + ### Added - `BoundedBifunctor`, a `Bifunctor` super type that offers upper bounds for both parameters - `Try`, a `Monad` representing an expression-like analog of `try/catch/finally` @@ -12,13 +20,6 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/). - `Kleisli`, the abstract representation of a `Kleisli` arrow (`Monad#flatMap`) as an `Fn1` - `These`, a `CoProduct3` of `A`, `B`, or `Tuple2` -### Changed -- `Bifunctor` is now a `BoundedBifunctor` where both parameter upper bounds are `Object` -- `Peek2` now accepts the more general `BoundedBifunctor` -- `Identity`, `Compose`, and `Const` functors all have better `toString` implementations -- `Into3-8` now supports functions with parameter variance -- `HListLens#tail` is now covariant in `Tail` parameter - ### Deprecated - `Either#trying` in favor of `Try#trying` diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Sequence.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Sequence.java index 3232736e7..a7b4b777b 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Sequence.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Sequence.java @@ -23,11 +23,16 @@ * @param the Traversable element type * @param the Applicative unification parameter * @param the Traversable unification parameter + * @param the Applicative instance wrapped in the input Traversable + * @param the Traversable instance wrapped in the output Applicative * @param the concrete parametrized output Applicative type * @param the concrete parametrized input Traversable type */ -public final class Sequence, App>, - TravApp extends Traversable, Trav>> implements Fn2, ? extends AppTrav>, AppTrav> { +public final class Sequence, + TravA extends Traversable, + AppTrav extends Applicative, + TravApp extends Traversable> implements Fn2, AppTrav> { private static final Sequence INSTANCE = new Sequence(); @@ -36,43 +41,47 @@ private Sequence() { @Override @SuppressWarnings("unchecked") - public AppTrav apply(TravApp traversable, Function, ? extends AppTrav> pure) { - return (AppTrav) traversable.traverse(id(), pure); + public AppTrav apply(TravApp traversable, Function pure) { + return (AppTrav) traversable.traverse(id(), trav -> pure.apply((TravA) trav)); } @SuppressWarnings("unchecked") public static , App>, - TravApp extends Traversable, Trav>> Sequence sequence() { + AppA extends Applicative, + TravA extends Traversable, + AppTrav extends Applicative, + TravApp extends Traversable> Sequence sequence() { return INSTANCE; } public static , App>, - TravApp extends Traversable, Trav>> Fn1, ? extends AppTrav>, AppTrav> sequence( + AppA extends Applicative, + TravA extends Traversable, + AppTrav extends Applicative, + TravApp extends Traversable> Fn1, AppTrav> sequence( TravApp traversable) { - return Sequence.sequence().apply(traversable); + return Sequence.sequence().apply(traversable); } public static , App>, - TravApp extends Traversable, Trav>> AppTrav sequence(TravApp traversable, - Function, ? extends AppTrav> pure) { - return Sequence.sequence(traversable).apply(pure); + TravA extends Traversable, + AppA extends Applicative, + AppTrav extends Applicative, + TravApp extends Traversable> AppTrav sequence(TravApp traversable, + Function pure) { + return Sequence.sequence(traversable).apply(pure); } - @SuppressWarnings("unchecked") - public static , App>, IterableApp extends Iterable>> Fn1, ? extends AppIterable>, AppIterable> sequence( - IterableApp iterableApp) { - return pure -> - (AppIterable) sequence(LambdaIterable.wrap(iterableApp), x -> pure.apply(((LambdaIterable) x).unwrap()) - .fmap(LambdaIterable::wrap)) - .fmap(LambdaIterable::unwrap); + @SuppressWarnings({"unchecked", "RedundantTypeArguments"}) + public static , AppIterable extends Applicative, App>, IterableApp extends Iterable> + Fn1, ? extends AppIterable>, AppIterable> sequence(IterableApp iterableApp) { + return pure -> (AppIterable) Sequence., AppA, Applicative, App>, LambdaIterable>sequence( + LambdaIterable.wrap(iterableApp), x -> pure.apply(x.unwrap()).fmap(LambdaIterable::wrap)) + .fmap(LambdaIterable::unwrap); } - public static , App>, - IterableApp extends Iterable>> AppIterable sequence(IterableApp iterableApp, - Function, ? extends AppIterable> pure) { - return Sequence.sequence(iterableApp).apply(pure); + public static , AppIterable extends Applicative, App>, IterableApp extends Iterable> + AppIterable sequence(IterableApp iterableApp, Function, ? extends AppIterable> pure) { + return Sequence.sequence(iterableApp).apply(pure); } } diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/SequenceTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/SequenceTest.java index 3eea6917f..73b45f407 100644 --- a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/SequenceTest.java +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/SequenceTest.java @@ -1,6 +1,7 @@ package com.jnape.palatable.lambda.functions.builtin.fn2; import com.jnape.palatable.lambda.adt.Either; +import com.jnape.palatable.lambda.adt.Maybe; import com.jnape.palatable.lambda.functor.builtin.Compose; import com.jnape.palatable.lambda.functor.builtin.Identity; import org.junit.Test; @@ -8,6 +9,7 @@ import java.util.function.Function; import static com.jnape.palatable.lambda.adt.Either.right; +import static com.jnape.palatable.lambda.adt.Maybe.just; import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; import static com.jnape.palatable.lambda.functions.builtin.fn2.Sequence.sequence; import static java.util.Arrays.asList; @@ -29,15 +31,15 @@ public void naturality() { @Test public void identity() { Either> traversable = right(new Identity<>(1)); - assertEquals(sequence(traversable.fmap(Identity::new), Identity::new), - new Identity<>(traversable)); + assertEquals(new Identity<>(traversable), + sequence(traversable.fmap(Identity::new), Identity::new)); } @Test public void composition() { Either>> traversable = right(new Identity<>(right(1))); - assertEquals(sequence(traversable.fmap(x -> new Compose<>(x.fmap(id()))), x -> new Compose<>(new Identity<>(right(x)))), - new Compose<>(sequence(traversable, Identity::new).fmap(x -> sequence(x, Either::right)).fmap(id()))); + assertEquals(new Compose<>(sequence(traversable, Identity::new).fmap(x -> sequence(x, Either::right)).fmap(id())), + sequence(traversable.fmap(x -> new Compose<>(x.fmap(id()))), x -> new Compose<>(new Identity<>(right(x))))); } @Test @@ -46,4 +48,19 @@ public void iterableSpecialization() { .orThrow(l -> new AssertionError("Expected a right value, but was a left value of <" + l + ">")), iterates(1, 2)); } + + @Test + public void compilation() { + Either> a = sequence(just(right(1)), Either::right); + assertEquals(right(just(1)), a); + + Maybe> b = sequence(right(just(1)), Maybe::just); + assertEquals(just(right(1)), b); + + Either> c = sequence(b, Either::right); + assertEquals(a, c); + + Maybe> d = sequence(asList(just(1), just(2)), Maybe::just); + assertThat(d.orElseThrow(AssertionError::new), iterates(1, 2)); + } } \ No newline at end of file From b62b2a0d380e7ede94c7ff48fa57648b54ed5e1c Mon Sep 17 00:00:00 2001 From: jnape Date: Sun, 18 Feb 2018 14:06:29 -0600 Subject: [PATCH 12/52] Adding Span --- CHANGELOG.md | 3 +- .../lambda/functions/builtin/fn2/Span.java | 43 +++++++++++++++++++ .../functions/builtin/fn2/SpanTest.java | 37 ++++++++++++++++ 3 files changed, 82 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Span.java create mode 100644 src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/SpanTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 5b79c69cc..6cd51155a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,7 +18,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/). - `CheckedRunnable`, the `Runnable` counterpart to `CheckedSupplier` that can throw checked exceptions - `Unit`, the lambda analog to `Void`, except actually inhabited by a singleton instance - `Kleisli`, the abstract representation of a `Kleisli` arrow (`Monad#flatMap`) as an `Fn1` -- `These`, a `CoProduct3` of `A`, `B`, or `Tuple2` +- `These`, a `CoProduct3` of `A`, `B`, or `Tuple2` +- `Span`, for splitting an `Iterable` into contiguous elements matching a predicate ### Deprecated - `Either#trying` in favor of `Try#trying` diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Span.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Span.java new file mode 100644 index 000000000..cd41bc74a --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Span.java @@ -0,0 +1,43 @@ +package com.jnape.palatable.lambda.functions.builtin.fn2; + +import com.jnape.palatable.lambda.adt.hlist.Tuple2; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.Fn2; + +import java.util.function.Function; + +import static com.jnape.palatable.lambda.functions.builtin.fn2.DropWhile.dropWhile; +import static com.jnape.palatable.lambda.functions.builtin.fn2.TakeWhile.takeWhile; + +/** + * Given a predicate, return a {@link Tuple2} where the first slot is the front contiguous elements of an {@link + * Iterable} matching the predicate and the second slot is all the remaining elements. + * + * @param the {@link Iterable} element type + */ +public final class Span implements Fn2, Iterable, Tuple2, Iterable>> { + + private static final Span INSTANCE = new Span(); + + private Span() { + } + + @Override + public Tuple2, Iterable> apply(Function predicate, Iterable as) { + return Tuple2.fill(as).biMap(takeWhile(predicate), dropWhile(predicate)); + } + + @SuppressWarnings("unchecked") + public static Span span() { + return INSTANCE; + } + + public static Fn1, Tuple2, Iterable>> span(Function predicate) { + return Span.span().apply(predicate); + } + + public static Tuple2, Iterable> span(Function predicate, + Iterable as) { + return Span.span(predicate).apply(as); + } +} diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/SpanTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/SpanTest.java new file mode 100644 index 000000000..c29320506 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/SpanTest.java @@ -0,0 +1,37 @@ +package com.jnape.palatable.lambda.functions.builtin.fn2; + +import com.jnape.palatable.lambda.adt.hlist.Tuple2; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.monoid.builtin.Concat; +import com.jnape.palatable.traitor.annotations.TestTraits; +import com.jnape.palatable.traitor.runners.Traits; +import org.junit.Test; +import org.junit.runner.RunWith; +import testsupport.traits.EmptyIterableSupport; +import testsupport.traits.FiniteIteration; +import testsupport.traits.ImmutableIteration; +import testsupport.traits.InfiniteIterableSupport; +import testsupport.traits.Laziness; + +import static com.jnape.palatable.lambda.functions.builtin.fn2.Eq.eq; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Into.into; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Span.span; +import static java.util.Arrays.asList; +import static org.junit.Assert.assertThat; +import static testsupport.matchers.IterableMatcher.iterates; + +@RunWith(Traits.class) +public class SpanTest { + + @TestTraits({EmptyIterableSupport.class, InfiniteIterableSupport.class, FiniteIteration.class, ImmutableIteration.class, Laziness.class}) + public Fn1, Iterable> testSubject() { + return span(eq(0)).fmap(into(Concat::concat)); + } + + @Test + public void splitsIterableAfterPredicateFailure() { + Tuple2, Iterable> spanned = span(eq(1), asList(1, 1, 1, 2, 3, 1)); + assertThat(spanned._1(), iterates(1, 1, 1)); + assertThat(spanned._2(), iterates(2, 3, 1)); + } +} \ No newline at end of file From 9e1784db760c3303d7ac43efbb9aee3b54810aa5 Mon Sep 17 00:00:00 2001 From: jnape Date: Mon, 19 Feb 2018 16:01:33 -0600 Subject: [PATCH 13/52] Moving iterators under iteration package, adding DroppingIterable - first serious step towards auto-deforesting, starting with Drop --- CHANGELOG.md | 1 + .../lambda/functions/builtin/fn1/Cycle.java | 2 +- .../lambda/functions/builtin/fn1/Flatten.java | 2 +- .../lambda/functions/builtin/fn1/Init.java | 2 +- .../lambda/functions/builtin/fn1/Repeat.java | 2 +- .../lambda/functions/builtin/fn1/Reverse.java | 2 +- .../builtin/fn2/CartesianProduct.java | 2 +- .../lambda/functions/builtin/fn2/Cons.java | 2 +- .../lambda/functions/builtin/fn2/Drop.java | 4 ++-- .../functions/builtin/fn2/DropWhile.java | 2 +- .../lambda/functions/builtin/fn2/Filter.java | 2 +- .../functions/builtin/fn2/InGroupsOf.java | 2 +- .../lambda/functions/builtin/fn2/Map.java | 2 +- .../functions/builtin/fn2/PrependAll.java | 2 +- .../lambda/functions/builtin/fn2/Snoc.java | 2 +- .../lambda/functions/builtin/fn2/Take.java | 2 +- .../functions/builtin/fn2/TakeWhile.java | 2 +- .../lambda/functions/builtin/fn2/Unfoldr.java | 2 +- .../functions/builtin/fn3/ScanLeft.java | 2 +- .../lambda/functions/builtin/fn3/ZipWith.java | 2 +- .../CombinatorialIterator.java | 2 +- .../ConcatenatingIterator.java | 2 +- .../ConsingIterator.java | 2 +- .../CyclicIterator.java | 2 +- .../lambda/iteration/DroppingIterable.java | 23 +++++++++++++++++++ .../DroppingIterator.java | 18 ++++----------- .../FilteringIterator.java | 2 +- .../FlatteningIterator.java | 2 +- .../GroupingIterator.java | 2 +- .../ImmutableIterator.java | 2 +- .../InfiniteIterator.java | 2 +- .../InitIterator.java | 2 +- .../MappingIterator.java | 2 +- .../PredicatedDroppingIterator.java | 2 +- .../PredicatedTakingIterator.java | 2 +- .../PrependingIterator.java | 2 +- .../RepetitiousIterator.java | 2 +- .../ReversingIterator.java | 2 +- .../RewindableIterator.java | 2 +- .../ScanningIterator.java | 2 +- .../SnocIterator.java | 2 +- .../TakingIterator.java | 2 +- .../UnfoldingIterator.java | 2 +- .../ZippingIterator.java | 2 +- .../lambda/monoid/builtin/Concat.java | 2 +- .../functions/builtin/fn2/DropTest.java | 11 +++++++++ .../CombinatorialIteratorTest.java | 2 +- .../ConcatenatingIteratorTest.java | 2 +- .../ConsingIteratorTest.java | 2 +- .../CyclicIteratorTest.java | 2 +- .../iteration/DroppingIterableTest.java | 16 +++++++++++++ .../DroppingIteratorTest.java | 2 +- .../FilteringIteratorTest.java | 2 +- .../FlatteningIteratorTest.java | 2 +- .../GroupingIteratorTest.java | 2 +- .../ImmutableIteratorTest.java | 2 +- .../InfiniteIteratorTest.java | 2 +- .../InitIteratorTest.java | 2 +- .../MappingIteratorTest.java | 2 +- .../PredicatedDroppingIteratorTest.java | 2 +- .../PredicatedTakingIteratorTest.java | 2 +- .../PrependingIteratorTest.java | 2 +- .../RepetitiousIteratorTest.java | 2 +- .../ReversingIteratorTest.java | 2 +- .../RewindableIteratorTest.java | 2 +- .../ScanningIteratorTest.java | 2 +- .../SnocIteratorTest.java | 2 +- .../TakingIteratorTest.java | 2 +- .../UnfoldingIteratorTest.java | 2 +- .../ZippingIteratorTest.java | 2 +- .../traits/NestingStackSafety.java | 15 ++++++++++++ 71 files changed, 137 insertions(+), 79 deletions(-) rename src/main/java/com/jnape/palatable/lambda/{iterators => iteration}/CombinatorialIterator.java (97%) rename src/main/java/com/jnape/palatable/lambda/{iterators => iteration}/ConcatenatingIterator.java (97%) rename src/main/java/com/jnape/palatable/lambda/{iterators => iteration}/ConsingIterator.java (96%) rename src/main/java/com/jnape/palatable/lambda/{iterators => iteration}/CyclicIterator.java (95%) create mode 100644 src/main/java/com/jnape/palatable/lambda/iteration/DroppingIterable.java rename src/main/java/com/jnape/palatable/lambda/{iterators => iteration}/DroppingIterator.java (62%) rename src/main/java/com/jnape/palatable/lambda/{iterators => iteration}/FilteringIterator.java (95%) rename src/main/java/com/jnape/palatable/lambda/{iterators => iteration}/FlatteningIterator.java (94%) rename src/main/java/com/jnape/palatable/lambda/{iterators => iteration}/GroupingIterator.java (93%) rename src/main/java/com/jnape/palatable/lambda/{iterators => iteration}/ImmutableIterator.java (83%) rename src/main/java/com/jnape/palatable/lambda/{iterators => iteration}/InfiniteIterator.java (77%) rename src/main/java/com/jnape/palatable/lambda/{iterators => iteration}/InitIterator.java (93%) rename src/main/java/com/jnape/palatable/lambda/{iterators => iteration}/MappingIterator.java (92%) rename src/main/java/com/jnape/palatable/lambda/{iterators => iteration}/PredicatedDroppingIterator.java (96%) rename src/main/java/com/jnape/palatable/lambda/{iterators => iteration}/PredicatedTakingIterator.java (96%) rename src/main/java/com/jnape/palatable/lambda/{iterators => iteration}/PrependingIterator.java (94%) rename src/main/java/com/jnape/palatable/lambda/{iterators => iteration}/RepetitiousIterator.java (83%) rename src/main/java/com/jnape/palatable/lambda/{iterators => iteration}/ReversingIterator.java (95%) rename src/main/java/com/jnape/palatable/lambda/{iterators => iteration}/RewindableIterator.java (97%) rename src/main/java/com/jnape/palatable/lambda/{iterators => iteration}/ScanningIterator.java (95%) rename src/main/java/com/jnape/palatable/lambda/{iterators => iteration}/SnocIterator.java (96%) rename src/main/java/com/jnape/palatable/lambda/{iterators => iteration}/TakingIterator.java (93%) rename src/main/java/com/jnape/palatable/lambda/{iterators => iteration}/UnfoldingIterator.java (95%) rename src/main/java/com/jnape/palatable/lambda/{iterators => iteration}/ZippingIterator.java (94%) rename src/test/java/com/jnape/palatable/lambda/{iterators => iteration}/CombinatorialIteratorTest.java (97%) rename src/test/java/com/jnape/palatable/lambda/{iterators => iteration}/ConcatenatingIteratorTest.java (98%) rename src/test/java/com/jnape/palatable/lambda/{iterators => iteration}/ConsingIteratorTest.java (97%) rename src/test/java/com/jnape/palatable/lambda/{iterators => iteration}/CyclicIteratorTest.java (93%) create mode 100644 src/test/java/com/jnape/palatable/lambda/iteration/DroppingIterableTest.java rename src/test/java/com/jnape/palatable/lambda/{iterators => iteration}/DroppingIteratorTest.java (96%) rename src/test/java/com/jnape/palatable/lambda/{iterators => iteration}/FilteringIteratorTest.java (96%) rename src/test/java/com/jnape/palatable/lambda/{iterators => iteration}/FlatteningIteratorTest.java (96%) rename src/test/java/com/jnape/palatable/lambda/{iterators => iteration}/GroupingIteratorTest.java (96%) rename src/test/java/com/jnape/palatable/lambda/{iterators => iteration}/ImmutableIteratorTest.java (93%) rename src/test/java/com/jnape/palatable/lambda/{iterators => iteration}/InfiniteIteratorTest.java (94%) rename src/test/java/com/jnape/palatable/lambda/{iterators => iteration}/InitIteratorTest.java (95%) rename src/test/java/com/jnape/palatable/lambda/{iterators => iteration}/MappingIteratorTest.java (92%) rename src/test/java/com/jnape/palatable/lambda/{iterators => iteration}/PredicatedDroppingIteratorTest.java (97%) rename src/test/java/com/jnape/palatable/lambda/{iterators => iteration}/PredicatedTakingIteratorTest.java (98%) rename src/test/java/com/jnape/palatable/lambda/{iterators => iteration}/PrependingIteratorTest.java (96%) rename src/test/java/com/jnape/palatable/lambda/{iterators => iteration}/RepetitiousIteratorTest.java (94%) rename src/test/java/com/jnape/palatable/lambda/{iterators => iteration}/ReversingIteratorTest.java (97%) rename src/test/java/com/jnape/palatable/lambda/{iterators => iteration}/RewindableIteratorTest.java (97%) rename src/test/java/com/jnape/palatable/lambda/{iterators => iteration}/ScanningIteratorTest.java (96%) rename src/test/java/com/jnape/palatable/lambda/{iterators => iteration}/SnocIteratorTest.java (97%) rename src/test/java/com/jnape/palatable/lambda/{iterators => iteration}/TakingIteratorTest.java (97%) rename src/test/java/com/jnape/palatable/lambda/{iterators => iteration}/UnfoldingIteratorTest.java (95%) rename src/test/java/com/jnape/palatable/lambda/{iterators => iteration}/ZippingIteratorTest.java (97%) create mode 100644 src/test/java/testsupport/traits/NestingStackSafety.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 6cd51155a..709cfaf05 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/). - `Identity`, `Compose`, and `Const` functors all have better `toString` implementations - `Into3-8` now supports functions with parameter variance - `HListLens#tail` is now covariant in `Tail` parameter +- More functions now automatically deforest nested calls ### Added - `BoundedBifunctor`, a `Bifunctor` super type that offers upper bounds for both parameters diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Cycle.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Cycle.java index 440d50041..264237532 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Cycle.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Cycle.java @@ -1,7 +1,7 @@ package com.jnape.palatable.lambda.functions.builtin.fn1; import com.jnape.palatable.lambda.functions.Fn1; -import com.jnape.palatable.lambda.iterators.CyclicIterator; +import com.jnape.palatable.lambda.iteration.CyclicIterator; import static java.util.Arrays.asList; diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Flatten.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Flatten.java index 0bb35e775..4c88bdec8 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Flatten.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Flatten.java @@ -1,7 +1,7 @@ package com.jnape.palatable.lambda.functions.builtin.fn1; import com.jnape.palatable.lambda.functions.Fn1; -import com.jnape.palatable.lambda.iterators.FlatteningIterator; +import com.jnape.palatable.lambda.iteration.FlatteningIterator; /** * Given a nested {@link Iterable} of {@link Iterable}s, return a lazily flattening {@link Iterable} diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Init.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Init.java index 0ba39822b..ced05d274 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Init.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Init.java @@ -1,7 +1,7 @@ package com.jnape.palatable.lambda.functions.builtin.fn1; import com.jnape.palatable.lambda.functions.Fn1; -import com.jnape.palatable.lambda.iterators.InitIterator; +import com.jnape.palatable.lambda.iteration.InitIterator; /** * Given an {@link Iterable}<A>, produce an diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Repeat.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Repeat.java index 30da5141f..4286dbace 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Repeat.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Repeat.java @@ -1,7 +1,7 @@ package com.jnape.palatable.lambda.functions.builtin.fn1; import com.jnape.palatable.lambda.functions.Fn1; -import com.jnape.palatable.lambda.iterators.RepetitiousIterator; +import com.jnape.palatable.lambda.iteration.RepetitiousIterator; /** * Given a value, return an infinite Iterable that repeatedly iterates that value. diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Reverse.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Reverse.java index f2ab76ae6..0ed3265f7 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Reverse.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Reverse.java @@ -1,7 +1,7 @@ package com.jnape.palatable.lambda.functions.builtin.fn1; import com.jnape.palatable.lambda.functions.Fn1; -import com.jnape.palatable.lambda.iterators.ReversingIterator; +import com.jnape.palatable.lambda.iteration.ReversingIterator; /** * Given an Iterable, return a reversed representation of that Iterable. Note that reversing diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/CartesianProduct.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/CartesianProduct.java index ec22bb0ed..aa883f86c 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/CartesianProduct.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/CartesianProduct.java @@ -3,7 +3,7 @@ import com.jnape.palatable.lambda.adt.hlist.Tuple2; import com.jnape.palatable.lambda.functions.Fn1; import com.jnape.palatable.lambda.functions.Fn2; -import com.jnape.palatable.lambda.iterators.CombinatorialIterator; +import com.jnape.palatable.lambda.iteration.CombinatorialIterator; /** * Lazily compute the cartesian product of an Iterable<A> and Iterable<B>, diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Cons.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Cons.java index a4e146cae..bee418871 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Cons.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Cons.java @@ -2,7 +2,7 @@ import com.jnape.palatable.lambda.functions.Fn1; import com.jnape.palatable.lambda.functions.Fn2; -import com.jnape.palatable.lambda.iterators.ConsingIterator; +import com.jnape.palatable.lambda.iteration.ConsingIterator; /** * Prepend an element to an Iterable. diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Drop.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Drop.java index a0c1d9082..c286d2da6 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Drop.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Drop.java @@ -2,7 +2,7 @@ import com.jnape.palatable.lambda.functions.Fn1; import com.jnape.palatable.lambda.functions.Fn2; -import com.jnape.palatable.lambda.iterators.DroppingIterator; +import com.jnape.palatable.lambda.iteration.DroppingIterable; /** * Lazily skip the first n elements from an Iterable by returning an Iterable @@ -22,7 +22,7 @@ private Drop() { @Override public Iterable apply(Integer n, Iterable as) { - return () -> new DroppingIterator<>(n, as.iterator()); + return new DroppingIterable<>(n, as); } @SuppressWarnings("unchecked") diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/DropWhile.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/DropWhile.java index a11472d68..d89d7f46e 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/DropWhile.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/DropWhile.java @@ -2,7 +2,7 @@ import com.jnape.palatable.lambda.functions.Fn1; import com.jnape.palatable.lambda.functions.Fn2; -import com.jnape.palatable.lambda.iterators.PredicatedDroppingIterator; +import com.jnape.palatable.lambda.iteration.PredicatedDroppingIterator; import java.util.function.Function; diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Filter.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Filter.java index ebad91eb5..0e9e51acd 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Filter.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Filter.java @@ -2,7 +2,7 @@ import com.jnape.palatable.lambda.functions.Fn1; import com.jnape.palatable.lambda.functions.Fn2; -import com.jnape.palatable.lambda.iterators.FilteringIterator; +import com.jnape.palatable.lambda.iteration.FilteringIterator; import java.util.function.Function; diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/InGroupsOf.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/InGroupsOf.java index ebdf7cab7..da93304c3 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/InGroupsOf.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/InGroupsOf.java @@ -2,7 +2,7 @@ import com.jnape.palatable.lambda.functions.Fn1; import com.jnape.palatable.lambda.functions.Fn2; -import com.jnape.palatable.lambda.iterators.GroupingIterator; +import com.jnape.palatable.lambda.iteration.GroupingIterator; /** * Lazily group the Iterable by returning an Iterable of smaller Iterables of diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Map.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Map.java index 1736edb0f..776141d15 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Map.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Map.java @@ -2,7 +2,7 @@ import com.jnape.palatable.lambda.functions.Fn1; import com.jnape.palatable.lambda.functions.Fn2; -import com.jnape.palatable.lambda.iterators.MappingIterator; +import com.jnape.palatable.lambda.iteration.MappingIterator; import java.util.function.Function; diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/PrependAll.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/PrependAll.java index da4a3dca7..4c7f26f2a 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/PrependAll.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/PrependAll.java @@ -2,7 +2,7 @@ import com.jnape.palatable.lambda.functions.Fn1; import com.jnape.palatable.lambda.functions.Fn2; -import com.jnape.palatable.lambda.iterators.PrependingIterator; +import com.jnape.palatable.lambda.iteration.PrependingIterator; /** * Lazily prepend each value with of the Iterable with the supplied separator value. An empty diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Snoc.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Snoc.java index 7ef0cec73..69125edf2 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Snoc.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Snoc.java @@ -2,7 +2,7 @@ import com.jnape.palatable.lambda.functions.Fn1; import com.jnape.palatable.lambda.functions.Fn2; -import com.jnape.palatable.lambda.iterators.SnocIterator; +import com.jnape.palatable.lambda.iteration.SnocIterator; /** * Opposite of {@link Cons}: lazily append an element to the end of the given {@link Iterable}. diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Take.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Take.java index a67bdcfd4..679e7f8a1 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Take.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Take.java @@ -2,7 +2,7 @@ import com.jnape.palatable.lambda.functions.Fn1; import com.jnape.palatable.lambda.functions.Fn2; -import com.jnape.palatable.lambda.iterators.TakingIterator; +import com.jnape.palatable.lambda.iteration.TakingIterator; /** * Lazily limit the Iterable to n elements by returning an Iterable that stops diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/TakeWhile.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/TakeWhile.java index e14f5caf8..7e3c0b073 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/TakeWhile.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/TakeWhile.java @@ -2,7 +2,7 @@ import com.jnape.palatable.lambda.functions.Fn1; import com.jnape.palatable.lambda.functions.Fn2; -import com.jnape.palatable.lambda.iterators.PredicatedTakingIterator; +import com.jnape.palatable.lambda.iteration.PredicatedTakingIterator; import java.util.function.Function; diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Unfoldr.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Unfoldr.java index cb0a277b6..9a2cd1b15 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Unfoldr.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Unfoldr.java @@ -4,7 +4,7 @@ import com.jnape.palatable.lambda.adt.hlist.Tuple2; import com.jnape.palatable.lambda.functions.Fn1; import com.jnape.palatable.lambda.functions.Fn2; -import com.jnape.palatable.lambda.iterators.UnfoldingIterator; +import com.jnape.palatable.lambda.iteration.UnfoldingIterator; import java.util.function.Function; diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/ScanLeft.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/ScanLeft.java index a6af6210b..9cc780a15 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/ScanLeft.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/ScanLeft.java @@ -3,7 +3,7 @@ import com.jnape.palatable.lambda.functions.Fn1; import com.jnape.palatable.lambda.functions.Fn2; import com.jnape.palatable.lambda.functions.Fn3; -import com.jnape.palatable.lambda.iterators.ScanningIterator; +import com.jnape.palatable.lambda.iteration.ScanningIterator; import java.util.function.BiFunction; diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/ZipWith.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/ZipWith.java index 28a83fdeb..75266b884 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/ZipWith.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/ZipWith.java @@ -3,7 +3,7 @@ import com.jnape.palatable.lambda.functions.Fn1; import com.jnape.palatable.lambda.functions.Fn2; import com.jnape.palatable.lambda.functions.Fn3; -import com.jnape.palatable.lambda.iterators.ZippingIterator; +import com.jnape.palatable.lambda.iteration.ZippingIterator; import java.util.function.BiFunction; diff --git a/src/main/java/com/jnape/palatable/lambda/iterators/CombinatorialIterator.java b/src/main/java/com/jnape/palatable/lambda/iteration/CombinatorialIterator.java similarity index 97% rename from src/main/java/com/jnape/palatable/lambda/iterators/CombinatorialIterator.java rename to src/main/java/com/jnape/palatable/lambda/iteration/CombinatorialIterator.java index 5e4a4dcd4..1aefe0536 100644 --- a/src/main/java/com/jnape/palatable/lambda/iterators/CombinatorialIterator.java +++ b/src/main/java/com/jnape/palatable/lambda/iteration/CombinatorialIterator.java @@ -1,4 +1,4 @@ -package com.jnape.palatable.lambda.iterators; +package com.jnape.palatable.lambda.iteration; import com.jnape.palatable.lambda.adt.hlist.Tuple2; diff --git a/src/main/java/com/jnape/palatable/lambda/iterators/ConcatenatingIterator.java b/src/main/java/com/jnape/palatable/lambda/iteration/ConcatenatingIterator.java similarity index 97% rename from src/main/java/com/jnape/palatable/lambda/iterators/ConcatenatingIterator.java rename to src/main/java/com/jnape/palatable/lambda/iteration/ConcatenatingIterator.java index cd58f6f74..c96454a43 100644 --- a/src/main/java/com/jnape/palatable/lambda/iterators/ConcatenatingIterator.java +++ b/src/main/java/com/jnape/palatable/lambda/iteration/ConcatenatingIterator.java @@ -1,4 +1,4 @@ -package com.jnape.palatable.lambda.iterators; +package com.jnape.palatable.lambda.iteration; import java.util.Iterator; import java.util.NoSuchElementException; diff --git a/src/main/java/com/jnape/palatable/lambda/iterators/ConsingIterator.java b/src/main/java/com/jnape/palatable/lambda/iteration/ConsingIterator.java similarity index 96% rename from src/main/java/com/jnape/palatable/lambda/iterators/ConsingIterator.java rename to src/main/java/com/jnape/palatable/lambda/iteration/ConsingIterator.java index c8dee5b3b..d35d92c0b 100644 --- a/src/main/java/com/jnape/palatable/lambda/iterators/ConsingIterator.java +++ b/src/main/java/com/jnape/palatable/lambda/iteration/ConsingIterator.java @@ -1,4 +1,4 @@ -package com.jnape.palatable.lambda.iterators; +package com.jnape.palatable.lambda.iteration; import java.util.Iterator; import java.util.NoSuchElementException; diff --git a/src/main/java/com/jnape/palatable/lambda/iterators/CyclicIterator.java b/src/main/java/com/jnape/palatable/lambda/iteration/CyclicIterator.java similarity index 95% rename from src/main/java/com/jnape/palatable/lambda/iterators/CyclicIterator.java rename to src/main/java/com/jnape/palatable/lambda/iteration/CyclicIterator.java index 4aa462bb2..ba04f23b8 100644 --- a/src/main/java/com/jnape/palatable/lambda/iterators/CyclicIterator.java +++ b/src/main/java/com/jnape/palatable/lambda/iteration/CyclicIterator.java @@ -1,4 +1,4 @@ -package com.jnape.palatable.lambda.iterators; +package com.jnape.palatable.lambda.iteration; import java.util.ArrayList; import java.util.Iterator; diff --git a/src/main/java/com/jnape/palatable/lambda/iteration/DroppingIterable.java b/src/main/java/com/jnape/palatable/lambda/iteration/DroppingIterable.java new file mode 100644 index 000000000..19d1c5aaa --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/iteration/DroppingIterable.java @@ -0,0 +1,23 @@ +package com.jnape.palatable.lambda.iteration; + +import java.util.Iterator; + +public class DroppingIterable implements Iterable { + private final int n; + private final Iterable as; + + public DroppingIterable(int n, Iterable as) { + while (as instanceof DroppingIterable) { + DroppingIterable nested = (DroppingIterable) as; + as = nested.as; + n += nested.n; + } + this.as = as; + this.n = n; + } + + @Override + public Iterator iterator() { + return new DroppingIterator<>(n, as.iterator()); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/iterators/DroppingIterator.java b/src/main/java/com/jnape/palatable/lambda/iteration/DroppingIterator.java similarity index 62% rename from src/main/java/com/jnape/palatable/lambda/iterators/DroppingIterator.java rename to src/main/java/com/jnape/palatable/lambda/iteration/DroppingIterator.java index 48938ff67..e0c543c3f 100644 --- a/src/main/java/com/jnape/palatable/lambda/iterators/DroppingIterator.java +++ b/src/main/java/com/jnape/palatable/lambda/iteration/DroppingIterator.java @@ -1,12 +1,12 @@ -package com.jnape.palatable.lambda.iterators; +package com.jnape.palatable.lambda.iteration; import java.util.Iterator; import java.util.NoSuchElementException; public class DroppingIterator extends ImmutableIterator { - private Integer n; - private Iterator asIterator; - private boolean dropped; + private final Integer n; + private final Iterator asIterator; + private boolean dropped; public DroppingIterator(Integer n, Iterator asIterator) { this.n = n; @@ -17,9 +17,7 @@ public DroppingIterator(Integer n, Iterator asIterator) { @Override public boolean hasNext() { if (!dropped) { - deforest(); drop(); - dropped = true; } return asIterator.hasNext(); } @@ -32,16 +30,10 @@ public A next() { return asIterator.next(); } - private void deforest() { - while (asIterator instanceof DroppingIterator) { - n += ((DroppingIterator) this.asIterator).n; - asIterator = ((DroppingIterator) this.asIterator).asIterator; - } - } - private void drop() { int i = 0; while (i++ < n && asIterator.hasNext()) asIterator.next(); + dropped = true; } } diff --git a/src/main/java/com/jnape/palatable/lambda/iterators/FilteringIterator.java b/src/main/java/com/jnape/palatable/lambda/iteration/FilteringIterator.java similarity index 95% rename from src/main/java/com/jnape/palatable/lambda/iterators/FilteringIterator.java rename to src/main/java/com/jnape/palatable/lambda/iteration/FilteringIterator.java index b95f39eda..75f02db95 100644 --- a/src/main/java/com/jnape/palatable/lambda/iterators/FilteringIterator.java +++ b/src/main/java/com/jnape/palatable/lambda/iteration/FilteringIterator.java @@ -1,4 +1,4 @@ -package com.jnape.palatable.lambda.iterators; +package com.jnape.palatable.lambda.iteration; import java.util.Iterator; import java.util.NoSuchElementException; diff --git a/src/main/java/com/jnape/palatable/lambda/iterators/FlatteningIterator.java b/src/main/java/com/jnape/palatable/lambda/iteration/FlatteningIterator.java similarity index 94% rename from src/main/java/com/jnape/palatable/lambda/iterators/FlatteningIterator.java rename to src/main/java/com/jnape/palatable/lambda/iteration/FlatteningIterator.java index 7938ce880..f9e16ae24 100644 --- a/src/main/java/com/jnape/palatable/lambda/iterators/FlatteningIterator.java +++ b/src/main/java/com/jnape/palatable/lambda/iteration/FlatteningIterator.java @@ -1,4 +1,4 @@ -package com.jnape.palatable.lambda.iterators; +package com.jnape.palatable.lambda.iteration; import java.util.Iterator; import java.util.NoSuchElementException; diff --git a/src/main/java/com/jnape/palatable/lambda/iterators/GroupingIterator.java b/src/main/java/com/jnape/palatable/lambda/iteration/GroupingIterator.java similarity index 93% rename from src/main/java/com/jnape/palatable/lambda/iterators/GroupingIterator.java rename to src/main/java/com/jnape/palatable/lambda/iteration/GroupingIterator.java index 310324b75..6960c87c8 100644 --- a/src/main/java/com/jnape/palatable/lambda/iterators/GroupingIterator.java +++ b/src/main/java/com/jnape/palatable/lambda/iteration/GroupingIterator.java @@ -1,4 +1,4 @@ -package com.jnape.palatable.lambda.iterators; +package com.jnape.palatable.lambda.iteration; import java.util.ArrayList; import java.util.Iterator; diff --git a/src/main/java/com/jnape/palatable/lambda/iterators/ImmutableIterator.java b/src/main/java/com/jnape/palatable/lambda/iteration/ImmutableIterator.java similarity index 83% rename from src/main/java/com/jnape/palatable/lambda/iterators/ImmutableIterator.java rename to src/main/java/com/jnape/palatable/lambda/iteration/ImmutableIterator.java index cef1ff254..7252d2313 100644 --- a/src/main/java/com/jnape/palatable/lambda/iterators/ImmutableIterator.java +++ b/src/main/java/com/jnape/palatable/lambda/iteration/ImmutableIterator.java @@ -1,4 +1,4 @@ -package com.jnape.palatable.lambda.iterators; +package com.jnape.palatable.lambda.iteration; import java.util.Iterator; diff --git a/src/main/java/com/jnape/palatable/lambda/iterators/InfiniteIterator.java b/src/main/java/com/jnape/palatable/lambda/iteration/InfiniteIterator.java similarity index 77% rename from src/main/java/com/jnape/palatable/lambda/iterators/InfiniteIterator.java rename to src/main/java/com/jnape/palatable/lambda/iteration/InfiniteIterator.java index b85b1d235..88b7c640d 100644 --- a/src/main/java/com/jnape/palatable/lambda/iterators/InfiniteIterator.java +++ b/src/main/java/com/jnape/palatable/lambda/iteration/InfiniteIterator.java @@ -1,4 +1,4 @@ -package com.jnape.palatable.lambda.iterators; +package com.jnape.palatable.lambda.iteration; public abstract class InfiniteIterator extends ImmutableIterator { @Override diff --git a/src/main/java/com/jnape/palatable/lambda/iterators/InitIterator.java b/src/main/java/com/jnape/palatable/lambda/iteration/InitIterator.java similarity index 93% rename from src/main/java/com/jnape/palatable/lambda/iterators/InitIterator.java rename to src/main/java/com/jnape/palatable/lambda/iteration/InitIterator.java index 0de021061..e89ebf1fb 100644 --- a/src/main/java/com/jnape/palatable/lambda/iterators/InitIterator.java +++ b/src/main/java/com/jnape/palatable/lambda/iteration/InitIterator.java @@ -1,4 +1,4 @@ -package com.jnape.palatable.lambda.iterators; +package com.jnape.palatable.lambda.iteration; import java.util.Iterator; import java.util.NoSuchElementException; diff --git a/src/main/java/com/jnape/palatable/lambda/iterators/MappingIterator.java b/src/main/java/com/jnape/palatable/lambda/iteration/MappingIterator.java similarity index 92% rename from src/main/java/com/jnape/palatable/lambda/iterators/MappingIterator.java rename to src/main/java/com/jnape/palatable/lambda/iteration/MappingIterator.java index 81b46c027..82624603f 100644 --- a/src/main/java/com/jnape/palatable/lambda/iterators/MappingIterator.java +++ b/src/main/java/com/jnape/palatable/lambda/iteration/MappingIterator.java @@ -1,4 +1,4 @@ -package com.jnape.palatable.lambda.iterators; +package com.jnape.palatable.lambda.iteration; import java.util.Iterator; import java.util.function.Function; diff --git a/src/main/java/com/jnape/palatable/lambda/iterators/PredicatedDroppingIterator.java b/src/main/java/com/jnape/palatable/lambda/iteration/PredicatedDroppingIterator.java similarity index 96% rename from src/main/java/com/jnape/palatable/lambda/iterators/PredicatedDroppingIterator.java rename to src/main/java/com/jnape/palatable/lambda/iteration/PredicatedDroppingIterator.java index c7f8f279e..e94b97d5d 100644 --- a/src/main/java/com/jnape/palatable/lambda/iterators/PredicatedDroppingIterator.java +++ b/src/main/java/com/jnape/palatable/lambda/iteration/PredicatedDroppingIterator.java @@ -1,4 +1,4 @@ -package com.jnape.palatable.lambda.iterators; +package com.jnape.palatable.lambda.iteration; import java.util.Iterator; import java.util.NoSuchElementException; diff --git a/src/main/java/com/jnape/palatable/lambda/iterators/PredicatedTakingIterator.java b/src/main/java/com/jnape/palatable/lambda/iteration/PredicatedTakingIterator.java similarity index 96% rename from src/main/java/com/jnape/palatable/lambda/iterators/PredicatedTakingIterator.java rename to src/main/java/com/jnape/palatable/lambda/iteration/PredicatedTakingIterator.java index c269ad72b..a53684946 100644 --- a/src/main/java/com/jnape/palatable/lambda/iterators/PredicatedTakingIterator.java +++ b/src/main/java/com/jnape/palatable/lambda/iteration/PredicatedTakingIterator.java @@ -1,4 +1,4 @@ -package com.jnape.palatable.lambda.iterators; +package com.jnape.palatable.lambda.iteration; import java.util.Iterator; import java.util.NoSuchElementException; diff --git a/src/main/java/com/jnape/palatable/lambda/iterators/PrependingIterator.java b/src/main/java/com/jnape/palatable/lambda/iteration/PrependingIterator.java similarity index 94% rename from src/main/java/com/jnape/palatable/lambda/iterators/PrependingIterator.java rename to src/main/java/com/jnape/palatable/lambda/iteration/PrependingIterator.java index 3b3a75fde..53755c0f2 100644 --- a/src/main/java/com/jnape/palatable/lambda/iterators/PrependingIterator.java +++ b/src/main/java/com/jnape/palatable/lambda/iteration/PrependingIterator.java @@ -1,4 +1,4 @@ -package com.jnape.palatable.lambda.iterators; +package com.jnape.palatable.lambda.iteration; import java.util.Iterator; import java.util.NoSuchElementException; diff --git a/src/main/java/com/jnape/palatable/lambda/iterators/RepetitiousIterator.java b/src/main/java/com/jnape/palatable/lambda/iteration/RepetitiousIterator.java similarity index 83% rename from src/main/java/com/jnape/palatable/lambda/iterators/RepetitiousIterator.java rename to src/main/java/com/jnape/palatable/lambda/iteration/RepetitiousIterator.java index da3c73cbe..7f43bed68 100644 --- a/src/main/java/com/jnape/palatable/lambda/iterators/RepetitiousIterator.java +++ b/src/main/java/com/jnape/palatable/lambda/iteration/RepetitiousIterator.java @@ -1,4 +1,4 @@ -package com.jnape.palatable.lambda.iterators; +package com.jnape.palatable.lambda.iteration; public class RepetitiousIterator extends InfiniteIterator { diff --git a/src/main/java/com/jnape/palatable/lambda/iterators/ReversingIterator.java b/src/main/java/com/jnape/palatable/lambda/iteration/ReversingIterator.java similarity index 95% rename from src/main/java/com/jnape/palatable/lambda/iterators/ReversingIterator.java rename to src/main/java/com/jnape/palatable/lambda/iteration/ReversingIterator.java index fd042ee9a..e676cfa11 100644 --- a/src/main/java/com/jnape/palatable/lambda/iterators/ReversingIterator.java +++ b/src/main/java/com/jnape/palatable/lambda/iteration/ReversingIterator.java @@ -1,4 +1,4 @@ -package com.jnape.palatable.lambda.iterators; +package com.jnape.palatable.lambda.iteration; import java.util.ArrayList; import java.util.Iterator; diff --git a/src/main/java/com/jnape/palatable/lambda/iterators/RewindableIterator.java b/src/main/java/com/jnape/palatable/lambda/iteration/RewindableIterator.java similarity index 97% rename from src/main/java/com/jnape/palatable/lambda/iterators/RewindableIterator.java rename to src/main/java/com/jnape/palatable/lambda/iteration/RewindableIterator.java index 99f96cb6a..07b540704 100644 --- a/src/main/java/com/jnape/palatable/lambda/iterators/RewindableIterator.java +++ b/src/main/java/com/jnape/palatable/lambda/iteration/RewindableIterator.java @@ -1,4 +1,4 @@ -package com.jnape.palatable.lambda.iterators; +package com.jnape.palatable.lambda.iteration; import java.util.Iterator; import java.util.NoSuchElementException; diff --git a/src/main/java/com/jnape/palatable/lambda/iterators/ScanningIterator.java b/src/main/java/com/jnape/palatable/lambda/iteration/ScanningIterator.java similarity index 95% rename from src/main/java/com/jnape/palatable/lambda/iterators/ScanningIterator.java rename to src/main/java/com/jnape/palatable/lambda/iteration/ScanningIterator.java index aaaa43fb7..d195b14b3 100644 --- a/src/main/java/com/jnape/palatable/lambda/iterators/ScanningIterator.java +++ b/src/main/java/com/jnape/palatable/lambda/iteration/ScanningIterator.java @@ -1,4 +1,4 @@ -package com.jnape.palatable.lambda.iterators; +package com.jnape.palatable.lambda.iteration; import java.util.Iterator; import java.util.NoSuchElementException; diff --git a/src/main/java/com/jnape/palatable/lambda/iterators/SnocIterator.java b/src/main/java/com/jnape/palatable/lambda/iteration/SnocIterator.java similarity index 96% rename from src/main/java/com/jnape/palatable/lambda/iterators/SnocIterator.java rename to src/main/java/com/jnape/palatable/lambda/iteration/SnocIterator.java index 2506bd658..4bde48e36 100644 --- a/src/main/java/com/jnape/palatable/lambda/iterators/SnocIterator.java +++ b/src/main/java/com/jnape/palatable/lambda/iteration/SnocIterator.java @@ -1,4 +1,4 @@ -package com.jnape.palatable.lambda.iterators; +package com.jnape.palatable.lambda.iteration; import java.util.Collections; import java.util.Iterator; diff --git a/src/main/java/com/jnape/palatable/lambda/iterators/TakingIterator.java b/src/main/java/com/jnape/palatable/lambda/iteration/TakingIterator.java similarity index 93% rename from src/main/java/com/jnape/palatable/lambda/iterators/TakingIterator.java rename to src/main/java/com/jnape/palatable/lambda/iteration/TakingIterator.java index 8e659dc68..6cfedc82b 100644 --- a/src/main/java/com/jnape/palatable/lambda/iterators/TakingIterator.java +++ b/src/main/java/com/jnape/palatable/lambda/iteration/TakingIterator.java @@ -1,4 +1,4 @@ -package com.jnape.palatable.lambda.iterators; +package com.jnape.palatable.lambda.iteration; import java.util.Iterator; import java.util.NoSuchElementException; diff --git a/src/main/java/com/jnape/palatable/lambda/iterators/UnfoldingIterator.java b/src/main/java/com/jnape/palatable/lambda/iteration/UnfoldingIterator.java similarity index 95% rename from src/main/java/com/jnape/palatable/lambda/iterators/UnfoldingIterator.java rename to src/main/java/com/jnape/palatable/lambda/iteration/UnfoldingIterator.java index a5fcfcc42..8bcdebe12 100644 --- a/src/main/java/com/jnape/palatable/lambda/iterators/UnfoldingIterator.java +++ b/src/main/java/com/jnape/palatable/lambda/iteration/UnfoldingIterator.java @@ -1,4 +1,4 @@ -package com.jnape.palatable.lambda.iterators; +package com.jnape.palatable.lambda.iteration; import com.jnape.palatable.lambda.adt.Maybe; import com.jnape.palatable.lambda.adt.hlist.Tuple2; diff --git a/src/main/java/com/jnape/palatable/lambda/iterators/ZippingIterator.java b/src/main/java/com/jnape/palatable/lambda/iteration/ZippingIterator.java similarity index 94% rename from src/main/java/com/jnape/palatable/lambda/iterators/ZippingIterator.java rename to src/main/java/com/jnape/palatable/lambda/iteration/ZippingIterator.java index 0372e6fff..147079e80 100644 --- a/src/main/java/com/jnape/palatable/lambda/iterators/ZippingIterator.java +++ b/src/main/java/com/jnape/palatable/lambda/iteration/ZippingIterator.java @@ -1,4 +1,4 @@ -package com.jnape.palatable.lambda.iterators; +package com.jnape.palatable.lambda.iteration; import java.util.Iterator; import java.util.function.BiFunction; diff --git a/src/main/java/com/jnape/palatable/lambda/monoid/builtin/Concat.java b/src/main/java/com/jnape/palatable/lambda/monoid/builtin/Concat.java index f48e4cc5a..e48030a83 100644 --- a/src/main/java/com/jnape/palatable/lambda/monoid/builtin/Concat.java +++ b/src/main/java/com/jnape/palatable/lambda/monoid/builtin/Concat.java @@ -1,7 +1,7 @@ package com.jnape.palatable.lambda.monoid.builtin; import com.jnape.palatable.lambda.functions.Fn1; -import com.jnape.palatable.lambda.iterators.ConcatenatingIterator; +import com.jnape.palatable.lambda.iteration.ConcatenatingIterator; import com.jnape.palatable.lambda.monoid.Monoid; import java.util.Collections; diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/DropTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/DropTest.java index 329fd13e2..62dc0e73b 100644 --- a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/DropTest.java +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/DropTest.java @@ -10,8 +10,13 @@ import testsupport.traits.ImmutableIteration; import testsupport.traits.Laziness; +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Head.head; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Repeat.repeat; import static com.jnape.palatable.lambda.functions.builtin.fn2.Drop.drop; +import static com.jnape.palatable.lambda.functions.builtin.fn3.Times.times; import static java.util.Arrays.asList; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThat; import static testsupport.matchers.IterableMatcher.iterates; @@ -27,4 +32,10 @@ public Fn1, Iterable> createTestSubject() { public void dropsElementsUpToLimit() { assertThat(drop(2, asList(1, 2, 3, 4)), iterates(3, 4)); } + + @Test + public void stackSafety() { + int stackBlowingNumber = 10_000; + assertEquals(just(1), head(times(stackBlowingNumber, drop(1), repeat(1)))); + } } diff --git a/src/test/java/com/jnape/palatable/lambda/iterators/CombinatorialIteratorTest.java b/src/test/java/com/jnape/palatable/lambda/iteration/CombinatorialIteratorTest.java similarity index 97% rename from src/test/java/com/jnape/palatable/lambda/iterators/CombinatorialIteratorTest.java rename to src/test/java/com/jnape/palatable/lambda/iteration/CombinatorialIteratorTest.java index ecfa8faab..7678bd2c4 100644 --- a/src/test/java/com/jnape/palatable/lambda/iterators/CombinatorialIteratorTest.java +++ b/src/test/java/com/jnape/palatable/lambda/iteration/CombinatorialIteratorTest.java @@ -1,4 +1,4 @@ -package com.jnape.palatable.lambda.iterators; +package com.jnape.palatable.lambda.iteration; import org.junit.Before; import org.junit.Test; diff --git a/src/test/java/com/jnape/palatable/lambda/iterators/ConcatenatingIteratorTest.java b/src/test/java/com/jnape/palatable/lambda/iteration/ConcatenatingIteratorTest.java similarity index 98% rename from src/test/java/com/jnape/palatable/lambda/iterators/ConcatenatingIteratorTest.java rename to src/test/java/com/jnape/palatable/lambda/iteration/ConcatenatingIteratorTest.java index 47c360f0e..f1191ae87 100644 --- a/src/test/java/com/jnape/palatable/lambda/iterators/ConcatenatingIteratorTest.java +++ b/src/test/java/com/jnape/palatable/lambda/iteration/ConcatenatingIteratorTest.java @@ -1,4 +1,4 @@ -package com.jnape.palatable.lambda.iterators; +package com.jnape.palatable.lambda.iteration; import org.junit.Before; import org.junit.Test; diff --git a/src/test/java/com/jnape/palatable/lambda/iterators/ConsingIteratorTest.java b/src/test/java/com/jnape/palatable/lambda/iteration/ConsingIteratorTest.java similarity index 97% rename from src/test/java/com/jnape/palatable/lambda/iterators/ConsingIteratorTest.java rename to src/test/java/com/jnape/palatable/lambda/iteration/ConsingIteratorTest.java index 467a4b138..b0eb89832 100644 --- a/src/test/java/com/jnape/palatable/lambda/iterators/ConsingIteratorTest.java +++ b/src/test/java/com/jnape/palatable/lambda/iteration/ConsingIteratorTest.java @@ -1,4 +1,4 @@ -package com.jnape.palatable.lambda.iterators; +package com.jnape.palatable.lambda.iteration; import org.junit.Before; import org.junit.Test; diff --git a/src/test/java/com/jnape/palatable/lambda/iterators/CyclicIteratorTest.java b/src/test/java/com/jnape/palatable/lambda/iteration/CyclicIteratorTest.java similarity index 93% rename from src/test/java/com/jnape/palatable/lambda/iterators/CyclicIteratorTest.java rename to src/test/java/com/jnape/palatable/lambda/iteration/CyclicIteratorTest.java index 80c85a895..ba7e84485 100644 --- a/src/test/java/com/jnape/palatable/lambda/iterators/CyclicIteratorTest.java +++ b/src/test/java/com/jnape/palatable/lambda/iteration/CyclicIteratorTest.java @@ -1,4 +1,4 @@ -package com.jnape.palatable.lambda.iterators; +package com.jnape.palatable.lambda.iteration; import org.junit.Test; diff --git a/src/test/java/com/jnape/palatable/lambda/iteration/DroppingIterableTest.java b/src/test/java/com/jnape/palatable/lambda/iteration/DroppingIterableTest.java new file mode 100644 index 000000000..4509dc92f --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/iteration/DroppingIterableTest.java @@ -0,0 +1,16 @@ +package com.jnape.palatable.lambda.iteration; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.traitor.annotations.TestTraits; +import com.jnape.palatable.traitor.runners.Traits; +import org.junit.runner.RunWith; +import testsupport.traits.NestingStackSafety; + +@RunWith(Traits.class) +public class DroppingIterableTest { + + @TestTraits({NestingStackSafety.class}) + public Fn1, Iterable> testSubject() { + return x -> new DroppingIterable<>(1, x); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/iterators/DroppingIteratorTest.java b/src/test/java/com/jnape/palatable/lambda/iteration/DroppingIteratorTest.java similarity index 96% rename from src/test/java/com/jnape/palatable/lambda/iterators/DroppingIteratorTest.java rename to src/test/java/com/jnape/palatable/lambda/iteration/DroppingIteratorTest.java index e5a09fb77..0e28a2dd2 100644 --- a/src/test/java/com/jnape/palatable/lambda/iterators/DroppingIteratorTest.java +++ b/src/test/java/com/jnape/palatable/lambda/iteration/DroppingIteratorTest.java @@ -1,4 +1,4 @@ -package com.jnape.palatable.lambda.iterators; +package com.jnape.palatable.lambda.iteration; import org.junit.Before; import org.junit.Test; diff --git a/src/test/java/com/jnape/palatable/lambda/iterators/FilteringIteratorTest.java b/src/test/java/com/jnape/palatable/lambda/iteration/FilteringIteratorTest.java similarity index 96% rename from src/test/java/com/jnape/palatable/lambda/iterators/FilteringIteratorTest.java rename to src/test/java/com/jnape/palatable/lambda/iteration/FilteringIteratorTest.java index 55481a9fc..5f2c3cb7e 100644 --- a/src/test/java/com/jnape/palatable/lambda/iterators/FilteringIteratorTest.java +++ b/src/test/java/com/jnape/palatable/lambda/iteration/FilteringIteratorTest.java @@ -1,4 +1,4 @@ -package com.jnape.palatable.lambda.iterators; +package com.jnape.palatable.lambda.iteration; import org.junit.Test; diff --git a/src/test/java/com/jnape/palatable/lambda/iterators/FlatteningIteratorTest.java b/src/test/java/com/jnape/palatable/lambda/iteration/FlatteningIteratorTest.java similarity index 96% rename from src/test/java/com/jnape/palatable/lambda/iterators/FlatteningIteratorTest.java rename to src/test/java/com/jnape/palatable/lambda/iteration/FlatteningIteratorTest.java index e33cb656b..481b2da6f 100644 --- a/src/test/java/com/jnape/palatable/lambda/iterators/FlatteningIteratorTest.java +++ b/src/test/java/com/jnape/palatable/lambda/iteration/FlatteningIteratorTest.java @@ -1,4 +1,4 @@ -package com.jnape.palatable.lambda.iterators; +package com.jnape.palatable.lambda.iteration; import org.junit.Test; diff --git a/src/test/java/com/jnape/palatable/lambda/iterators/GroupingIteratorTest.java b/src/test/java/com/jnape/palatable/lambda/iteration/GroupingIteratorTest.java similarity index 96% rename from src/test/java/com/jnape/palatable/lambda/iterators/GroupingIteratorTest.java rename to src/test/java/com/jnape/palatable/lambda/iteration/GroupingIteratorTest.java index 98084dea3..0caab11a5 100644 --- a/src/test/java/com/jnape/palatable/lambda/iterators/GroupingIteratorTest.java +++ b/src/test/java/com/jnape/palatable/lambda/iteration/GroupingIteratorTest.java @@ -1,4 +1,4 @@ -package com.jnape.palatable.lambda.iterators; +package com.jnape.palatable.lambda.iteration; import org.junit.Before; import org.junit.Test; diff --git a/src/test/java/com/jnape/palatable/lambda/iterators/ImmutableIteratorTest.java b/src/test/java/com/jnape/palatable/lambda/iteration/ImmutableIteratorTest.java similarity index 93% rename from src/test/java/com/jnape/palatable/lambda/iterators/ImmutableIteratorTest.java rename to src/test/java/com/jnape/palatable/lambda/iteration/ImmutableIteratorTest.java index 226cdd675..d1ffc6ebd 100644 --- a/src/test/java/com/jnape/palatable/lambda/iterators/ImmutableIteratorTest.java +++ b/src/test/java/com/jnape/palatable/lambda/iteration/ImmutableIteratorTest.java @@ -1,4 +1,4 @@ -package com.jnape.palatable.lambda.iterators; +package com.jnape.palatable.lambda.iteration; import org.junit.Before; import org.junit.Test; diff --git a/src/test/java/com/jnape/palatable/lambda/iterators/InfiniteIteratorTest.java b/src/test/java/com/jnape/palatable/lambda/iteration/InfiniteIteratorTest.java similarity index 94% rename from src/test/java/com/jnape/palatable/lambda/iterators/InfiniteIteratorTest.java rename to src/test/java/com/jnape/palatable/lambda/iteration/InfiniteIteratorTest.java index 428541d91..9d9f8abd5 100644 --- a/src/test/java/com/jnape/palatable/lambda/iterators/InfiniteIteratorTest.java +++ b/src/test/java/com/jnape/palatable/lambda/iteration/InfiniteIteratorTest.java @@ -1,4 +1,4 @@ -package com.jnape.palatable.lambda.iterators; +package com.jnape.palatable.lambda.iteration; import org.junit.Before; import org.junit.Test; diff --git a/src/test/java/com/jnape/palatable/lambda/iterators/InitIteratorTest.java b/src/test/java/com/jnape/palatable/lambda/iteration/InitIteratorTest.java similarity index 95% rename from src/test/java/com/jnape/palatable/lambda/iterators/InitIteratorTest.java rename to src/test/java/com/jnape/palatable/lambda/iteration/InitIteratorTest.java index 32eeabce0..04c512cb3 100644 --- a/src/test/java/com/jnape/palatable/lambda/iterators/InitIteratorTest.java +++ b/src/test/java/com/jnape/palatable/lambda/iteration/InitIteratorTest.java @@ -1,4 +1,4 @@ -package com.jnape.palatable.lambda.iterators; +package com.jnape.palatable.lambda.iteration; import org.junit.Test; diff --git a/src/test/java/com/jnape/palatable/lambda/iterators/MappingIteratorTest.java b/src/test/java/com/jnape/palatable/lambda/iteration/MappingIteratorTest.java similarity index 92% rename from src/test/java/com/jnape/palatable/lambda/iterators/MappingIteratorTest.java rename to src/test/java/com/jnape/palatable/lambda/iteration/MappingIteratorTest.java index da1469db9..c4522f8c1 100644 --- a/src/test/java/com/jnape/palatable/lambda/iterators/MappingIteratorTest.java +++ b/src/test/java/com/jnape/palatable/lambda/iteration/MappingIteratorTest.java @@ -1,4 +1,4 @@ -package com.jnape.palatable.lambda.iterators; +package com.jnape.palatable.lambda.iteration; import com.jnape.palatable.lambda.functions.Fn1; import org.junit.Test; diff --git a/src/test/java/com/jnape/palatable/lambda/iterators/PredicatedDroppingIteratorTest.java b/src/test/java/com/jnape/palatable/lambda/iteration/PredicatedDroppingIteratorTest.java similarity index 97% rename from src/test/java/com/jnape/palatable/lambda/iterators/PredicatedDroppingIteratorTest.java rename to src/test/java/com/jnape/palatable/lambda/iteration/PredicatedDroppingIteratorTest.java index b597da2a0..68f933a9a 100644 --- a/src/test/java/com/jnape/palatable/lambda/iterators/PredicatedDroppingIteratorTest.java +++ b/src/test/java/com/jnape/palatable/lambda/iteration/PredicatedDroppingIteratorTest.java @@ -1,4 +1,4 @@ -package com.jnape.palatable.lambda.iterators; +package com.jnape.palatable.lambda.iteration; import com.jnape.palatable.lambda.functions.Fn1; import org.junit.Before; diff --git a/src/test/java/com/jnape/palatable/lambda/iterators/PredicatedTakingIteratorTest.java b/src/test/java/com/jnape/palatable/lambda/iteration/PredicatedTakingIteratorTest.java similarity index 98% rename from src/test/java/com/jnape/palatable/lambda/iterators/PredicatedTakingIteratorTest.java rename to src/test/java/com/jnape/palatable/lambda/iteration/PredicatedTakingIteratorTest.java index 6e817057b..833ad43c8 100644 --- a/src/test/java/com/jnape/palatable/lambda/iterators/PredicatedTakingIteratorTest.java +++ b/src/test/java/com/jnape/palatable/lambda/iteration/PredicatedTakingIteratorTest.java @@ -1,4 +1,4 @@ -package com.jnape.palatable.lambda.iterators; +package com.jnape.palatable.lambda.iteration; import com.jnape.palatable.lambda.functions.specialized.Predicate; import org.junit.Test; diff --git a/src/test/java/com/jnape/palatable/lambda/iterators/PrependingIteratorTest.java b/src/test/java/com/jnape/palatable/lambda/iteration/PrependingIteratorTest.java similarity index 96% rename from src/test/java/com/jnape/palatable/lambda/iterators/PrependingIteratorTest.java rename to src/test/java/com/jnape/palatable/lambda/iteration/PrependingIteratorTest.java index 8f4eaa173..84bdebc1a 100644 --- a/src/test/java/com/jnape/palatable/lambda/iterators/PrependingIteratorTest.java +++ b/src/test/java/com/jnape/palatable/lambda/iteration/PrependingIteratorTest.java @@ -1,4 +1,4 @@ -package com.jnape.palatable.lambda.iterators; +package com.jnape.palatable.lambda.iteration; import org.junit.Rule; import org.junit.Test; diff --git a/src/test/java/com/jnape/palatable/lambda/iterators/RepetitiousIteratorTest.java b/src/test/java/com/jnape/palatable/lambda/iteration/RepetitiousIteratorTest.java similarity index 94% rename from src/test/java/com/jnape/palatable/lambda/iterators/RepetitiousIteratorTest.java rename to src/test/java/com/jnape/palatable/lambda/iteration/RepetitiousIteratorTest.java index 2d284c00a..bcf9d2628 100644 --- a/src/test/java/com/jnape/palatable/lambda/iterators/RepetitiousIteratorTest.java +++ b/src/test/java/com/jnape/palatable/lambda/iteration/RepetitiousIteratorTest.java @@ -1,4 +1,4 @@ -package com.jnape.palatable.lambda.iterators; +package com.jnape.palatable.lambda.iteration; import org.junit.Before; import org.junit.Test; diff --git a/src/test/java/com/jnape/palatable/lambda/iterators/ReversingIteratorTest.java b/src/test/java/com/jnape/palatable/lambda/iteration/ReversingIteratorTest.java similarity index 97% rename from src/test/java/com/jnape/palatable/lambda/iterators/ReversingIteratorTest.java rename to src/test/java/com/jnape/palatable/lambda/iteration/ReversingIteratorTest.java index 22164a729..754345bfb 100644 --- a/src/test/java/com/jnape/palatable/lambda/iterators/ReversingIteratorTest.java +++ b/src/test/java/com/jnape/palatable/lambda/iteration/ReversingIteratorTest.java @@ -1,4 +1,4 @@ -package com.jnape.palatable.lambda.iterators; +package com.jnape.palatable.lambda.iteration; import org.junit.Before; import org.junit.Test; diff --git a/src/test/java/com/jnape/palatable/lambda/iterators/RewindableIteratorTest.java b/src/test/java/com/jnape/palatable/lambda/iteration/RewindableIteratorTest.java similarity index 97% rename from src/test/java/com/jnape/palatable/lambda/iterators/RewindableIteratorTest.java rename to src/test/java/com/jnape/palatable/lambda/iteration/RewindableIteratorTest.java index 544e23fed..3e1498b90 100644 --- a/src/test/java/com/jnape/palatable/lambda/iterators/RewindableIteratorTest.java +++ b/src/test/java/com/jnape/palatable/lambda/iteration/RewindableIteratorTest.java @@ -1,4 +1,4 @@ -package com.jnape.palatable.lambda.iterators; +package com.jnape.palatable.lambda.iteration; import org.junit.Before; import org.junit.Test; diff --git a/src/test/java/com/jnape/palatable/lambda/iterators/ScanningIteratorTest.java b/src/test/java/com/jnape/palatable/lambda/iteration/ScanningIteratorTest.java similarity index 96% rename from src/test/java/com/jnape/palatable/lambda/iterators/ScanningIteratorTest.java rename to src/test/java/com/jnape/palatable/lambda/iteration/ScanningIteratorTest.java index 387bc965e..cde0c2f1b 100644 --- a/src/test/java/com/jnape/palatable/lambda/iterators/ScanningIteratorTest.java +++ b/src/test/java/com/jnape/palatable/lambda/iteration/ScanningIteratorTest.java @@ -1,4 +1,4 @@ -package com.jnape.palatable.lambda.iterators; +package com.jnape.palatable.lambda.iteration; import org.junit.Test; diff --git a/src/test/java/com/jnape/palatable/lambda/iterators/SnocIteratorTest.java b/src/test/java/com/jnape/palatable/lambda/iteration/SnocIteratorTest.java similarity index 97% rename from src/test/java/com/jnape/palatable/lambda/iterators/SnocIteratorTest.java rename to src/test/java/com/jnape/palatable/lambda/iteration/SnocIteratorTest.java index a0b156a57..7ad6ec47b 100644 --- a/src/test/java/com/jnape/palatable/lambda/iterators/SnocIteratorTest.java +++ b/src/test/java/com/jnape/palatable/lambda/iteration/SnocIteratorTest.java @@ -1,4 +1,4 @@ -package com.jnape.palatable.lambda.iterators; +package com.jnape.palatable.lambda.iteration; import org.junit.Before; import org.junit.Test; diff --git a/src/test/java/com/jnape/palatable/lambda/iterators/TakingIteratorTest.java b/src/test/java/com/jnape/palatable/lambda/iteration/TakingIteratorTest.java similarity index 97% rename from src/test/java/com/jnape/palatable/lambda/iterators/TakingIteratorTest.java rename to src/test/java/com/jnape/palatable/lambda/iteration/TakingIteratorTest.java index 9b335554a..9f17bcf4d 100644 --- a/src/test/java/com/jnape/palatable/lambda/iterators/TakingIteratorTest.java +++ b/src/test/java/com/jnape/palatable/lambda/iteration/TakingIteratorTest.java @@ -1,4 +1,4 @@ -package com.jnape.palatable.lambda.iterators; +package com.jnape.palatable.lambda.iteration; import org.junit.Test; diff --git a/src/test/java/com/jnape/palatable/lambda/iterators/UnfoldingIteratorTest.java b/src/test/java/com/jnape/palatable/lambda/iteration/UnfoldingIteratorTest.java similarity index 95% rename from src/test/java/com/jnape/palatable/lambda/iterators/UnfoldingIteratorTest.java rename to src/test/java/com/jnape/palatable/lambda/iteration/UnfoldingIteratorTest.java index 2bcb6474a..2f13f2176 100644 --- a/src/test/java/com/jnape/palatable/lambda/iterators/UnfoldingIteratorTest.java +++ b/src/test/java/com/jnape/palatable/lambda/iteration/UnfoldingIteratorTest.java @@ -1,4 +1,4 @@ -package com.jnape.palatable.lambda.iterators; +package com.jnape.palatable.lambda.iteration; import com.jnape.palatable.lambda.adt.Maybe; import com.jnape.palatable.lambda.adt.hlist.Tuple2; diff --git a/src/test/java/com/jnape/palatable/lambda/iterators/ZippingIteratorTest.java b/src/test/java/com/jnape/palatable/lambda/iteration/ZippingIteratorTest.java similarity index 97% rename from src/test/java/com/jnape/palatable/lambda/iterators/ZippingIteratorTest.java rename to src/test/java/com/jnape/palatable/lambda/iteration/ZippingIteratorTest.java index 561eb2534..f88705a91 100644 --- a/src/test/java/com/jnape/palatable/lambda/iterators/ZippingIteratorTest.java +++ b/src/test/java/com/jnape/palatable/lambda/iteration/ZippingIteratorTest.java @@ -1,4 +1,4 @@ -package com.jnape.palatable.lambda.iterators; +package com.jnape.palatable.lambda.iteration; import org.junit.Before; import org.junit.Test; diff --git a/src/test/java/testsupport/traits/NestingStackSafety.java b/src/test/java/testsupport/traits/NestingStackSafety.java new file mode 100644 index 000000000..af3065c75 --- /dev/null +++ b/src/test/java/testsupport/traits/NestingStackSafety.java @@ -0,0 +1,15 @@ +package testsupport.traits; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.traitor.traits.Trait; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Repeat.repeat; +import static com.jnape.palatable.lambda.functions.builtin.fn3.Times.times; + +public final class NestingStackSafety implements Trait, Iterable>> { + + @Override + public void test(Fn1, Iterable> fn) { + times(10_000, fn, repeat(1)).iterator().next(); + } +} From db06c1995eae2eb17acb75b29c29855b288e1d6b Mon Sep 17 00:00:00 2001 From: jnape Date: Mon, 19 Feb 2018 16:25:40 -0600 Subject: [PATCH 14/52] - Snoc deforesting moved to custom Iterable --- CHANGELOG.md | 2 +- .../lambda/functions/builtin/fn2/Snoc.java | 4 +-- .../lambda/iteration/DroppingIterable.java | 2 +- .../lambda/iteration/SnocIterable.java | 28 +++++++++++++++ .../lambda/iteration/SnocIterator.java | 34 ++++--------------- .../lambda/iteration/SnocIterableTest.java | 16 +++++++++ .../lambda/iteration/SnocIteratorTest.java | 20 ++--------- 7 files changed, 57 insertions(+), 49 deletions(-) create mode 100644 src/main/java/com/jnape/palatable/lambda/iteration/SnocIterable.java create mode 100644 src/test/java/com/jnape/palatable/lambda/iteration/SnocIterableTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 709cfaf05..87e42c10e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/). - `Identity`, `Compose`, and `Const` functors all have better `toString` implementations - `Into3-8` now supports functions with parameter variance - `HListLens#tail` is now covariant in `Tail` parameter -- More functions now automatically deforest nested calls +- More functions now automatically deforest nested calls (`drop`, `snoc`) ### Added - `BoundedBifunctor`, a `Bifunctor` super type that offers upper bounds for both parameters diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Snoc.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Snoc.java index 69125edf2..6437c0c57 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Snoc.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Snoc.java @@ -2,7 +2,7 @@ import com.jnape.palatable.lambda.functions.Fn1; import com.jnape.palatable.lambda.functions.Fn2; -import com.jnape.palatable.lambda.iteration.SnocIterator; +import com.jnape.palatable.lambda.iteration.SnocIterable; /** * Opposite of {@link Cons}: lazily append an element to the end of the given {@link Iterable}. @@ -22,7 +22,7 @@ private Snoc() { @Override public Iterable apply(A a, Iterable as) { - return () -> new SnocIterator<>(a, as); + return new SnocIterable<>(a, as); } @SuppressWarnings("unchecked") diff --git a/src/main/java/com/jnape/palatable/lambda/iteration/DroppingIterable.java b/src/main/java/com/jnape/palatable/lambda/iteration/DroppingIterable.java index 19d1c5aaa..b545a296f 100644 --- a/src/main/java/com/jnape/palatable/lambda/iteration/DroppingIterable.java +++ b/src/main/java/com/jnape/palatable/lambda/iteration/DroppingIterable.java @@ -2,7 +2,7 @@ import java.util.Iterator; -public class DroppingIterable implements Iterable { +public final class DroppingIterable implements Iterable { private final int n; private final Iterable as; diff --git a/src/main/java/com/jnape/palatable/lambda/iteration/SnocIterable.java b/src/main/java/com/jnape/palatable/lambda/iteration/SnocIterable.java new file mode 100644 index 000000000..a16947b4a --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/iteration/SnocIterable.java @@ -0,0 +1,28 @@ +package com.jnape.palatable.lambda.iteration; + +import java.util.Collections; +import java.util.Iterator; + +import static com.jnape.palatable.lambda.functions.builtin.fn2.Cons.cons; +import static com.jnape.palatable.lambda.monoid.builtin.Concat.concat; + +public final class SnocIterable implements Iterable { + private final Iterable as; + private final Iterable snocs; + + public SnocIterable(A a, Iterable as) { + Iterable snocs = cons(a, Collections::emptyIterator); + while (as instanceof SnocIterable) { + SnocIterable nested = ((SnocIterable) as); + as = nested.as; + snocs = concat(nested.snocs, snocs); + } + this.as = as; + this.snocs = snocs; + } + + @Override + public Iterator iterator() { + return new SnocIterator<>(as.iterator(), snocs.iterator()); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/iteration/SnocIterator.java b/src/main/java/com/jnape/palatable/lambda/iteration/SnocIterator.java index 4bde48e36..7c084c7ab 100644 --- a/src/main/java/com/jnape/palatable/lambda/iteration/SnocIterator.java +++ b/src/main/java/com/jnape/palatable/lambda/iteration/SnocIterator.java @@ -1,30 +1,21 @@ package com.jnape.palatable.lambda.iteration; -import java.util.Collections; import java.util.Iterator; import java.util.NoSuchElementException; -import java.util.function.Supplier; - -import static com.jnape.palatable.lambda.functions.builtin.fn2.Cons.cons; public final class SnocIterator implements Iterator { - private final Supplier> initsSupplier; - private final A last; - private Iterator inits; - private Iterator lasts; + private final Iterator as; + private final Iterator snocs; - public SnocIterator(A last, Iterable inits) { - this.last = last; - initsSupplier = inits::iterator; + public SnocIterator(Iterator as, Iterator snocs) { + this.as = as; + this.snocs = snocs; } @Override public boolean hasNext() { - if (inits == null) - queueAndDeforest(); - - return inits.hasNext() || lasts.hasNext(); + return as.hasNext() || snocs.hasNext(); } @Override @@ -32,17 +23,6 @@ public A next() { if (!hasNext()) throw new NoSuchElementException(); - return inits.hasNext() ? inits.next() : lasts.next(); - } - - private void queueAndDeforest() { - Iterable lastConses = Collections::emptyIterator; - inits = this; - while (inits instanceof SnocIterator) { - SnocIterator it = (SnocIterator) inits; - lastConses = cons(it.last, lastConses); - inits = it.initsSupplier.get(); - } - lasts = lastConses.iterator(); + return as.hasNext() ? as.next() : snocs.next(); } } diff --git a/src/test/java/com/jnape/palatable/lambda/iteration/SnocIterableTest.java b/src/test/java/com/jnape/palatable/lambda/iteration/SnocIterableTest.java new file mode 100644 index 000000000..a04d8a079 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/iteration/SnocIterableTest.java @@ -0,0 +1,16 @@ +package com.jnape.palatable.lambda.iteration; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.traitor.annotations.TestTraits; +import com.jnape.palatable.traitor.runners.Traits; +import org.junit.runner.RunWith; +import testsupport.traits.NestingStackSafety; + +@RunWith(Traits.class) +public class SnocIterableTest { + + @TestTraits({NestingStackSafety.class}) + public Fn1, Iterable> testSubject() { + return xs -> new SnocIterable<>(1, xs); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/iteration/SnocIteratorTest.java b/src/test/java/com/jnape/palatable/lambda/iteration/SnocIteratorTest.java index 7ad6ec47b..6fca49817 100644 --- a/src/test/java/com/jnape/palatable/lambda/iteration/SnocIteratorTest.java +++ b/src/test/java/com/jnape/palatable/lambda/iteration/SnocIteratorTest.java @@ -3,14 +3,8 @@ import org.junit.Before; import org.junit.Test; -import java.util.Collections; - -import static com.jnape.palatable.lambda.adt.Maybe.just; -import static com.jnape.palatable.lambda.functions.builtin.fn1.Last.last; -import static com.jnape.palatable.lambda.functions.builtin.fn2.Iterate.iterate; -import static com.jnape.palatable.lambda.functions.builtin.fn2.Take.take; -import static com.jnape.palatable.lambda.functions.builtin.fn3.FoldLeft.foldLeft; import static java.util.Arrays.asList; +import static java.util.Collections.singletonList; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; @@ -21,7 +15,7 @@ public class SnocIteratorTest { @Before public void setUp() { - iterator = new SnocIterator<>(3, asList(1, 2)); + iterator = new SnocIterator<>(asList(1, 2).iterator(), singletonList(3).iterator()); } @Test @@ -45,14 +39,4 @@ public void doesNotHaveNextIfLastIterated() { iterator.next(); assertFalse(iterator.hasNext()); } - - @Test - public void stackSafety() { - int stackBlowingNumber = 100000; - Iterable ints = foldLeft((xs, x) -> () -> new SnocIterator<>(x, xs), - Collections::emptyIterator, - take(stackBlowingNumber, iterate(x -> x + 1, 1))); - - assertEquals(just(stackBlowingNumber), last(ints)); - } } \ No newline at end of file From db7c1af03616df7a196938e51036dcf9287a2d70 Mon Sep 17 00:00:00 2001 From: jnape Date: Mon, 19 Feb 2018 17:21:18 -0600 Subject: [PATCH 15/52] Cycle automatically deforests --- .../lambda/functions/builtin/fn1/Cycle.java | 4 ++-- .../lambda/iteration/CyclicIterable.java | 19 +++++++++++++++++++ .../lambda/iteration/CyclicIterableTest.java | 16 ++++++++++++++++ .../iteration/DroppingIterableTest.java | 4 ++-- .../lambda/iteration/SnocIterableTest.java | 4 ++-- ...stingStackSafety.java => Deforesting.java} | 2 +- 6 files changed, 42 insertions(+), 7 deletions(-) create mode 100644 src/main/java/com/jnape/palatable/lambda/iteration/CyclicIterable.java create mode 100644 src/test/java/com/jnape/palatable/lambda/iteration/CyclicIterableTest.java rename src/test/java/testsupport/traits/{NestingStackSafety.java => Deforesting.java} (82%) diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Cycle.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Cycle.java index 264237532..652b5470d 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Cycle.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Cycle.java @@ -1,7 +1,7 @@ package com.jnape.palatable.lambda.functions.builtin.fn1; import com.jnape.palatable.lambda.functions.Fn1; -import com.jnape.palatable.lambda.iteration.CyclicIterator; +import com.jnape.palatable.lambda.iteration.CyclicIterable; import static java.util.Arrays.asList; @@ -20,7 +20,7 @@ private Cycle() { @Override public Iterable apply(Iterable as) { - return () -> new CyclicIterator<>(as.iterator()); + return new CyclicIterable<>(as); } @SuppressWarnings("unchecked") diff --git a/src/main/java/com/jnape/palatable/lambda/iteration/CyclicIterable.java b/src/main/java/com/jnape/palatable/lambda/iteration/CyclicIterable.java new file mode 100644 index 000000000..6d08dbcf2 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/iteration/CyclicIterable.java @@ -0,0 +1,19 @@ +package com.jnape.palatable.lambda.iteration; + +import java.util.Iterator; + +public final class CyclicIterable implements Iterable { + private final Iterable as; + + public CyclicIterable(Iterable as) { + while (as instanceof CyclicIterable) { + as = ((CyclicIterable) as).as; + } + this.as = as; + } + + @Override + public Iterator iterator() { + return new CyclicIterator<>(as.iterator()); + } +} diff --git a/src/test/java/com/jnape/palatable/lambda/iteration/CyclicIterableTest.java b/src/test/java/com/jnape/palatable/lambda/iteration/CyclicIterableTest.java new file mode 100644 index 000000000..66938afa1 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/iteration/CyclicIterableTest.java @@ -0,0 +1,16 @@ +package com.jnape.palatable.lambda.iteration; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.traitor.annotations.TestTraits; +import com.jnape.palatable.traitor.runners.Traits; +import org.junit.runner.RunWith; +import testsupport.traits.Deforesting; + +@RunWith(Traits.class) +public class CyclicIterableTest { + + @TestTraits({Deforesting.class}) + public Fn1, Iterable> testSubject() { + return CyclicIterable::new; + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/iteration/DroppingIterableTest.java b/src/test/java/com/jnape/palatable/lambda/iteration/DroppingIterableTest.java index 4509dc92f..52ab7899f 100644 --- a/src/test/java/com/jnape/palatable/lambda/iteration/DroppingIterableTest.java +++ b/src/test/java/com/jnape/palatable/lambda/iteration/DroppingIterableTest.java @@ -4,12 +4,12 @@ import com.jnape.palatable.traitor.annotations.TestTraits; import com.jnape.palatable.traitor.runners.Traits; import org.junit.runner.RunWith; -import testsupport.traits.NestingStackSafety; +import testsupport.traits.Deforesting; @RunWith(Traits.class) public class DroppingIterableTest { - @TestTraits({NestingStackSafety.class}) + @TestTraits({Deforesting.class}) public Fn1, Iterable> testSubject() { return x -> new DroppingIterable<>(1, x); } diff --git a/src/test/java/com/jnape/palatable/lambda/iteration/SnocIterableTest.java b/src/test/java/com/jnape/palatable/lambda/iteration/SnocIterableTest.java index a04d8a079..258e872a3 100644 --- a/src/test/java/com/jnape/palatable/lambda/iteration/SnocIterableTest.java +++ b/src/test/java/com/jnape/palatable/lambda/iteration/SnocIterableTest.java @@ -4,12 +4,12 @@ import com.jnape.palatable.traitor.annotations.TestTraits; import com.jnape.palatable.traitor.runners.Traits; import org.junit.runner.RunWith; -import testsupport.traits.NestingStackSafety; +import testsupport.traits.Deforesting; @RunWith(Traits.class) public class SnocIterableTest { - @TestTraits({NestingStackSafety.class}) + @TestTraits({Deforesting.class}) public Fn1, Iterable> testSubject() { return xs -> new SnocIterable<>(1, xs); } diff --git a/src/test/java/testsupport/traits/NestingStackSafety.java b/src/test/java/testsupport/traits/Deforesting.java similarity index 82% rename from src/test/java/testsupport/traits/NestingStackSafety.java rename to src/test/java/testsupport/traits/Deforesting.java index af3065c75..cb38b87ef 100644 --- a/src/test/java/testsupport/traits/NestingStackSafety.java +++ b/src/test/java/testsupport/traits/Deforesting.java @@ -6,7 +6,7 @@ import static com.jnape.palatable.lambda.functions.builtin.fn1.Repeat.repeat; import static com.jnape.palatable.lambda.functions.builtin.fn3.Times.times; -public final class NestingStackSafety implements Trait, Iterable>> { +public final class Deforesting implements Trait, Iterable>> { @Override public void test(Fn1, Iterable> fn) { From 1eeaf9290b0d731f704110d466f7b87894fc2dd2 Mon Sep 17 00:00:00 2001 From: jnape Date: Mon, 19 Feb 2018 17:34:49 -0600 Subject: [PATCH 16/52] Distinct automatically deforests --- .../functions/builtin/fn1/Distinct.java | 8 ++----- .../lambda/iteration/DistinctIterable.java | 24 +++++++++++++++++++ .../iteration/DistinctIterableTest.java | 16 +++++++++++++ 3 files changed, 42 insertions(+), 6 deletions(-) create mode 100644 src/main/java/com/jnape/palatable/lambda/iteration/DistinctIterable.java create mode 100644 src/test/java/com/jnape/palatable/lambda/iteration/DistinctIterableTest.java diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Distinct.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Distinct.java index 197f90abd..8b39ac2a4 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Distinct.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Distinct.java @@ -1,10 +1,7 @@ package com.jnape.palatable.lambda.functions.builtin.fn1; import com.jnape.palatable.lambda.functions.Fn1; - -import java.util.HashMap; - -import static com.jnape.palatable.lambda.functions.builtin.fn2.Filter.filter; +import com.jnape.palatable.lambda.iteration.DistinctIterable; /** * Return an {@link Iterable} of the distinct values from the given input {@link Iterable}. @@ -19,8 +16,7 @@ private Distinct() { @Override public Iterable apply(Iterable as) { - HashMap known = new HashMap<>(); - return filter(a -> known.putIfAbsent(a, true) == null, as); + return new DistinctIterable<>(as); } @SuppressWarnings("unchecked") diff --git a/src/main/java/com/jnape/palatable/lambda/iteration/DistinctIterable.java b/src/main/java/com/jnape/palatable/lambda/iteration/DistinctIterable.java new file mode 100644 index 000000000..447b76168 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/iteration/DistinctIterable.java @@ -0,0 +1,24 @@ +package com.jnape.palatable.lambda.iteration; + +import java.util.HashMap; +import java.util.Iterator; + +import static com.jnape.palatable.lambda.functions.builtin.fn2.Filter.filter; + +public final class DistinctIterable implements Iterable { + + private final Iterable as; + + public DistinctIterable(Iterable as) { + while (as instanceof DistinctIterable) { + as = ((DistinctIterable) as).as; + } + this.as = as; + } + + @Override + public Iterator iterator() { + HashMap known = new HashMap<>(); + return filter(a -> known.putIfAbsent(a, true) == null, as).iterator(); + } +} diff --git a/src/test/java/com/jnape/palatable/lambda/iteration/DistinctIterableTest.java b/src/test/java/com/jnape/palatable/lambda/iteration/DistinctIterableTest.java new file mode 100644 index 000000000..69a2bd252 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/iteration/DistinctIterableTest.java @@ -0,0 +1,16 @@ +package com.jnape.palatable.lambda.iteration; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.traitor.annotations.TestTraits; +import com.jnape.palatable.traitor.runners.Traits; +import org.junit.runner.RunWith; +import testsupport.traits.Deforesting; + +@RunWith(Traits.class) +public class DistinctIterableTest { + + @TestTraits({Deforesting.class}) + public Fn1, Iterable> testSubject() { + return DistinctIterable::new; + } +} \ No newline at end of file From 9f62caf840d430a13167397df768bb45b5a3c627 Mon Sep 17 00:00:00 2001 From: jnape Date: Mon, 19 Feb 2018 18:14:08 -0600 Subject: [PATCH 17/52] Map automatically deforests via fusion --- .../lambda/functions/builtin/fn2/Map.java | 4 +-- .../lambda/iteration/MappingIterable.java | 33 +++++++++++++++++++ .../lambda/iteration/MappingIterableTest.java | 19 +++++++++++ 3 files changed, 54 insertions(+), 2 deletions(-) create mode 100644 src/main/java/com/jnape/palatable/lambda/iteration/MappingIterable.java create mode 100644 src/test/java/com/jnape/palatable/lambda/iteration/MappingIterableTest.java diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Map.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Map.java index 776141d15..2b48c2a07 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Map.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Map.java @@ -2,7 +2,7 @@ import com.jnape.palatable.lambda.functions.Fn1; import com.jnape.palatable.lambda.functions.Fn2; -import com.jnape.palatable.lambda.iteration.MappingIterator; +import com.jnape.palatable.lambda.iteration.MappingIterable; import java.util.function.Function; @@ -22,7 +22,7 @@ private Map() { @Override public Iterable apply(Function fn, Iterable as) { - return () -> new MappingIterator<>(fn, as.iterator()); + return new MappingIterable<>(fn, as); } @SuppressWarnings("unchecked") diff --git a/src/main/java/com/jnape/palatable/lambda/iteration/MappingIterable.java b/src/main/java/com/jnape/palatable/lambda/iteration/MappingIterable.java new file mode 100644 index 000000000..1730a6838 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/iteration/MappingIterable.java @@ -0,0 +1,33 @@ +package com.jnape.palatable.lambda.iteration; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.function.Function; + +import static com.jnape.palatable.lambda.functions.builtin.fn3.FoldLeft.foldLeft; +import static java.util.Collections.singletonList; + +public final class MappingIterable implements Iterable { + private final Iterable as; + private final List mappers; + + @SuppressWarnings("unchecked") + public MappingIterable(Function fn, Iterable as) { + List mappers = new ArrayList<>(singletonList(fn)); + while (as instanceof MappingIterable) { + MappingIterable nested = (MappingIterable) as; + as = (Iterable) nested.as; + mappers.addAll(0, nested.mappers); + } + this.as = as; + this.mappers = mappers; + } + + @Override + @SuppressWarnings("unchecked") + public Iterator iterator() { + Function fnComposedOnTheHeap = o -> foldLeft((x, fn) -> fn.apply(x), o, mappers); + return new MappingIterator<>(fnComposedOnTheHeap, as.iterator()); + } +} diff --git a/src/test/java/com/jnape/palatable/lambda/iteration/MappingIterableTest.java b/src/test/java/com/jnape/palatable/lambda/iteration/MappingIterableTest.java new file mode 100644 index 000000000..fdddf37e0 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/iteration/MappingIterableTest.java @@ -0,0 +1,19 @@ +package com.jnape.palatable.lambda.iteration; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.traitor.annotations.TestTraits; +import com.jnape.palatable.traitor.runners.Traits; +import org.junit.runner.RunWith; +import testsupport.traits.Deforesting; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Map.map; + +@RunWith(Traits.class) +public class MappingIterableTest { + + @TestTraits({Deforesting.class}) + public Fn1, Iterable> testSubject() { + return map(id()); + } +} \ No newline at end of file From 0821efafb3f18e12459d7653d9d3c689c1cf1225 Mon Sep 17 00:00:00 2001 From: jnape Date: Sat, 24 Feb 2018 16:42:56 -0600 Subject: [PATCH 18/52] DropWhile automatically deforests --- .../functions/builtin/fn2/DropWhile.java | 4 +-- .../iteration/PredicatedDroppingIterable.java | 32 +++++++++++++++++++ .../functions/builtin/fn2/DropWhileTest.java | 2 +- .../PredicatedDroppingIterableTest.java | 19 +++++++++++ 4 files changed, 54 insertions(+), 3 deletions(-) create mode 100644 src/main/java/com/jnape/palatable/lambda/iteration/PredicatedDroppingIterable.java create mode 100644 src/test/java/com/jnape/palatable/lambda/iteration/PredicatedDroppingIterableTest.java diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/DropWhile.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/DropWhile.java index d89d7f46e..9b87e63b0 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/DropWhile.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/DropWhile.java @@ -2,7 +2,7 @@ import com.jnape.palatable.lambda.functions.Fn1; import com.jnape.palatable.lambda.functions.Fn2; -import com.jnape.palatable.lambda.iteration.PredicatedDroppingIterator; +import com.jnape.palatable.lambda.iteration.PredicatedDroppingIterable; import java.util.function.Function; @@ -25,7 +25,7 @@ private DropWhile() { @Override public Iterable apply(Function predicate, Iterable as) { - return () -> new PredicatedDroppingIterator<>(predicate, as.iterator()); + return new PredicatedDroppingIterable<>(predicate, as); } @SuppressWarnings("unchecked") diff --git a/src/main/java/com/jnape/palatable/lambda/iteration/PredicatedDroppingIterable.java b/src/main/java/com/jnape/palatable/lambda/iteration/PredicatedDroppingIterable.java new file mode 100644 index 000000000..e4b2ca429 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/iteration/PredicatedDroppingIterable.java @@ -0,0 +1,32 @@ +package com.jnape.palatable.lambda.iteration; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.function.Function; + +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; + + public PredicatedDroppingIterable(Function predicate, Iterable as) { + List> predicates = new ArrayList<>(singletonList(predicate)); + + while (as instanceof PredicatedDroppingIterable) { + PredicatedDroppingIterable nested = (PredicatedDroppingIterable) as; + as = nested.as; + predicates.addAll(nested.predicates); + } + this.predicates = predicates; + this.as = as; + } + + @Override + public Iterator iterator() { + Function metaPredicate = a -> any(p -> p.apply(a), predicates); + return new PredicatedDroppingIterator<>(metaPredicate, as.iterator()); + } +} 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 352aa47b4..b57ae8085 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 @@ -23,7 +23,7 @@ public class DropWhileTest { @TestTraits({Laziness.class, ImmutableIteration.class, FiniteIteration.class, EmptyIterableSupport.class}) public Fn1, Iterable> createTestSubject() { - return dropWhile(constantly(true)); + return dropWhile(constantly(false)); } @Test diff --git a/src/test/java/com/jnape/palatable/lambda/iteration/PredicatedDroppingIterableTest.java b/src/test/java/com/jnape/palatable/lambda/iteration/PredicatedDroppingIterableTest.java new file mode 100644 index 000000000..2202c2b98 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/iteration/PredicatedDroppingIterableTest.java @@ -0,0 +1,19 @@ +package com.jnape.palatable.lambda.iteration; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.traitor.annotations.TestTraits; +import com.jnape.palatable.traitor.runners.Traits; +import org.junit.runner.RunWith; +import testsupport.traits.Deforesting; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; +import static com.jnape.palatable.lambda.functions.builtin.fn2.DropWhile.dropWhile; + +@RunWith(Traits.class) +public class PredicatedDroppingIterableTest { + + @TestTraits({Deforesting.class}) + public Fn1, Iterable> testSubject() { + return dropWhile(constantly(false)); + } +} \ No newline at end of file From e1c9275e497d8151accf1dc5acade0af86fc2381 Mon Sep 17 00:00:00 2001 From: jnape Date: Sat, 24 Feb 2018 17:21:18 -0600 Subject: [PATCH 19/52] Filter automatically deforests --- .../lambda/functions/builtin/fn2/Filter.java | 4 +-- .../lambda/iteration/FilteringIterable.java | 31 +++++++++++++++++++ .../iteration/FilteringIterableTest.java | 19 ++++++++++++ 3 files changed, 52 insertions(+), 2 deletions(-) create mode 100644 src/main/java/com/jnape/palatable/lambda/iteration/FilteringIterable.java create mode 100644 src/test/java/com/jnape/palatable/lambda/iteration/FilteringIterableTest.java diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Filter.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Filter.java index 0e9e51acd..787978318 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Filter.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Filter.java @@ -2,7 +2,7 @@ import com.jnape.palatable.lambda.functions.Fn1; import com.jnape.palatable.lambda.functions.Fn2; -import com.jnape.palatable.lambda.iteration.FilteringIterator; +import com.jnape.palatable.lambda.iteration.FilteringIterable; import java.util.function.Function; @@ -23,7 +23,7 @@ private Filter() { @Override public Iterable apply(Function predicate, Iterable as) { - return () -> new FilteringIterator<>(predicate, as.iterator()); + return new FilteringIterable<>(predicate, as); } @SuppressWarnings("unchecked") diff --git a/src/main/java/com/jnape/palatable/lambda/iteration/FilteringIterable.java b/src/main/java/com/jnape/palatable/lambda/iteration/FilteringIterable.java new file mode 100644 index 000000000..bd7be982c --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/iteration/FilteringIterable.java @@ -0,0 +1,31 @@ +package com.jnape.palatable.lambda.iteration; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.function.Function; + +import static com.jnape.palatable.lambda.functions.builtin.fn2.All.all; +import static java.util.Collections.singletonList; + +public final class FilteringIterable implements Iterable { + private final List> predicates; + private final Iterable as; + + public FilteringIterable(Function predicate, Iterable as) { + List> predicates = new ArrayList<>(singletonList(predicate)); + while (as instanceof FilteringIterable) { + FilteringIterable nested = (FilteringIterable) as; + predicates.addAll(nested.predicates); + as = nested.as; + } + this.predicates = predicates; + this.as = as; + } + + @Override + public Iterator iterator() { + Function metaPredicate = a -> all(p -> p.apply(a), predicates); + return new FilteringIterator<>(metaPredicate, as.iterator()); + } +} diff --git a/src/test/java/com/jnape/palatable/lambda/iteration/FilteringIterableTest.java b/src/test/java/com/jnape/palatable/lambda/iteration/FilteringIterableTest.java new file mode 100644 index 000000000..7d1c645d6 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/iteration/FilteringIterableTest.java @@ -0,0 +1,19 @@ +package com.jnape.palatable.lambda.iteration; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.traitor.annotations.TestTraits; +import com.jnape.palatable.traitor.runners.Traits; +import org.junit.runner.RunWith; +import testsupport.traits.Deforesting; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Filter.filter; + +@RunWith(Traits.class) +public class FilteringIterableTest { + + @TestTraits({Deforesting.class}) + public Fn1, Iterable> testSubject() { + return filter(constantly(true)); + } +} \ No newline at end of file From 1a8548f777af81be446236367691a9316679ab4f Mon Sep 17 00:00:00 2001 From: jnape Date: Sat, 24 Feb 2018 17:42:32 -0600 Subject: [PATCH 20/52] Safe to assume Java 8, since it's almost two major versions behind. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 24a49ae3d..d5f7e440c 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ [![Build Status](https://img.shields.io/travis/palatable/lambda/master.svg)](https://travis-ci.org/palatable/lambda) [![Lambda](https://img.shields.io/maven-central/v/com.jnape.palatable/lambda.svg)](http://search.maven.org/#search%7Cga%7C1%7Ccom.jnape.palatable.lambda) -Functional patterns for Java 8 +Functional patterns for Java #### Table of Contents From 47eccfe0eeefab3ec599a94b1eef40d1510bdc0a Mon Sep 17 00:00:00 2001 From: jnape Date: Sat, 24 Feb 2018 18:00:32 -0600 Subject: [PATCH 21/52] Reverse deforests (to direct iteration if nesting-level % 2 == 1) speeding up other tests --- .../lambda/functions/builtin/fn1/Reverse.java | 4 ++-- .../lambda/iteration/ReversingIterable.java | 24 +++++++++++++++++++ .../functions/builtin/fn1/TailsTest.java | 2 +- .../iteration/ConcatenatingIteratorTest.java | 2 +- .../lambda/iteration/ConsingIteratorTest.java | 2 +- .../iteration/ReversingIterableTest.java | 18 ++++++++++++++ 6 files changed, 47 insertions(+), 5 deletions(-) create mode 100644 src/main/java/com/jnape/palatable/lambda/iteration/ReversingIterable.java create mode 100644 src/test/java/com/jnape/palatable/lambda/iteration/ReversingIterableTest.java diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Reverse.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Reverse.java index 0ed3265f7..7b50cecbc 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Reverse.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Reverse.java @@ -1,7 +1,7 @@ package com.jnape.palatable.lambda.functions.builtin.fn1; import com.jnape.palatable.lambda.functions.Fn1; -import com.jnape.palatable.lambda.iteration.ReversingIterator; +import com.jnape.palatable.lambda.iteration.ReversingIterable; /** * Given an Iterable, return a reversed representation of that Iterable. Note that reversing @@ -18,7 +18,7 @@ private Reverse() { @Override public Iterable apply(Iterable as) { - return () -> new ReversingIterator<>(as.iterator()); + return new ReversingIterable<>(as); } @SuppressWarnings("unchecked") diff --git a/src/main/java/com/jnape/palatable/lambda/iteration/ReversingIterable.java b/src/main/java/com/jnape/palatable/lambda/iteration/ReversingIterable.java new file mode 100644 index 000000000..df4c25e45 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/iteration/ReversingIterable.java @@ -0,0 +1,24 @@ +package com.jnape.palatable.lambda.iteration; + +import java.util.Iterator; + +public final class ReversingIterable implements Iterable { + private final Iterable as; + private final boolean reverse; + + public ReversingIterable(Iterable as) { + boolean reverse = true; + while (as instanceof ReversingIterable) { + ReversingIterable nested = (ReversingIterable) as; + as = nested.as; + reverse = !nested.reverse; + } + this.as = as; + this.reverse = reverse; + } + + @Override + public Iterator iterator() { + return reverse ? new ReversingIterator<>(as.iterator()) : as.iterator(); + } +} diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/TailsTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/TailsTest.java index 15960accf..45b3a1b25 100644 --- a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/TailsTest.java +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/TailsTest.java @@ -49,6 +49,6 @@ public void nonEmpty() { @Test public void largeNumberOfElements() { - assertEquals(just(emptyList()), last(tails(take(100000, repeat(1))))); + assertEquals(just(emptyList()), last(tails(take(10_000, repeat(1))))); } } \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/iteration/ConcatenatingIteratorTest.java b/src/test/java/com/jnape/palatable/lambda/iteration/ConcatenatingIteratorTest.java index f1191ae87..b22ebfe32 100644 --- a/src/test/java/com/jnape/palatable/lambda/iteration/ConcatenatingIteratorTest.java +++ b/src/test/java/com/jnape/palatable/lambda/iteration/ConcatenatingIteratorTest.java @@ -85,7 +85,7 @@ public void nextPullsFromXsFirstThenFromYs() { @Test public void stackSafety() { - Integer stackBlowingNumber = 50000; + Integer stackBlowingNumber = 10_000; Iterable> xss = map(Collections::singleton, take(stackBlowingNumber, iterate(x -> x + 1, 1))); Iterable deeplyNestedConcat = foldRight((xs, ys) -> () -> new ConcatenatingIterator<>(xs, ys), (Iterable) Collections.emptySet(), diff --git a/src/test/java/com/jnape/palatable/lambda/iteration/ConsingIteratorTest.java b/src/test/java/com/jnape/palatable/lambda/iteration/ConsingIteratorTest.java index b0eb89832..68f99d943 100644 --- a/src/test/java/com/jnape/palatable/lambda/iteration/ConsingIteratorTest.java +++ b/src/test/java/com/jnape/palatable/lambda/iteration/ConsingIteratorTest.java @@ -50,7 +50,7 @@ public void doesNotHaveNextIfNoElementsLeft() { @Test public void stackSafety() { - Integer stackBlowingNumber = 1000000; + Integer stackBlowingNumber = 10_000; Iterable ints = foldRight((x, acc) -> () -> new ConsingIterator<>(x, acc), (Iterable) Collections.emptyList(), take(stackBlowingNumber, iterate(x -> x + 1, 1))); diff --git a/src/test/java/com/jnape/palatable/lambda/iteration/ReversingIterableTest.java b/src/test/java/com/jnape/palatable/lambda/iteration/ReversingIterableTest.java new file mode 100644 index 000000000..e70b7cbd9 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/iteration/ReversingIterableTest.java @@ -0,0 +1,18 @@ +package com.jnape.palatable.lambda.iteration; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.traitor.annotations.TestTraits; +import com.jnape.palatable.traitor.runners.Traits; +import org.junit.runner.RunWith; +import testsupport.traits.Deforesting; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Reverse.reverse; + +@RunWith(Traits.class) +public class ReversingIterableTest { + + @TestTraits({Deforesting.class}) + public Fn1, Iterable> testSubject() { + return reverse(); + } +} \ No newline at end of file From 9d243788025da828c7c26788861ffd7d8ba8050f Mon Sep 17 00:00:00 2001 From: jnape Date: Sat, 24 Feb 2018 18:21:12 -0600 Subject: [PATCH 22/52] Take deforests --- .../lambda/functions/builtin/fn2/Take.java | 4 +-- .../lambda/iteration/TakingIterable.java | 25 +++++++++++++++++++ .../lambda/iteration/TakingIterableTest.java | 18 +++++++++++++ 3 files changed, 45 insertions(+), 2 deletions(-) create mode 100644 src/main/java/com/jnape/palatable/lambda/iteration/TakingIterable.java create mode 100644 src/test/java/com/jnape/palatable/lambda/iteration/TakingIterableTest.java diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Take.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Take.java index 679e7f8a1..ed10f75bc 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Take.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Take.java @@ -2,7 +2,7 @@ import com.jnape.palatable.lambda.functions.Fn1; import com.jnape.palatable.lambda.functions.Fn2; -import com.jnape.palatable.lambda.iteration.TakingIterator; +import com.jnape.palatable.lambda.iteration.TakingIterable; /** * Lazily limit the Iterable to n elements by returning an Iterable that stops @@ -22,7 +22,7 @@ private Take() { @Override public Iterable apply(Integer n, Iterable as) { - return () -> new TakingIterator<>(n, as.iterator()); + return new TakingIterable<>(n, as); } @SuppressWarnings("unchecked") diff --git a/src/main/java/com/jnape/palatable/lambda/iteration/TakingIterable.java b/src/main/java/com/jnape/palatable/lambda/iteration/TakingIterable.java new file mode 100644 index 000000000..ce67f8e89 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/iteration/TakingIterable.java @@ -0,0 +1,25 @@ +package com.jnape.palatable.lambda.iteration; + +import java.util.Iterator; + +import static java.lang.Math.min; + +public final class TakingIterable implements Iterable { + private final int n; + private final Iterable as; + + public TakingIterable(int n, Iterable as) { + while (as instanceof TakingIterable) { + TakingIterable nested = (TakingIterable) as; + n = min(n, nested.n); + as = nested.as; + } + this.n = n; + this.as = as; + } + + @Override + public Iterator iterator() { + return new TakingIterator<>(n, as.iterator()); + } +} diff --git a/src/test/java/com/jnape/palatable/lambda/iteration/TakingIterableTest.java b/src/test/java/com/jnape/palatable/lambda/iteration/TakingIterableTest.java new file mode 100644 index 000000000..b1bd84454 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/iteration/TakingIterableTest.java @@ -0,0 +1,18 @@ +package com.jnape.palatable.lambda.iteration; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.traitor.annotations.TestTraits; +import com.jnape.palatable.traitor.runners.Traits; +import org.junit.runner.RunWith; +import testsupport.traits.Deforesting; + +import static com.jnape.palatable.lambda.functions.builtin.fn2.Take.take; + +@RunWith(Traits.class) +public class TakingIterableTest { + + @TestTraits({Deforesting.class}) + public Fn1, Iterable> testSubject() { + return take(1); + } +} \ No newline at end of file From 607aed215e8ab38c96f153cbe8dde1daab4ce62a Mon Sep 17 00:00:00 2001 From: jnape Date: Sat, 24 Feb 2018 18:28:01 -0600 Subject: [PATCH 23/52] TakeWhile deforests --- .../functions/builtin/fn2/TakeWhile.java | 4 +-- .../iteration/PredicatedTakingIterable.java | 31 +++++++++++++++++++ .../PredicatedTakingIterableTest.java | 19 ++++++++++++ 3 files changed, 52 insertions(+), 2 deletions(-) create mode 100644 src/main/java/com/jnape/palatable/lambda/iteration/PredicatedTakingIterable.java create mode 100644 src/test/java/com/jnape/palatable/lambda/iteration/PredicatedTakingIterableTest.java diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/TakeWhile.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/TakeWhile.java index 7e3c0b073..0b7bc7621 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/TakeWhile.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/TakeWhile.java @@ -2,7 +2,7 @@ import com.jnape.palatable.lambda.functions.Fn1; import com.jnape.palatable.lambda.functions.Fn2; -import com.jnape.palatable.lambda.iteration.PredicatedTakingIterator; +import com.jnape.palatable.lambda.iteration.PredicatedTakingIterable; import java.util.function.Function; @@ -24,7 +24,7 @@ private TakeWhile() { @Override public Iterable apply(Function predicate, Iterable as) { - return () -> new PredicatedTakingIterator<>(predicate, as.iterator()); + return new PredicatedTakingIterable<>(predicate, as); } @SuppressWarnings("unchecked") diff --git a/src/main/java/com/jnape/palatable/lambda/iteration/PredicatedTakingIterable.java b/src/main/java/com/jnape/palatable/lambda/iteration/PredicatedTakingIterable.java new file mode 100644 index 000000000..4c80ccdb8 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/iteration/PredicatedTakingIterable.java @@ -0,0 +1,31 @@ +package com.jnape.palatable.lambda.iteration; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.function.Function; + +import static com.jnape.palatable.lambda.functions.builtin.fn2.All.all; +import static java.util.Collections.singletonList; + +public final class PredicatedTakingIterable implements Iterable { + private final List> predicates; + private final Iterable as; + + public PredicatedTakingIterable(Function predicate, Iterable as) { + List> predicates = new ArrayList<>(singletonList(predicate)); + while (as instanceof PredicatedTakingIterable) { + PredicatedTakingIterable nested = (PredicatedTakingIterable) as; + as = nested.as; + predicates.addAll(nested.predicates); + } + this.predicates = predicates; + this.as = as; + } + + @Override + public Iterator iterator() { + Function metaPredicate = a -> all(p -> p.apply(a), predicates); + return new PredicatedTakingIterator<>(metaPredicate, as.iterator()); + } +} diff --git a/src/test/java/com/jnape/palatable/lambda/iteration/PredicatedTakingIterableTest.java b/src/test/java/com/jnape/palatable/lambda/iteration/PredicatedTakingIterableTest.java new file mode 100644 index 000000000..bb8738cc5 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/iteration/PredicatedTakingIterableTest.java @@ -0,0 +1,19 @@ +package com.jnape.palatable.lambda.iteration; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.traitor.annotations.TestTraits; +import com.jnape.palatable.traitor.runners.Traits; +import org.junit.runner.RunWith; +import testsupport.traits.Deforesting; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; +import static com.jnape.palatable.lambda.functions.builtin.fn2.TakeWhile.takeWhile; + +@RunWith(Traits.class) +public class PredicatedTakingIterableTest { + + @TestTraits({Deforesting.class}) + public Fn1, Iterable> testSubject() { + return takeWhile(constantly(true)); + } +} \ No newline at end of file From 89ff37ccb7fcb8a540a0dabbc511875795184eb6 Mon Sep 17 00:00:00 2001 From: jnape Date: Sat, 24 Feb 2018 18:29:08 -0600 Subject: [PATCH 24/52] Updating CHANGELOG --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 87e42c10e..ca915088e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/). - `Identity`, `Compose`, and `Const` functors all have better `toString` implementations - `Into3-8` now supports functions with parameter variance - `HListLens#tail` is now covariant in `Tail` parameter -- More functions now automatically deforest nested calls (`drop`, `snoc`) +- More functions now automatically deforest nested calls (`cons`, `cycle`, `distinct`, `drop`, `dropwhile`, `filter`, `map`, `reverse`, `snoc`, `take`, `takewhile`, `tail`) ### Added - `BoundedBifunctor`, a `Bifunctor` super type that offers upper bounds for both parameters From 14a21c62da6fea2f869e56208e8f0b00fb820415 Mon Sep 17 00:00:00 2001 From: jnape Date: Sat, 24 Feb 2018 18:39:05 -0600 Subject: [PATCH 25/52] Adding MagnetizeBy and Magnetize --- CHANGELOG.md | 1 + .../functions/builtin/fn1/Magnetize.java | 33 ++++++++++ .../functions/builtin/fn2/MagnetizeBy.java | 61 +++++++++++++++++++ .../functions/builtin/fn1/MagnetizeTest.java | 38 ++++++++++++ .../builtin/fn2/MagnetizeByTest.java | 56 +++++++++++++++++ 5 files changed, 189 insertions(+) create mode 100644 src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Magnetize.java create mode 100644 src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/MagnetizeBy.java create mode 100644 src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/MagnetizeTest.java create mode 100644 src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/MagnetizeByTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md index ca915088e..922f7ae73 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/). - `Kleisli`, the abstract representation of a `Kleisli` arrow (`Monad#flatMap`) as an `Fn1` - `These`, a `CoProduct3` of `A`, `B`, or `Tuple2` - `Span`, for splitting an `Iterable` into contiguous elements matching a predicate +- `MagnetizeBy` and `Magnetize`, for grouping elements by pairwise predicate tests ### Deprecated - `Either#trying` in favor of `Try#trying` diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Magnetize.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Magnetize.java new file mode 100644 index 000000000..47c4d3ed8 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Magnetize.java @@ -0,0 +1,33 @@ +package com.jnape.palatable.lambda.functions.builtin.fn1; + +import com.jnape.palatable.lambda.functions.Fn1; + +import static com.jnape.palatable.lambda.functions.builtin.fn2.Eq.eq; +import static com.jnape.palatable.lambda.functions.builtin.fn2.MagnetizeBy.magnetizeBy; + +/** + * {@link Magnetize} an {@link Iterable} using value equality as the magnetizing function. + * + * @param the Iterable element type + */ +public final class Magnetize implements Fn1, Iterable>> { + + private static final Magnetize INSTANCE = new Magnetize(); + + private Magnetize() { + } + + @Override + public Iterable> apply(Iterable as) { + return magnetizeBy(eq().toBiFunction(), as); + } + + @SuppressWarnings("unchecked") + public static Magnetize magnetize() { + return INSTANCE; + } + + public static Iterable> magnetize(Iterable as) { + return Magnetize.magnetize().apply(as); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/MagnetizeBy.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/MagnetizeBy.java new file mode 100644 index 000000000..1e89e53a3 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/MagnetizeBy.java @@ -0,0 +1,61 @@ +package com.jnape.palatable.lambda.functions.builtin.fn2; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.Fn2; + +import java.util.Collections; +import java.util.function.BiFunction; + +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; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Size.size; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Uncons.uncons; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Cons.cons; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Drop.drop; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Into.into; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Unfoldr.unfoldr; + +/** + * Given a binary predicate and an {@link Iterable}<A>, return an {@link Iterable}<{@link + * Iterable}<A>> of the contiguous groups of elements that match the predicate pairwise. + *

+ * Example: magnetizeBy((x, y) -> x <= y, asList(1, 2, 3, 2, 2, 3, 2, 1)); // [[1, 2, 3], [2, 2, 3], [2], + * [1]] + * + * @param + */ +public final class MagnetizeBy implements Fn2, Iterable, Iterable>> { + + private static final MagnetizeBy INSTANCE = new MagnetizeBy(); + + private MagnetizeBy() { + } + + @Override + public Iterable> apply(BiFunction predicate, Iterable as) { + return () -> uncons(as).fmap(into((A head, Iterable tail) -> { + Iterable group = cons(head, unfoldr(into((pivot, ys) -> uncons(ys) + .flatMap(into((y, recurse) -> predicate.apply(pivot, y) + ? just(tuple(y, tuple(y, recurse))) + : nothing()))), tuple(head, tail))); + return cons(group, () -> apply(predicate, drop(size(group).intValue(), as)).iterator()); + })).orElseGet(() -> Collections::emptyIterator).iterator(); + } + + @SuppressWarnings("unchecked") + public static MagnetizeBy magnetizeBy() { + return INSTANCE; + } + + public static Fn1, Iterable>> magnetizeBy( + BiFunction predicate) { + return MagnetizeBy.magnetizeBy().apply(predicate); + } + + public static Iterable> magnetizeBy( + BiFunction predicate, + Iterable as) { + return MagnetizeBy.magnetizeBy(predicate).apply(as); + } +} diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/MagnetizeTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/MagnetizeTest.java new file mode 100644 index 000000000..87f5a983e --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/MagnetizeTest.java @@ -0,0 +1,38 @@ +package com.jnape.palatable.lambda.functions.builtin.fn1; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.traitor.annotations.TestTraits; +import com.jnape.palatable.traitor.runners.Traits; +import org.junit.Test; +import org.junit.runner.RunWith; +import testsupport.traits.EmptyIterableSupport; +import testsupport.traits.FiniteIteration; +import testsupport.traits.ImmutableIteration; +import testsupport.traits.InfiniteIterableSupport; +import testsupport.traits.Laziness; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Magnetize.magnetize; +import static java.util.Arrays.asList; +import static org.hamcrest.collection.IsIterableContainingInOrder.contains; +import static org.junit.Assert.assertThat; +import static testsupport.matchers.IterableMatcher.iterates; + +@RunWith(Traits.class) +public class MagnetizeTest { + + @TestTraits({EmptyIterableSupport.class, InfiniteIterableSupport.class, FiniteIteration.class, ImmutableIteration.class, Laziness.class}) + public Fn1, Iterable>> testSubject() { + return magnetize(); + } + + @Test + @SuppressWarnings("unchecked") + public void magnetizesElementsByPredicateOutcome() { + assertThat(magnetize(asList(1, 1, 2, 3, 3, 3, 2, 2, 1)), + contains(iterates(1, 1), + iterates(2), + iterates(3, 3, 3), + iterates(2, 2), + iterates(1))); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/MagnetizeByTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/MagnetizeByTest.java new file mode 100644 index 000000000..1995332f2 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/MagnetizeByTest.java @@ -0,0 +1,56 @@ +package com.jnape.palatable.lambda.functions.builtin.fn2; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.traitor.annotations.TestTraits; +import com.jnape.palatable.traitor.runners.Traits; +import org.junit.Test; +import org.junit.runner.RunWith; +import testsupport.traits.EmptyIterableSupport; +import testsupport.traits.FiniteIteration; +import testsupport.traits.ImmutableIteration; +import testsupport.traits.InfiniteIterableSupport; +import testsupport.traits.Laziness; + +import java.util.function.BiFunction; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Last.last; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Repeat.repeat; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Eq.eq; +import static com.jnape.palatable.lambda.functions.builtin.fn2.MagnetizeBy.magnetizeBy; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Take.take; +import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; +import static org.hamcrest.collection.IsIterableContainingInOrder.contains; +import static org.junit.Assert.assertThat; +import static testsupport.matchers.IterableMatcher.isEmpty; +import static testsupport.matchers.IterableMatcher.iterates; + +@RunWith(Traits.class) +public class MagnetizeByTest { + + @TestTraits({EmptyIterableSupport.class, InfiniteIterableSupport.class, FiniteIteration.class, ImmutableIteration.class, Laziness.class}) + public Fn1, Iterable>> testSubject() { + return magnetizeBy(eq().toBiFunction()); + } + + @Test + @SuppressWarnings("unchecked") + public void magnetizesElementsByPredicateOutcome() { + BiFunction lte = (x, y) -> x <= y; + assertThat(magnetizeBy(lte, emptyList()), isEmpty()); + assertThat(magnetizeBy(lte, singletonList(1)), contains(iterates(1))); + assertThat(magnetizeBy(lte, asList(1, 2, 3, 2, 2, 3, 2, 1)), + contains(iterates(1, 2, 3), + iterates(2, 2, 3), + iterates(2), + iterates(1))); + } + + @Test + public void stackSafety() { + int stackBlowingNumber = 10_000; + assertThat(last(magnetizeBy((x, y) -> false, take(stackBlowingNumber, repeat(1)))).orElseThrow(AssertionError::new), + iterates(1)); + } +} \ No newline at end of file From 6a1ca16fb8caec7fc1fd8235ac1986fd964ccce5 Mon Sep 17 00:00:00 2001 From: jnape Date: Sun, 25 Feb 2018 13:44:18 -0600 Subject: [PATCH 26/52] Adding both, for dually applying two functions invariant in the argument --- CHANGELOG.md | 1 + .../lambda/functions/builtin/fn2/Both.java | 51 +++++++++++++++++++ .../functions/builtin/fn2/BothTest.java | 15 ++++++ 3 files changed, 67 insertions(+) create mode 100644 src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Both.java create mode 100644 src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/BothTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 922f7ae73..966ea76f8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/). - `These`, a `CoProduct3` of `A`, `B`, or `Tuple2` - `Span`, for splitting an `Iterable` into contiguous elements matching a predicate - `MagnetizeBy` and `Magnetize`, for grouping elements by pairwise predicate tests +- `Both`, for dually applying two functions and producing a `Tuple2` of their results ### Deprecated - `Either#trying` in favor of `Try#trying` diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Both.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Both.java new file mode 100644 index 000000000..04bce60f5 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Both.java @@ -0,0 +1,51 @@ +package com.jnape.palatable.lambda.functions.builtin.fn2; + +import com.jnape.palatable.lambda.adt.hlist.Tuple2; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.Fn3; + +import java.util.function.Function; + +/** + * Given two functions f and g, produce a + * {@link Fn1}<A, {@link Tuple2}<B, C>> (the dual application of both functions). + * + * @param both function's input type + * @param the first function return type + * @param the second function return type + */ +public final class Both implements Fn3, Function, A, Tuple2> { + + private static final Both INSTANCE = new Both(); + + private Both() { + } + + @Override + public Tuple2 apply(Function f, + Function g, + A a) { + return Tuple2.fill(a).biMap(f, g); + } + + @SuppressWarnings("unchecked") + public static Both both() { + return INSTANCE; + } + + public static Fn1, Fn1>> both( + Function f) { + return Both.both().apply(f); + } + + public static Fn1> both(Function f, + Function g) { + return Both.both(f).apply(g); + } + + public static Tuple2 both(Function f, + Function g, + A a) { + return Both.both(f, g).apply(a); + } +} diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/BothTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/BothTest.java new file mode 100644 index 000000000..961d2679c --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/BothTest.java @@ -0,0 +1,15 @@ +package com.jnape.palatable.lambda.functions.builtin.fn2; + +import org.junit.Test; + +import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Both.both; +import static org.junit.Assert.assertEquals; + +public class BothTest { + + @Test + public void duallyAppliesTwoFunctionsToSameInput() { + assertEquals(tuple(1, -1), both(x -> x + 1, x -> x - 1, 0)); + } +} \ No newline at end of file From c72ef9199a1547a7c83f747251475e3e1488f7dc Mon Sep 17 00:00:00 2001 From: jnape Date: Sun, 25 Feb 2018 13:57:06 -0600 Subject: [PATCH 27/52] Lens#both for dually focusing with two lenses at once --- CHANGELOG.md | 1 + .../com/jnape/palatable/lambda/lens/Lens.java | 41 +++++++++++++++++++ .../jnape/palatable/lambda/lens/LensTest.java | 14 +++++++ 3 files changed, 56 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 966ea76f8..bb163162a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/). - `Span`, for splitting an `Iterable` into contiguous elements matching a predicate - `MagnetizeBy` and `Magnetize`, for grouping elements by pairwise predicate tests - `Both`, for dually applying two functions and producing a `Tuple2` of their results +- `Lens#both`, for dually focusing with two lenses at once ### Deprecated - `Either#trying` in favor of `Try#trying` diff --git a/src/main/java/com/jnape/palatable/lambda/lens/Lens.java b/src/main/java/com/jnape/palatable/lambda/lens/Lens.java index fca2e37ec..badea035b 100644 --- a/src/main/java/com/jnape/palatable/lambda/lens/Lens.java +++ b/src/main/java/com/jnape/palatable/lambda/lens/Lens.java @@ -1,6 +1,9 @@ package com.jnape.palatable.lambda.lens; +import com.jnape.palatable.lambda.adt.hlist.Tuple2; +import com.jnape.palatable.lambda.functions.Fn1; import com.jnape.palatable.lambda.functions.Fn2; +import com.jnape.palatable.lambda.functions.builtin.fn2.Both; import com.jnape.palatable.lambda.functor.Applicative; import com.jnape.palatable.lambda.functor.Functor; import com.jnape.palatable.lambda.functor.Profunctor; @@ -311,6 +314,23 @@ static Lens.Simple simpleLens(Function gett return adapt(lens(getter, setter)); } + /** + * Dually focus on two lenses at the same time. Requires S and T to be invariant between + * lenses. + * + * @param f the first lens + * @param g the second lens + * @param both larger values + * @param f's smaller viewing value + * @param g's smaller viewing value + * @param f's smaller setting value + * @param g's smaller setting value + * @return the dual-focus lens + */ + static Lens, Tuple2> both(Lens f, Lens g) { + return lens(Both.both(view(f), view(g)), (s, cd) -> cd.biMap(set(f), set(g)).into(Fn1::compose).apply(s)); + } + /** * A convenience type with a simplified type signature for common lenses with both unified "larger" values and * unified "smaller" values. @@ -335,11 +355,32 @@ default Lens.Simple andThen(Lens.Simple f) { return f.compose(this); } + /** + * Adapt a {@link Lens} with the right variance to a {@link Lens.Simple}. + * + * @param lens the lens + * @param S/T + * @param A/B + * @return the simple lens + */ @SuppressWarnings("unchecked") static Simple adapt(Lens lens) { return lens::apply; } + /** + * Specialization of {@link Lens#both(Lens, Lens)} for simple lenses. + * + * @param f the first lens + * @param g the second lens + * @param both lens larger values + * @param both lens smaller values + * @return the dual-focus simple lens + */ + static Lens.Simple> both(Lens f, Lens g) { + return adapt(Lens.both(f, g)); + } + /** * A convenience type with a simplified type signature for fixed simple lenses. * diff --git a/src/test/java/com/jnape/palatable/lambda/lens/LensTest.java b/src/test/java/com/jnape/palatable/lambda/lens/LensTest.java index df8e40942..0b7b4c5ae 100644 --- a/src/test/java/com/jnape/palatable/lambda/lens/LensTest.java +++ b/src/test/java/com/jnape/palatable/lambda/lens/LensTest.java @@ -1,6 +1,7 @@ package com.jnape.palatable.lambda.lens; import com.jnape.palatable.lambda.adt.Maybe; +import com.jnape.palatable.lambda.adt.hlist.Tuple2; import com.jnape.palatable.lambda.functions.Fn1; import com.jnape.palatable.lambda.functor.builtin.Const; import com.jnape.palatable.lambda.functor.builtin.Identity; @@ -18,7 +19,10 @@ import java.util.Set; import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; +import static com.jnape.palatable.lambda.lens.Lens.both; import static com.jnape.palatable.lambda.lens.Lens.lens; +import static com.jnape.palatable.lambda.lens.Lens.simpleLens; import static com.jnape.palatable.lambda.lens.functions.Set.set; import static com.jnape.palatable.lambda.lens.functions.View.view; import static java.lang.Integer.parseInt; @@ -89,4 +93,14 @@ public void andThenComposesInReverse() { assertEquals("one", view(EARLIER_LENS.andThen(LENS), map)); assertEquals(singletonMap("foo", singleton(1)), set(EARLIER_LENS.andThen(LENS), 1, map)); } + + @Test + public void bothSplitsFocusBetweenLenses() { + Lens firstChar = simpleLens(s -> s.charAt(0), (s, c) -> c + s.substring(1)); + Lens length = simpleLens(String::length, (s, k) -> s.substring(0, k)); + Lens, Tuple2> both = both(firstChar, length); + + assertEquals(tuple('a', 3), view(both, "abc")); + assertEquals("zb", set(both, tuple('z', 2), "abc")); + } } \ No newline at end of file From 5d451d9aa15f7fa7c5764adfdfe3c1eec2a50201 Mon Sep 17 00:00:00 2001 From: jnape Date: Sun, 25 Feb 2018 15:01:46 -0600 Subject: [PATCH 28/52] Lens#both for dually focusing with two lenses at once --- CHANGELOG.md | 1 + .../functions/builtin/fn4/IfThenElse.java | 50 +++++++++++++++++++ .../functions/builtin/fn4/IfThenElseTest.java | 21 ++++++++ 3 files changed, 72 insertions(+) create mode 100644 src/main/java/com/jnape/palatable/lambda/functions/builtin/fn4/IfThenElse.java create mode 100644 src/test/java/com/jnape/palatable/lambda/functions/builtin/fn4/IfThenElseTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md index bb163162a..ea47d3c55 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/). - `MagnetizeBy` and `Magnetize`, for grouping elements by pairwise predicate tests - `Both`, for dually applying two functions and producing a `Tuple2` of their results - `Lens#both`, for dually focusing with two lenses at once +- `IfThenElse`, an expression form for `if` statements ### Deprecated - `Either#trying` in favor of `Try#trying` diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn4/IfThenElse.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn4/IfThenElse.java new file mode 100644 index 000000000..fe353a497 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn4/IfThenElse.java @@ -0,0 +1,50 @@ +package com.jnape.palatable.lambda.functions.builtin.fn4; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.Fn2; +import com.jnape.palatable.lambda.functions.Fn3; +import com.jnape.palatable.lambda.functions.Fn4; + +import java.util.function.Function; + +public final class IfThenElse implements Fn4, Function, Function, A, B> { + + private static final IfThenElse INSTANCE = new IfThenElse(); + + private IfThenElse() { + } + + @Override + public B apply(Function predicate, Function thenCase, + Function elseCase, A a) { + return predicate.apply(a) ? thenCase.apply(a) : elseCase.apply(a); + } + + @SuppressWarnings("unchecked") + public static IfThenElse ifThenElse() { + return INSTANCE; + } + + public static Fn3, Function, A, B> ifThenElse( + Function predicate) { + return IfThenElse.ifThenElse().apply(predicate); + } + + public static Fn2, A, B> ifThenElse( + Function predicate, Function thenCase) { + return IfThenElse.ifThenElse(predicate).apply(thenCase); + } + + public static Fn1 ifThenElse( + Function predicate, Function thenCase, + Function elseCase) { + return IfThenElse.ifThenElse(predicate, thenCase).apply(elseCase); + } + + public static B ifThenElse( + Function predicate, Function thenCase, + Function elseCase, + A a) { + return ifThenElse(predicate, thenCase, elseCase).apply(a); + } +} diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn4/IfThenElseTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn4/IfThenElseTest.java new file mode 100644 index 000000000..70e1af6ab --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn4/IfThenElseTest.java @@ -0,0 +1,21 @@ +package com.jnape.palatable.lambda.functions.builtin.fn4; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.specialized.Predicate; +import org.junit.Test; + +import static com.jnape.palatable.lambda.functions.builtin.fn4.IfThenElse.ifThenElse; +import static org.junit.Assert.assertEquals; + +public class IfThenElseTest { + + @Test + public void standardLogic() { + Predicate even = x -> x % 2 == 0; + Fn1 inc = x -> x + 1; + Fn1 dec = x -> x - 1; + + assertEquals((Integer) 3, ifThenElse(even, inc, dec, 2)); + assertEquals((Integer) 0, ifThenElse(even, inc, dec, 1)); + } +} \ No newline at end of file From 1c63de8ad6b3315acf55e2617112532136d1d5b1 Mon Sep 17 00:00:00 2001 From: jnape Date: Sun, 25 Feb 2018 15:55:38 -0600 Subject: [PATCH 29/52] Fixing typo in javadoc --- src/main/java/com/jnape/palatable/lambda/adt/hlist/Tuple2.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 00083fac4..63cf5dcfb 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 @@ -154,7 +154,7 @@ public static Tuple2 fromEntry(Map.Entry entry) { } /** - * Given a value of type A, produced an instance of this tuple with each slot set to that value. + * Given a value of type A, produce an instance of this tuple with each slot set to that value. * * @param a the value to fill the tuple with * @param the value type From f01e118007afbb2c58eed3f05a9d5f2730f1669a Mon Sep 17 00:00:00 2001 From: jnape Date: Sun, 4 Mar 2018 12:01:52 -0500 Subject: [PATCH 30/52] Concat is now stack-safe on both paths of nesting --- CHANGELOG.md | 2 +- .../iteration/ConcatenatingIterable.java | 28 +++++ .../iteration/ConcatenatingIterator.java | 47 ------- .../lambda/iteration/ImmutableQueue.java | 119 ++++++++++++++++++ .../lambda/iteration/ImmutableStack.java | 81 ++++++++++++ .../lambda/monoid/builtin/Concat.java | 4 +- .../iteration/ConcatenatingIterableTest.java | 37 ++++++ .../iteration/ConcatenatingIteratorTest.java | 99 --------------- 8 files changed, 268 insertions(+), 149 deletions(-) create mode 100644 src/main/java/com/jnape/palatable/lambda/iteration/ConcatenatingIterable.java delete mode 100644 src/main/java/com/jnape/palatable/lambda/iteration/ConcatenatingIterator.java create mode 100644 src/main/java/com/jnape/palatable/lambda/iteration/ImmutableQueue.java create mode 100644 src/main/java/com/jnape/palatable/lambda/iteration/ImmutableStack.java create mode 100644 src/test/java/com/jnape/palatable/lambda/iteration/ConcatenatingIterableTest.java delete mode 100644 src/test/java/com/jnape/palatable/lambda/iteration/ConcatenatingIteratorTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md index ea47d3c55..c93146f93 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/). - `Identity`, `Compose`, and `Const` functors all have better `toString` implementations - `Into3-8` now supports functions with parameter variance - `HListLens#tail` is now covariant in `Tail` parameter -- More functions now automatically deforest nested calls (`cons`, `cycle`, `distinct`, `drop`, `dropwhile`, `filter`, `map`, `reverse`, `snoc`, `take`, `takewhile`, `tail`) +- More functions now automatically deforest nested calls (`concat` `cons`, `cycle`, `distinct`, `drop`, `dropwhile`, `filter`, `map`, `reverse`, `snoc`, `take`, `takewhile`, `tail`) ### Added - `BoundedBifunctor`, a `Bifunctor` super type that offers upper bounds for both parameters diff --git a/src/main/java/com/jnape/palatable/lambda/iteration/ConcatenatingIterable.java b/src/main/java/com/jnape/palatable/lambda/iteration/ConcatenatingIterable.java new file mode 100644 index 000000000..f7ae08023 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/iteration/ConcatenatingIterable.java @@ -0,0 +1,28 @@ +package com.jnape.palatable.lambda.iteration; + +import java.util.Iterator; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Flatten.flatten; + +public final class ConcatenatingIterable implements Iterable { + + private final ImmutableQueue> iterables; + + public ConcatenatingIterable(Iterable xs, Iterable ys) { + if (xs instanceof ConcatenatingIterable) { + ImmutableQueue> iterables = ((ConcatenatingIterable) xs).iterables; + this.iterables = ys instanceof ConcatenatingIterable + ? iterables.concat(((ConcatenatingIterable) ys).iterables) + : iterables.pushBack(ys); + } else { + iterables = ys instanceof ConcatenatingIterable + ? ((ConcatenatingIterable) ys).iterables.pushFront(xs) + : ImmutableQueue.>empty().pushFront(ys).pushFront(xs); + } + } + + @Override + public Iterator iterator() { + return flatten(iterables).iterator(); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/iteration/ConcatenatingIterator.java b/src/main/java/com/jnape/palatable/lambda/iteration/ConcatenatingIterator.java deleted file mode 100644 index c96454a43..000000000 --- a/src/main/java/com/jnape/palatable/lambda/iteration/ConcatenatingIterator.java +++ /dev/null @@ -1,47 +0,0 @@ -package com.jnape.palatable.lambda.iteration; - -import java.util.Iterator; -import java.util.NoSuchElementException; -import java.util.concurrent.atomic.AtomicReference; -import java.util.function.Supplier; - -public class ConcatenatingIterator implements Iterator { - - private final Supplier> xsSupplier; - private final Supplier> ysSupplier; - private final AtomicReference> xsRef; - private final AtomicReference> ysRef; - private boolean iteratedXs; - - public ConcatenatingIterator(Iterable xs, Iterable ys) { - xsSupplier = xs::iterator; - ysSupplier = ys::iterator; - xsRef = new AtomicReference<>(); - ysRef = new AtomicReference<>(); - iteratedXs = false; - } - - @Override - public boolean hasNext() { - if (hasNext(xsRef, xsSupplier)) - return true; - - iteratedXs = true; - return hasNext(ysRef, ysSupplier); - } - - private boolean hasNext(AtomicReference> ref, Supplier> supplier) { - Iterator as = ref.updateAndGet(it -> it == null ? supplier.get() : it); - while (as instanceof ConcatenatingIterator && ((ConcatenatingIterator) as).iteratedXs) - as = ref.updateAndGet(it -> ((ConcatenatingIterator) it).ysRef.get()); - return as.hasNext(); - } - - @Override - public A next() { - if (!hasNext()) - throw new NoSuchElementException(); - - return !iteratedXs ? xsRef.get().next() : ysRef.get().next(); - } -} diff --git a/src/main/java/com/jnape/palatable/lambda/iteration/ImmutableQueue.java b/src/main/java/com/jnape/palatable/lambda/iteration/ImmutableQueue.java new file mode 100644 index 000000000..94d94d0b0 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/iteration/ImmutableQueue.java @@ -0,0 +1,119 @@ +package com.jnape.palatable.lambda.iteration; + +import com.jnape.palatable.lambda.adt.Maybe; + +import java.util.Iterator; +import java.util.NoSuchElementException; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; +import static com.jnape.palatable.lambda.functions.builtin.fn3.FoldLeft.foldLeft; + +abstract class ImmutableQueue implements Iterable { + + abstract ImmutableQueue pushFront(A a); + + abstract ImmutableQueue pushBack(A a); + + abstract Maybe head(); + + abstract ImmutableQueue tail(); + + abstract ImmutableQueue concat(ImmutableQueue other); + + final boolean isEmpty() { + return head().fmap(constantly(false)).orElse(true); + } + + @Override + public Iterator iterator() { + return new Iterator() { + private ImmutableQueue queue = ImmutableQueue.this; + + @Override + public boolean hasNext() { + return !queue.isEmpty(); + } + + @Override + public A next() { + A next = queue.head().orElseThrow(NoSuchElementException::new); + queue = queue.tail(); + return next; + } + }; + } + + @SuppressWarnings("unchecked") + public static ImmutableQueue empty() { + return Empty.INSTANCE; + } + + private static final class Empty extends ImmutableQueue { + private static final Empty INSTANCE = new Empty(); + + @Override + ImmutableQueue pushFront(A a) { + return new NonEmpty<>(ImmutableStack.empty().push(a), ImmutableStack.empty()); + } + + @Override + ImmutableQueue pushBack(A a) { + return pushFront(a); + } + + @Override + ImmutableQueue concat(ImmutableQueue other) { + return other; + } + + @Override + Maybe head() { + return Maybe.nothing(); + } + + @Override + ImmutableQueue tail() { + return this; + } + } + + private static final class NonEmpty extends ImmutableQueue { + private final ImmutableStack outbound; + private final ImmutableStack inbound; + + private NonEmpty(ImmutableStack outbound, ImmutableStack inbound) { + this.outbound = outbound; + this.inbound = inbound; + } + + @Override + ImmutableQueue pushFront(A a) { + return new NonEmpty<>(outbound.push(a), inbound); + } + + @Override + ImmutableQueue pushBack(A a) { + return new NonEmpty<>(outbound, inbound.push(a)); + } + + @Override + ImmutableQueue concat(ImmutableQueue other) { + return new NonEmpty<>(outbound, foldLeft(ImmutableStack::push, inbound, other)); + } + + @Override + Maybe head() { + return outbound.head(); + } + + @Override + ImmutableQueue tail() { + ImmutableStack outTail = outbound.tail(); + if (!outTail.isEmpty()) + return new NonEmpty<>(outTail, inbound); + + ImmutableStack newOutbound = foldLeft(ImmutableStack::push, ImmutableStack.empty(), inbound); + return newOutbound.isEmpty() ? empty() : new NonEmpty<>(newOutbound, ImmutableStack.empty()); + } + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/iteration/ImmutableStack.java b/src/main/java/com/jnape/palatable/lambda/iteration/ImmutableStack.java new file mode 100644 index 000000000..619619efb --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/iteration/ImmutableStack.java @@ -0,0 +1,81 @@ +package com.jnape.palatable.lambda.iteration; + +import com.jnape.palatable.lambda.adt.Maybe; + +import java.util.Iterator; +import java.util.NoSuchElementException; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; + +abstract class ImmutableStack implements Iterable { + + final ImmutableStack push(A a) { + return new Node<>(a, this); + } + + abstract Maybe head(); + + abstract ImmutableStack tail(); + + final boolean isEmpty() { + return head().fmap(constantly(false)).orElse(true); + } + + @Override + public Iterator iterator() { + return new Iterator() { + private ImmutableStack stack = ImmutableStack.this; + + @Override + public boolean hasNext() { + return !stack.isEmpty(); + } + + @Override + public A next() { + A next = stack.head().orElseThrow(NoSuchElementException::new); + stack = stack.tail(); + return next; + } + }; + } + + @SuppressWarnings("unchecked") + public static ImmutableStack empty() { + return Empty.INSTANCE; + } + + private static final class Empty extends ImmutableStack { + private static final Empty INSTANCE = new Empty(); + + @Override + Maybe head() { + return Maybe.nothing(); + } + + @Override + ImmutableStack tail() { + return this; + } + } + + private static final class Node extends ImmutableStack { + private final A head; + private final ImmutableStack tail; + + public Node(A head, ImmutableStack tail) { + this.head = head; + this.tail = tail; + } + + @Override + Maybe head() { + return Maybe.just(head); + } + + @Override + ImmutableStack tail() { + return tail; + } + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/monoid/builtin/Concat.java b/src/main/java/com/jnape/palatable/lambda/monoid/builtin/Concat.java index e48030a83..ec38b568b 100644 --- a/src/main/java/com/jnape/palatable/lambda/monoid/builtin/Concat.java +++ b/src/main/java/com/jnape/palatable/lambda/monoid/builtin/Concat.java @@ -1,7 +1,7 @@ package com.jnape.palatable.lambda.monoid.builtin; import com.jnape.palatable.lambda.functions.Fn1; -import com.jnape.palatable.lambda.iteration.ConcatenatingIterator; +import com.jnape.palatable.lambda.iteration.ConcatenatingIterable; import com.jnape.palatable.lambda.monoid.Monoid; import java.util.Collections; @@ -25,7 +25,7 @@ public Iterable identity() { @Override public Iterable apply(Iterable xs, Iterable ys) { - return () -> new ConcatenatingIterator<>(xs, ys); + return new ConcatenatingIterable<>(xs, ys); } @SuppressWarnings("unchecked") diff --git a/src/test/java/com/jnape/palatable/lambda/iteration/ConcatenatingIterableTest.java b/src/test/java/com/jnape/palatable/lambda/iteration/ConcatenatingIterableTest.java new file mode 100644 index 000000000..c9e9faf61 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/iteration/ConcatenatingIterableTest.java @@ -0,0 +1,37 @@ +package com.jnape.palatable.lambda.iteration; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.monoid.builtin.Concat; +import com.jnape.palatable.traitor.annotations.TestTraits; +import com.jnape.palatable.traitor.framework.Subjects; +import com.jnape.palatable.traitor.runners.Traits; +import org.junit.Test; +import org.junit.runner.RunWith; +import testsupport.traits.Deforesting; + +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Last.last; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Repeat.repeat; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Replicate.replicate; +import static com.jnape.palatable.traitor.framework.Subjects.subjects; +import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; +import static org.junit.Assert.assertEquals; + +@RunWith(Traits.class) +public class ConcatenatingIterableTest { + + @TestTraits({Deforesting.class}) + public Subjects, Iterable>> testSubject() { + return subjects(xs -> new ConcatenatingIterable<>(emptyList(), xs), + xs -> new ConcatenatingIterable<>(xs, emptyList()), + xs -> new ConcatenatingIterable<>(repeat(1), xs), + xs -> new ConcatenatingIterable<>(xs, repeat(1))); + } + + @Test + public void stackSafety() { + Iterable xs = Concat.concat().reduceLeft(replicate(1_000_000, asList(1, 2, 3))); + assertEquals(just(3), last(xs)); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/iteration/ConcatenatingIteratorTest.java b/src/test/java/com/jnape/palatable/lambda/iteration/ConcatenatingIteratorTest.java deleted file mode 100644 index b22ebfe32..000000000 --- a/src/test/java/com/jnape/palatable/lambda/iteration/ConcatenatingIteratorTest.java +++ /dev/null @@ -1,99 +0,0 @@ -package com.jnape.palatable.lambda.iteration; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.runners.MockitoJUnitRunner; - -import java.util.Collections; -import java.util.Iterator; - -import static com.jnape.palatable.lambda.adt.Maybe.just; -import static com.jnape.palatable.lambda.functions.builtin.fn1.Last.last; -import static com.jnape.palatable.lambda.functions.builtin.fn1.Size.size; -import static com.jnape.palatable.lambda.functions.builtin.fn2.Iterate.iterate; -import static com.jnape.palatable.lambda.functions.builtin.fn2.Map.map; -import static com.jnape.palatable.lambda.functions.builtin.fn2.Take.take; -import static com.jnape.palatable.lambda.functions.builtin.fn3.FoldRight.foldRight; -import static org.hamcrest.core.Is.is; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertThat; -import static org.junit.Assert.assertTrue; -import static org.mockito.Mockito.when; -import static testsupport.Mocking.mockIteratorToHaveValues; - -@RunWith(MockitoJUnitRunner.class) -public class ConcatenatingIteratorTest { - - @Mock private Iterator xs; - @Mock private Iterator ys; - - private ConcatenatingIterator concatenatingIterator; - - @Before - public void setUp() throws Exception { - concatenatingIterator = new ConcatenatingIterator<>(() -> xs, () -> ys); - } - - @Test - public void hasNextIfMoreXsAndMoreYs() { - when(xs.hasNext()).thenReturn(true); - when(ys.hasNext()).thenReturn(true); - - assertTrue(concatenatingIterator.hasNext()); - } - - @Test - public void hasNextIfJustMoreXs() { - when(xs.hasNext()).thenReturn(true); - when(ys.hasNext()).thenReturn(false); - - assertTrue(concatenatingIterator.hasNext()); - } - - @Test - public void hasNextIfJustMoreYs() { - when(xs.hasNext()).thenReturn(false); - when(ys.hasNext()).thenReturn(true); - - assertTrue(concatenatingIterator.hasNext()); - } - - @Test - public void doesNotHaveNextIfNeitherMoreXsNorMoreYs() { - when(xs.hasNext()).thenReturn(false); - when(ys.hasNext()).thenReturn(false); - - assertFalse(concatenatingIterator.hasNext()); - } - - @Test - public void nextPullsFromXsFirstThenFromYs() { - mockIteratorToHaveValues(xs, 1, 2); - mockIteratorToHaveValues(ys, 3); - - assertThat(concatenatingIterator.hasNext(), is(true)); - assertThat(concatenatingIterator.next(), is(1)); - assertThat(concatenatingIterator.hasNext(), is(true)); - assertThat(concatenatingIterator.next(), is(2)); - assertThat(concatenatingIterator.hasNext(), is(true)); - assertThat(concatenatingIterator.next(), is(3)); - assertThat(concatenatingIterator.hasNext(), is(false)); - } - - @Test - public void stackSafety() { - Integer stackBlowingNumber = 10_000; - Iterable> xss = map(Collections::singleton, take(stackBlowingNumber, iterate(x -> x + 1, 1))); - Iterable deeplyNestedConcat = foldRight((xs, ys) -> () -> new ConcatenatingIterator<>(xs, ys), - (Iterable) Collections.emptySet(), - xss); - - Iterable ints = () -> new ConcatenatingIterator<>(deeplyNestedConcat, deeplyNestedConcat); - - assertEquals(just(stackBlowingNumber), last(ints)); - assertEquals((long) (stackBlowingNumber * 2), size(ints).longValue()); - } -} \ No newline at end of file From 531c34250750b90949c685adffaea94f5dd91fbc Mon Sep 17 00:00:00 2001 From: jnape Date: Sun, 4 Mar 2018 18:23:32 -0600 Subject: [PATCH 31/52] Traversable#traverse is now actually usable without call site coercions --- CHANGELOG.md | 1 + .../jnape/palatable/lambda/adt/Either.java | 20 ++++++------ .../com/jnape/palatable/lambda/adt/Maybe.java | 21 +++++-------- .../com/jnape/palatable/lambda/adt/These.java | 15 ++++----- .../com/jnape/palatable/lambda/adt/Try.java | 10 +++--- .../palatable/lambda/adt/choice/Choice2.java | 10 +++--- .../palatable/lambda/adt/choice/Choice3.java | 12 +++---- .../palatable/lambda/adt/choice/Choice4.java | 14 ++++----- .../palatable/lambda/adt/choice/Choice5.java | 16 +++++----- .../palatable/lambda/adt/choice/Choice6.java | 18 +++++------ .../palatable/lambda/adt/choice/Choice7.java | 20 ++++++------ .../palatable/lambda/adt/choice/Choice8.java | 22 ++++++------- .../lambda/adt/hlist/SingletonHList.java | 8 ++--- .../palatable/lambda/adt/hlist/Tuple2.java | 8 ++--- .../palatable/lambda/adt/hlist/Tuple3.java | 8 ++--- .../palatable/lambda/adt/hlist/Tuple4.java | 8 ++--- .../palatable/lambda/adt/hlist/Tuple5.java | 8 ++--- .../palatable/lambda/adt/hlist/Tuple6.java | 8 ++--- .../palatable/lambda/adt/hlist/Tuple7.java | 8 ++--- .../palatable/lambda/adt/hlist/Tuple8.java | 8 ++--- .../functions/builtin/fn2/Sequence.java | 3 +- .../functions/recursion/RecursiveResult.java | 8 ++--- .../lambda/functor/builtin/Const.java | 7 ++--- .../lambda/functor/builtin/Identity.java | 8 ++--- .../lambda/traversable/LambdaIterable.java | 11 ++++--- .../lambda/traversable/Traversable.java | 23 +++++++------- .../lambda/traversable/TraversableTest.java | 31 +++++++++++++++++++ .../testsupport/traits/TraversableLaws.java | 2 +- 28 files changed, 177 insertions(+), 159 deletions(-) create mode 100644 src/test/java/com/jnape/palatable/lambda/traversable/TraversableTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md index c93146f93..993f15d2d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/). ## [Unreleased] ### Changed - ***Breaking Change***: `Sequence` now has two more type parameters to aid in inference +- ***Breaking Change***: `Traversable#traverse` now has three more type parameters to aid in inference - `Bifunctor` is now a `BoundedBifunctor` where both parameter upper bounds are `Object` - `Peek2` now accepts the more general `BoundedBifunctor` - `Identity`, `Compose`, and `Const` functors all have better `toString` implementations diff --git a/src/main/java/com/jnape/palatable/lambda/adt/Either.java b/src/main/java/com/jnape/palatable/lambda/adt/Either.java index 9d304f94b..c596662e4 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/Either.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/Either.java @@ -224,31 +224,31 @@ public final Either biMap(Function lef } @Override - public Either pure(R2 r2) { + public final Either pure(R2 r2) { return right(r2); } @Override - public Either zip(Applicative, Either> appFn) { + public final Either zip(Applicative, Either> appFn) { return appFn.>>coerce().flatMap(this::biMapR); } @Override - public Either discardL(Applicative> appB) { + public final Either discardL(Applicative> appB) { return Monad.super.discardL(appB).coerce(); } @Override - public Either discardR(Applicative> appB) { + public final Either discardR(Applicative> appB) { return Monad.super.discardR(appB).coerce(); } @Override - public Applicative, App> traverse( - Function> fn, - Function>, ? extends Applicative>, App>> pure) { - return match(l -> pure.apply(left(l)).fmap(x -> (Either) x), - r -> fn.apply(r).fmap(Either::right)); + @SuppressWarnings("unchecked") + public final >, AppB extends Applicative, AppTrav extends Applicative> AppTrav traverse( + Function fn, + Function pure) { + return (AppTrav) match(l -> pure.apply((TravB) left(l)), r -> fn.apply(r).fmap(Either::right)); } /** @@ -257,7 +257,7 @@ public Applicative, App> traverse( * * @return Maybe the right value */ - public Maybe toMaybe() { + public final Maybe toMaybe() { return projectB(); } diff --git a/src/main/java/com/jnape/palatable/lambda/adt/Maybe.java b/src/main/java/com/jnape/palatable/lambda/adt/Maybe.java index 993a26936..e5dad5f19 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/Maybe.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/Maybe.java @@ -134,11 +134,6 @@ public final Maybe discardR(Applicative appB) { @Override public abstract Maybe flatMap(Function> f); - @Override - public abstract Applicative, App> traverse( - Function> fn, - Function, ? extends Applicative, App>> pure); - /** * If this value is present, accept it by consumer; otherwise, do nothing. * @@ -223,10 +218,11 @@ public Maybe flatMap(Function> f) { } @Override - public Applicative, App> traverse( - Function> fn, - Function, ? extends Applicative, App>> pure) { - return fn.apply(a).fmap(Just::new); + @SuppressWarnings("unchecked") + public , + AppB extends Applicative, AppTrav extends Applicative> AppTrav traverse( + Function fn, Function pure) { + return fn.apply(a).fmap(Just::new).fmap(Applicative::coerce).coerce(); } @Override @@ -259,10 +255,9 @@ public Maybe flatMap(Function> f) { @Override @SuppressWarnings("unchecked") - public Applicative, App> traverse( - Function> fn, - Function, ? extends Applicative, App>> pure) { - return pure.apply(nothing()).fmap(x -> (Maybe) x); + public , AppB extends Applicative, AppTrav extends Applicative> AppTrav traverse( + Function fn, Function pure) { + return pure.apply((TravB) nothing()); } @Override 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 c8927b103..f4ffc5846 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/These.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/These.java @@ -53,16 +53,13 @@ public final These pure(C c) { return match(a -> both(a, c), b -> b(c), into((a, b) -> both(a, c))); } - /** - * {@inheritDoc} - */ @Override - public Applicative, App> traverse( - Function> fn, - Function>, ? extends Applicative>, App>> pure) { - return match(a -> pure.apply(a(a)).fmap(x -> (These) x), - b -> fn.apply(b).fmap(this::pure), - into((a, b) -> fn.apply(b).fmap(c -> both(a, c)))); + @SuppressWarnings("unchecked") + public >, AppB extends Applicative, AppTrav extends Applicative> AppTrav traverse( + Function fn, Function pure) { + return match(a -> pure.apply((TravB) a(a)), + b -> fn.apply(b).fmap(this::pure).fmap(Applicative::coerce).coerce(), + into((a, b) -> fn.apply(b).fmap(c -> both(a, c)).fmap(Applicative::coerce).coerce())); } /** diff --git a/src/main/java/com/jnape/palatable/lambda/adt/Try.java b/src/main/java/com/jnape/palatable/lambda/adt/Try.java index 88b49de70..710e4b42e 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/Try.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/Try.java @@ -161,12 +161,10 @@ public Try discardR(Applicative> appB) { @Override @SuppressWarnings("unchecked") - public Applicative, App> traverse( - Function> fn, - Function>, ? extends Applicative>, App>> pure) { - return match(t -> pure.apply(failure(t)), - a -> fn.apply(a).fmap(Try::success)) - .fmap(x -> (Try) x); + public >, AppB extends Applicative, AppTrav extends Applicative> AppTrav traverse( + Function fn, Function pure) { + return match(t -> pure.apply((TravB) failure(t)), + a -> fn.apply(a).fmap(Try::success).fmap(Applicative::coerce).coerce()); } @Override diff --git a/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice2.java b/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice2.java index f38f681cf..5276adb04 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice2.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice2.java @@ -84,11 +84,11 @@ public final Choice2 flatMap(Function Applicative, App> traverse( - Function> fn, - Function>, ? extends Applicative>, App>> pure) { - return match(a -> pure.apply(a(a)).fmap(x -> (Choice2) x), - b -> fn.apply(b).fmap(Choice2::b)); + @SuppressWarnings("unchecked") + public >, AppB extends Applicative, AppTrav extends Applicative> AppTrav traverse( + Function fn, Function pure) { + return match(a -> pure.apply((TravB) a(a)), + b -> fn.apply(b).fmap(Choice2::b).fmap(Applicative::coerce).coerce()); } /** diff --git a/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice3.java b/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice3.java index 4ef49ac33..8cc0abba5 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice3.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice3.java @@ -88,12 +88,12 @@ public Choice3 flatMap(Function Applicative, App> traverse( - Function> fn, - Function>, ? extends Applicative>, App>> pure) { - return match(a -> pure.apply(a(a)).fmap(x -> (Choice3) x), - b -> pure.apply(b(b)).fmap(x -> (Choice3) x), - c -> fn.apply(c).fmap(Choice3::c)); + @SuppressWarnings("unchecked") + public >, AppB extends Applicative, AppTrav extends Applicative> AppTrav traverse( + Function fn, Function pure) { + return match(a -> pure.apply((TravB) Choice3.a(a)).coerce(), + b -> pure.apply((TravB) Choice3.b(b)).coerce(), + c -> fn.apply(c).fmap(Choice3::c).fmap(Applicative::coerce).coerce()); } /** diff --git a/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice4.java b/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice4.java index dc98c7231..9a604fa71 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice4.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice4.java @@ -92,13 +92,13 @@ public Choice4 flatMap(Function Applicative, App> traverse( - Function> fn, - Function>, ? extends Applicative>, App>> pure) { - return match(a -> pure.apply(a(a)).fmap(x -> (Choice4) x), - b -> pure.apply(b(b)).fmap(x -> (Choice4) x), - c -> pure.apply(c(c)).fmap(x -> (Choice4) x), - d -> fn.apply(d).fmap(Choice4::d)); + @SuppressWarnings("unchecked") + public >, AppB extends Applicative, AppTrav extends Applicative> AppTrav traverse( + Function fn, Function pure) { + return match(a -> pure.apply((TravB) Choice4.a(a)).coerce(), + b -> pure.apply((TravB) Choice4.b(b)).coerce(), + c -> pure.apply((TravB) Choice4.c(c)), + d -> fn.apply(d).fmap(Choice4::d).fmap(Applicative::coerce).coerce()); } /** diff --git a/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice5.java b/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice5.java index f3d609dd4..758aa2a00 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice5.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice5.java @@ -94,14 +94,14 @@ public Choice5 flatMap(Function Applicative, App> traverse( - Function> fn, - Function>, ? extends Applicative>, App>> pure) { - return match(a -> pure.apply(a(a)).fmap(x -> (Choice5) x), - b -> pure.apply(b(b)).fmap(x -> (Choice5) x), - c -> pure.apply(c(c)).fmap(x -> (Choice5) x), - d -> pure.apply(d(d)).fmap(x -> (Choice5) x), - e -> fn.apply(e).fmap(Choice5::e)); + @SuppressWarnings("unchecked") + public >, AppB extends Applicative, AppTrav extends Applicative> AppTrav traverse( + Function fn, Function pure) { + return match(a -> pure.apply((TravB) Choice5.a(a)).coerce(), + b -> pure.apply((TravB) Choice5.b(b)).coerce(), + c -> pure.apply((TravB) Choice5.c(c)), + d -> pure.apply((TravB) Choice5.d(d)), + e -> fn.apply(e).fmap(Choice5::e).fmap(Applicative::coerce).coerce()); } /** diff --git a/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice6.java b/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice6.java index b351db250..9286e9abf 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice6.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice6.java @@ -98,15 +98,15 @@ public Choice6 flatMap( } @Override - public Applicative, App> traverse( - Function> fn, - Function>, ? extends Applicative>, App>> pure) { - return match(a -> pure.apply(a(a)).fmap(x -> (Choice6) x), - b -> pure.apply(b(b)).fmap(x -> (Choice6) x), - c -> pure.apply(c(c)).fmap(x -> (Choice6) x), - d -> pure.apply(d(d)).fmap(x -> (Choice6) x), - e -> pure.apply(e(e)).fmap(x -> (Choice6) x), - f -> fn.apply(f).fmap(Choice6::f)); + @SuppressWarnings("unchecked") + public >, AppB extends Applicative, AppTrav extends Applicative> AppTrav traverse( + Function fn, Function pure) { + return match(a -> pure.apply((TravB) Choice6.a(a)).coerce(), + b -> pure.apply((TravB) Choice6.b(b)).coerce(), + c -> pure.apply((TravB) Choice6.c(c)), + d -> pure.apply((TravB) Choice6.d(d)), + e -> pure.apply((TravB) Choice6.e(e)), + f -> fn.apply(f).fmap(Choice6::f).fmap(Applicative::coerce).coerce()); } /** diff --git a/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice7.java b/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice7.java index 920a707db..18d9bdc9e 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice7.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice7.java @@ -101,16 +101,16 @@ public Choice7 flatMap( } @Override - public Applicative, App> traverse( - Function> fn, - Function>, ? extends Applicative>, App>> pure) { - return match(a -> pure.apply(a(a)).fmap(x -> (Choice7) x), - b -> pure.apply(b(b)).fmap(x -> (Choice7) x), - c -> pure.apply(c(c)).fmap(x -> (Choice7) x), - d -> pure.apply(d(d)).fmap(x -> (Choice7) x), - e -> pure.apply(e(e)).fmap(x -> (Choice7) x), - f -> pure.apply(f(f)).fmap(x -> (Choice7) x), - g -> fn.apply(g).fmap(Choice7::g)); + @SuppressWarnings("unchecked") + public >, AppB extends Applicative, AppTrav extends Applicative> AppTrav traverse( + Function fn, Function pure) { + return match(a -> pure.apply((TravB) Choice7.a(a)).coerce(), + b -> pure.apply((TravB) Choice7.b(b)).coerce(), + c -> pure.apply((TravB) Choice7.c(c)), + d -> pure.apply((TravB) Choice7.d(d)), + e -> pure.apply((TravB) Choice7.e(e)), + f -> pure.apply((TravB) Choice7.f(f)), + g -> fn.apply(g).fmap(Choice7::g).fmap(Applicative::coerce).coerce()); } /** diff --git a/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice8.java b/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice8.java index b9d070c39..e7ec41a49 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice8.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice8.java @@ -97,17 +97,17 @@ public Choice8 flatMap( } @Override - public Applicative, App> traverse( - Function> fn, - Function>, ? extends Applicative>, App>> pure) { - return match(a -> pure.apply(a(a)).fmap(x -> (Choice8) x), - b -> pure.apply(b(b)).fmap(x -> (Choice8) x), - c -> pure.apply(c(c)).fmap(x -> (Choice8) x), - d -> pure.apply(d(d)).fmap(x -> (Choice8) x), - e -> pure.apply(e(e)).fmap(x -> (Choice8) x), - f -> pure.apply(f(f)).fmap(x -> (Choice8) x), - g -> pure.apply(g(g)).fmap(x -> (Choice8) x), - h -> fn.apply(h).fmap(Choice8::h)); + @SuppressWarnings("unchecked") + public >, AppB extends Applicative, AppTrav extends Applicative> AppTrav traverse( + Function fn, Function pure) { + return match(a -> pure.apply((TravB) Choice8.a(a)).coerce(), + b -> pure.apply((TravB) Choice8.b(b)).coerce(), + c -> pure.apply((TravB) Choice8.c(c)), + d -> pure.apply((TravB) Choice8.d(d)), + e -> pure.apply((TravB) Choice8.e(e)), + f -> pure.apply((TravB) Choice8.f(f)), + g -> pure.apply((TravB) Choice8.g(g)), + h -> fn.apply(h).fmap(Choice8::h).fmap(Applicative::coerce).coerce()); } /** 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 1dae649b5..0a8143d84 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 @@ -61,10 +61,10 @@ public <_1Prime> SingletonHList<_1Prime> flatMap(Function Applicative, App> traverse( - Function> fn, - Function, ? extends Applicative, App>> pure) { - return fn.apply(head()).fmap(SingletonHList::new); + @SuppressWarnings("unchecked") + public , AppB extends Applicative, AppTrav extends Applicative> AppTrav traverse( + Function fn, Function pure) { + return fn.apply(head()).fmap(SingletonHList::new).fmap(Applicative::coerce).coerce(); } /** 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 63cf5dcfb..173bed228 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 @@ -135,10 +135,10 @@ public <_2Prime> Tuple2<_1, _2Prime> flatMap(Function Applicative, App> traverse( - Function> fn, - Function>, ? extends Applicative>, App>> pure) { - return fn.apply(_2).fmap(_2Prime -> fmap(constantly(_2Prime))); + @SuppressWarnings("unchecked") + public <_2Prime, App extends Applicative, TravB extends Traversable<_2Prime, Tuple2<_1, ?>>, AppB extends Applicative<_2Prime, App>, AppTrav extends Applicative> AppTrav traverse( + Function fn, Function pure) { + return fn.apply(_2).fmap(_2Prime -> fmap(constantly(_2Prime))).fmap(Applicative::coerce).coerce(); } /** 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 10511c10a..55ddb87b5 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 @@ -133,10 +133,10 @@ public <_3Prime> Tuple3<_1, _2, _3Prime> flatMap( } @Override - public <_3Prime, App extends Applicative> Applicative, App> traverse( - Function> fn, - Function>, ? extends Applicative>, App>> pure) { - return fn.apply(_3).fmap(_3Prime -> fmap(constantly(_3Prime))); + @SuppressWarnings("unchecked") + public <_3Prime, App extends Applicative, TravB extends Traversable<_3Prime, Tuple3<_1, _2, ?>>, AppB extends Applicative<_3Prime, App>, AppTrav extends Applicative> AppTrav traverse( + Function fn, Function pure) { + return fn.apply(_3).fmap(_3Prime -> fmap(constantly(_3Prime))).fmap(Applicative::coerce).coerce(); } /** 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 7e0f21ea0..92347c62a 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 @@ -145,10 +145,10 @@ public <_4Prime> Tuple4<_1, _2, _3, _4Prime> flatMap( } @Override - public <_4Prime, App extends Applicative> Applicative, App> traverse( - Function> fn, - Function>, ? extends Applicative>, App>> pure) { - return fn.apply(_4).fmap(_4Prime -> fmap(constantly(_4Prime))); + @SuppressWarnings("unchecked") + public <_4Prime, App extends Applicative, TravB extends Traversable<_4Prime, Tuple4<_1, _2, _3, ?>>, AppB extends Applicative<_4Prime, App>, AppTrav extends Applicative> AppTrav traverse( + Function fn, Function pure) { + return fn.apply(_4).fmap(_4Prime -> fmap(constantly(_4Prime))).fmap(Applicative::coerce).coerce(); } /** 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 065aef622..d087d4a48 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 @@ -156,10 +156,10 @@ public <_5Prime> Tuple5<_1, _2, _3, _4, _5Prime> flatMap( } @Override - public <_5Prime, App extends Applicative> Applicative, App> traverse( - Function> fn, - Function>, ? extends Applicative>, App>> pure) { - return fn.apply(_5).fmap(_5Prime -> fmap(constantly(_5Prime))); + @SuppressWarnings("unchecked") + public <_5Prime, App extends Applicative, TravB extends Traversable<_5Prime, Tuple5<_1, _2, _3, _4, ?>>, AppB extends Applicative<_5Prime, App>, AppTrav extends Applicative> AppTrav traverse( + Function fn, Function pure) { + return fn.apply(_5).fmap(_3Prime -> fmap(constantly(_3Prime))).fmap(Applicative::coerce).coerce(); } /** 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 5bba3c052..2d7e21ab6 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 @@ -171,10 +171,10 @@ public <_6Prime> Tuple6<_1, _2, _3, _4, _5, _6Prime> flatMap( } @Override - public <_6Prime, App extends Applicative> Applicative, App> traverse( - Function> fn, - Function>, ? extends Applicative>, App>> pure) { - return fn.apply(_6).fmap(_6Prime -> fmap(constantly(_6Prime))); + @SuppressWarnings("unchecked") + public <_6Prime, App extends Applicative, TravB extends Traversable<_6Prime, Tuple6<_1, _2, _3, _4, _5, ?>>, AppB extends Applicative<_6Prime, App>, AppTrav extends Applicative> AppTrav traverse( + Function fn, Function pure) { + return fn.apply(_6).fmap(_6Prime -> fmap(constantly(_6Prime))).fmap(Applicative::coerce).coerce(); } /** 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 bbe9ec79d..28f47406c 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 @@ -186,10 +186,10 @@ public <_7Prime> Tuple7<_1, _2, _3, _4, _5, _6, _7Prime> flatMap( } @Override - public <_7Prime, App extends Applicative> Applicative, App> traverse( - Function> fn, - Function>, ? extends Applicative>, App>> pure) { - return fn.apply(_7).fmap(_7Prime -> fmap(constantly(_7Prime))); + @SuppressWarnings("unchecked") + public <_7Prime, App extends Applicative, TravB extends Traversable<_7Prime, Tuple7<_1, _2, _3, _4, _5, _6, ?>>, AppB extends Applicative<_7Prime, App>, AppTrav extends Applicative> AppTrav traverse( + Function fn, Function pure) { + return fn.apply(_7).fmap(_7Prime -> fmap(constantly(_7Prime))).fmap(Applicative::coerce).coerce(); } /** 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 2a1d1615d..1226cc395 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 @@ -199,10 +199,10 @@ public <_8Prime> Tuple8<_1, _2, _3, _4, _5, _6, _7, _8Prime> flatMap( } @Override - public <_8Prime, App extends Applicative> Applicative, App> traverse( - Function> fn, - Function>, ? extends Applicative>, App>> pure) { - return fn.apply(_8).fmap(_8Prime -> fmap(constantly(_8Prime))); + @SuppressWarnings("unchecked") + public <_8Prime, App extends Applicative, TravB extends Traversable<_8Prime, Tuple8<_1, _2, _3, _4, _5, _6, _7, ?>>, AppB extends Applicative<_8Prime, App>, AppTrav extends Applicative> AppTrav traverse( + Function fn, Function pure) { + return fn.apply(_8).fmap(_8Prime -> fmap(constantly(_8Prime))).fmap(Applicative::coerce).coerce(); } /** diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Sequence.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Sequence.java index a7b4b777b..198e9feb9 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Sequence.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Sequence.java @@ -40,9 +40,8 @@ private Sequence() { } @Override - @SuppressWarnings("unchecked") public AppTrav apply(TravApp traversable, Function pure) { - return (AppTrav) traversable.traverse(id(), trav -> pure.apply((TravA) trav)); + return traversable.traverse(id(), pure); } @SuppressWarnings("unchecked") diff --git a/src/main/java/com/jnape/palatable/lambda/functions/recursion/RecursiveResult.java b/src/main/java/com/jnape/palatable/lambda/functions/recursion/RecursiveResult.java index 2d3bdbf53..555af77e5 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/recursion/RecursiveResult.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/recursion/RecursiveResult.java @@ -75,11 +75,9 @@ public RecursiveResult discardR(Applicative> @Override @SuppressWarnings("unchecked") - public Applicative, App> traverse( - Function> fn, - Function>, ? extends Applicative>, App>> pure) { - return match(__ -> pure.apply(coerce()).fmap(x -> (RecursiveResult) x), - b -> fn.apply(b).fmap(this::pure)); + public >, AppB extends Applicative, AppTrav extends Applicative> AppTrav traverse( + Function fn, Function pure) { + return match(__ -> pure.apply(coerce()), b -> fn.apply(b).fmap(this::pure).fmap(Applicative::coerce).coerce()); } public static RecursiveResult recurse(A a) { diff --git a/src/main/java/com/jnape/palatable/lambda/functor/builtin/Const.java b/src/main/java/com/jnape/palatable/lambda/functor/builtin/Const.java index 807aab0e5..6de15b769 100644 --- a/src/main/java/com/jnape/palatable/lambda/functor/builtin/Const.java +++ b/src/main/java/com/jnape/palatable/lambda/functor/builtin/Const.java @@ -75,10 +75,9 @@ public Const flatMap(Function Applicative, App> traverse( - Function> fn, - Function>, ? extends Applicative>, App>> pure) { - return pure.apply(coerce()).fmap(x -> (Const) x); + public >, AppB extends Applicative, AppTrav extends Applicative> AppTrav traverse( + Function fn, Function pure) { + return pure.apply(coerce()); } /** diff --git a/src/main/java/com/jnape/palatable/lambda/functor/builtin/Identity.java b/src/main/java/com/jnape/palatable/lambda/functor/builtin/Identity.java index 44a58a929..68e223c40 100644 --- a/src/main/java/com/jnape/palatable/lambda/functor/builtin/Identity.java +++ b/src/main/java/com/jnape/palatable/lambda/functor/builtin/Identity.java @@ -66,10 +66,10 @@ public Identity discardR(Applicative appB) { } @Override - public Applicative, App> traverse( - Function> fn, - Function, ? extends Applicative, App>> pure) { - return fn.apply(runIdentity()).fmap(Identity::new); + @SuppressWarnings("unchecked") + public , AppB extends Applicative, AppTrav extends Applicative> AppTrav traverse( + Function fn, Function pure) { + return (AppTrav) fn.apply(runIdentity()).fmap(Identity::new); } @Override diff --git a/src/main/java/com/jnape/palatable/lambda/traversable/LambdaIterable.java b/src/main/java/com/jnape/palatable/lambda/traversable/LambdaIterable.java index d0f492942..682fd81bf 100644 --- a/src/main/java/com/jnape/palatable/lambda/traversable/LambdaIterable.java +++ b/src/main/java/com/jnape/palatable/lambda/traversable/LambdaIterable.java @@ -83,11 +83,12 @@ public LambdaIterable flatMap(Function Applicative, App> traverse( - Function> fn, - Function, ? extends Applicative, App>> pure) { - return foldRight((a, appTrav) -> appTrav.zip(fn.apply(a).fmap(b -> bs -> LambdaIterable.wrap(cons(b, bs.unwrap())))), - pure.apply(LambdaIterable.empty()).fmap(ti -> (LambdaIterable) ti), + @SuppressWarnings("unchecked") + public , AppB extends Applicative, AppTrav extends Applicative> AppTrav traverse( + Function fn, + Function pure) { + return foldRight((a, appTrav) -> (AppTrav) appTrav.zip(fn.apply(a).fmap(b -> bs -> (TravB) wrap(cons(b, ((LambdaIterable) bs).unwrap())))), + (AppTrav) pure.apply((TravB) LambdaIterable.empty()), as); } diff --git a/src/main/java/com/jnape/palatable/lambda/traversable/Traversable.java b/src/main/java/com/jnape/palatable/lambda/traversable/Traversable.java index fd74b4502..aff6884da 100644 --- a/src/main/java/com/jnape/palatable/lambda/traversable/Traversable.java +++ b/src/main/java/com/jnape/palatable/lambda/traversable/Traversable.java @@ -36,22 +36,21 @@ public interface Traversable extends Functor { * Apply fn to each element of this traversable from left to right, and collapse the results into * a single resulting applicative, potentially with the assistance of the applicative's pure function. * - * @param fn the function to apply - * @param pure the applicative pure function - * @param the resulting element type - * @param the result applicative type + * @param fn the function to apply + * @param pure the applicative pure function + * @param the resulting element type + * @param the result applicative type + * @param this Traversable instance over B + * @param the result applicative instance over B + * @param the full inferred resulting type from the traversal * @return the traversed Traversable, wrapped inside an applicative */ - Applicative, App> traverse( - Function> fn, - Function, ? extends Applicative, App>> pure); + , AppB extends Applicative, + AppTrav extends Applicative> AppTrav traverse( + Function fn, Function pure); @Override - @SuppressWarnings({"Convert2MethodRef", "unchecked"}) default Traversable fmap(Function fn) { - return traverse(a -> new Identity<>(fn.apply(a)), x -> new Identity<>(x)) - .fmap(x -> (Traversable) x) - .>>coerce() - .runIdentity(); + return traverse(a -> new Identity(fn.apply(a)), Identity::new).runIdentity(); } } diff --git a/src/test/java/com/jnape/palatable/lambda/traversable/TraversableTest.java b/src/test/java/com/jnape/palatable/lambda/traversable/TraversableTest.java new file mode 100644 index 000000000..f27c2b214 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/traversable/TraversableTest.java @@ -0,0 +1,31 @@ +package com.jnape.palatable.lambda.traversable; + +import com.jnape.palatable.lambda.adt.Either; +import com.jnape.palatable.lambda.adt.Maybe; +import org.junit.Test; + +import static com.jnape.palatable.lambda.adt.Either.right; +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; +import static java.util.Arrays.asList; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; +import static testsupport.matchers.IterableMatcher.iterates; + +public class TraversableTest { + + @Test + public void compilation() { + Either> a = just(Either.right(1)).traverse(id(), Either::right); + assertEquals(right(just(1)), a); + + Maybe> b = Either.>right(just(1)).traverse(id(), Maybe::just); + assertEquals(just(right(1)), b); + + Either> c = b.traverse(id(), Either::right); + assertEquals(a, c); + + Maybe> d = LambdaIterable.wrap(asList(just(1), just(2))).traverse(id(), Maybe::just); + assertThat(d.orElseThrow(AssertionError::new).unwrap(), iterates(1, 2)); + } +} \ No newline at end of file diff --git a/src/test/java/testsupport/traits/TraversableLaws.java b/src/test/java/testsupport/traits/TraversableLaws.java index a7d15fbd8..8ce0ab0f0 100644 --- a/src/test/java/testsupport/traits/TraversableLaws.java +++ b/src/test/java/testsupport/traits/TraversableLaws.java @@ -60,7 +60,7 @@ private Maybe testComposition(Traversable trav) { Function> g = x -> new Identity<>(x); return trav.traverse(f.andThen(x -> x.fmap(g)).andThen(Compose::new), x -> new Compose<>(new Identity<>(new Identity<>(x)))) - .equals(new Compose>(trav.traverse(f, x -> new Identity<>(x)).fmap(t -> t.traverse(g, x -> new Identity<>(x))))) + .equals(new Compose<>(trav.traverse(f, x -> new Identity<>(x)).fmap(t -> t.traverse(g, x -> new Identity<>(x))))) ? nothing() : just("compose (trav.traverse(f.andThen(x -> x.fmap(g)).andThen(Compose::new), x -> new Compose<>(new Identity<>(new Identity<>(x))))\n" + " .equals(new Compose>(trav.traverse(f, x -> new Identity<>(x)).fmap(t -> t.traverse(g, x -> new Identity<>(x))))))"); From 2e4714da3d2705d70bf6f47b78fb299a47cf6c51 Mon Sep 17 00:00:00 2001 From: jnape Date: Sun, 4 Mar 2018 18:30:14 -0600 Subject: [PATCH 32/52] Fixing Javadocs --- .../palatable/lambda/functions/builtin/fn2/MagnetizeBy.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/MagnetizeBy.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/MagnetizeBy.java index 1e89e53a3..a7e512cc7 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/MagnetizeBy.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/MagnetizeBy.java @@ -20,10 +20,10 @@ * Given a binary predicate and an {@link Iterable}<A>, return an {@link Iterable}<{@link * Iterable}<A>> of the contiguous groups of elements that match the predicate pairwise. *

- * Example: magnetizeBy((x, y) -> x <= y, asList(1, 2, 3, 2, 2, 3, 2, 1)); // [[1, 2, 3], [2, 2, 3], [2], + * Example: magnetizeBy((x, y) -> x <= y, asList(1, 2, 3, 2, 2, 3, 2, 1)); // [[1, 2, 3], [2, 2, 3], [2], * [1]] * - * @param + * @param the {@link Iterable} element type */ public final class MagnetizeBy implements Fn2, Iterable, Iterable>> { From 76b0642a07b9dd761ea3d4208c905b34c9721625 Mon Sep 17 00:00:00 2001 From: jnape Date: Sat, 10 Mar 2018 14:49:25 -0600 Subject: [PATCH 33/52] Flatten is lazier --- CHANGELOG.md | 3 ++- .../palatable/lambda/iteration/FlatteningIterator.java | 2 +- .../lambda/iteration/FlatteningIteratorTest.java | 9 ++++++++- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 993f15d2d..9b181298f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,7 +12,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/). - `Identity`, `Compose`, and `Const` functors all have better `toString` implementations - `Into3-8` now supports functions with parameter variance - `HListLens#tail` is now covariant in `Tail` parameter -- More functions now automatically deforest nested calls (`concat` `cons`, `cycle`, `distinct`, `drop`, `dropwhile`, `filter`, `map`, `reverse`, `snoc`, `take`, `takewhile`, `tail`) +- More functions now automatically deforest nested calls (`concat` `cons`, `cycle`, `distinct`, `drop`, `dropwhile`, `filter`, `map`, `reverse`, `snoc`, `take`, `takewhile`, `tail`) +- `Flatten` calls `Iterator#hasNext` less aggressively, allowing for better laziness ### Added - `BoundedBifunctor`, a `Bifunctor` super type that offers upper bounds for both parameters diff --git a/src/main/java/com/jnape/palatable/lambda/iteration/FlatteningIterator.java b/src/main/java/com/jnape/palatable/lambda/iteration/FlatteningIterator.java index f9e16ae24..40458cd48 100644 --- a/src/main/java/com/jnape/palatable/lambda/iteration/FlatteningIterator.java +++ b/src/main/java/com/jnape/palatable/lambda/iteration/FlatteningIterator.java @@ -13,7 +13,7 @@ public FlatteningIterator(Iterator> xss) { @Override public boolean hasNext() { - while (xss.hasNext() && (xs == null || !xs.hasNext())) + while ((xs == null || !xs.hasNext()) && xss.hasNext()) xs = xss.next().iterator(); return xs != null && xs.hasNext(); diff --git a/src/test/java/com/jnape/palatable/lambda/iteration/FlatteningIteratorTest.java b/src/test/java/com/jnape/palatable/lambda/iteration/FlatteningIteratorTest.java index 481b2da6f..7288e8fbc 100644 --- a/src/test/java/com/jnape/palatable/lambda/iteration/FlatteningIteratorTest.java +++ b/src/test/java/com/jnape/palatable/lambda/iteration/FlatteningIteratorTest.java @@ -2,6 +2,8 @@ import org.junit.Test; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Magnetize.magnetize; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Repeat.repeat; import static java.util.Arrays.asList; import static java.util.Collections.emptyIterator; import static java.util.Collections.emptyList; @@ -25,10 +27,15 @@ public void doesNotHaveNextForIterableOfEmptyIterables() { @Test public void iteratesNestedIterables() { - FlatteningIterator iterator = new FlatteningIterator<>(asList(singletonList(1), asList(2,3), emptyList()).iterator()); + FlatteningIterator iterator = new FlatteningIterator<>(asList(singletonList(1), asList(2, 3), emptyList()).iterator()); assertEquals(1, iterator.next()); assertEquals(2, iterator.next()); assertEquals(3, iterator.next()); assertFalse(iterator.hasNext()); } + + @Test(timeout = 500) + public void betterLaziness() { + assertEquals((Integer) 1, new FlatteningIterator<>(magnetize(repeat(1)).iterator()).next()); + } } \ No newline at end of file From c87b7ddd026bea301bce1213d82e3ebb4f5e4cd1 Mon Sep 17 00:00:00 2001 From: jnape Date: Thu, 8 Mar 2018 20:45:55 -0600 Subject: [PATCH 34/52] Adding Try#orThrow --- .../com/jnape/palatable/lambda/adt/Try.java | 18 ++++++++++++++++++ .../jnape/palatable/lambda/adt/TryTest.java | 19 +++++++++++++++++-- 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/jnape/palatable/lambda/adt/Try.java b/src/main/java/com/jnape/palatable/lambda/adt/Try.java index 710e4b42e..0a2169f09 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/Try.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/Try.java @@ -97,6 +97,14 @@ public final T forfeit(Function fn) { return match(id(), fn); } + /** + * If this is a success value, return it. Otherwise, rethrow the captured failure. + * + * @return possibly the success value + * @throws T the possible failure + */ + public abstract A orThrow() throws T; + /** * If this is a success, wrap the value in a {@link Maybe#just} and return it. Otherwise, return {@link * Maybe#nothing()}. @@ -245,6 +253,11 @@ private Failure(T t) { this.t = t; } + @Override + public A orThrow() throws T { + throw t; + } + @Override public R match(Function aFn, Function bFn) { return aFn.apply(t); @@ -275,6 +288,11 @@ private Success(A a) { this.a = a; } + @Override + public A orThrow() throws T { + return a; + } + @Override public R match(Function aFn, Function bFn) { return bFn.apply(a); diff --git a/src/test/java/com/jnape/palatable/lambda/adt/TryTest.java b/src/test/java/com/jnape/palatable/lambda/adt/TryTest.java index ba874efe6..362671cf6 100644 --- a/src/test/java/com/jnape/palatable/lambda/adt/TryTest.java +++ b/src/test/java/com/jnape/palatable/lambda/adt/TryTest.java @@ -3,7 +3,9 @@ import com.jnape.palatable.traitor.annotations.TestTraits; import com.jnape.palatable.traitor.framework.Subjects; import com.jnape.palatable.traitor.runners.Traits; +import org.junit.Rule; import org.junit.Test; +import org.junit.rules.ExpectedException; import org.junit.runner.RunWith; import testsupport.traits.ApplicativeLaws; import testsupport.traits.FunctorLaws; @@ -18,8 +20,10 @@ import static com.jnape.palatable.lambda.adt.Maybe.nothing; import static com.jnape.palatable.lambda.adt.Try.failure; import static com.jnape.palatable.lambda.adt.Try.success; +import static com.jnape.palatable.lambda.adt.Try.trying; import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; import static com.jnape.palatable.traitor.framework.Subjects.subjects; +import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.instanceOf; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThat; @@ -28,6 +32,8 @@ @RunWith(Traits.class) public class TryTest { + @Rule public ExpectedException thrown = ExpectedException.none(); + @TestTraits({FunctorLaws.class, ApplicativeLaws.class, MonadLaws.class, TraversableLaws.class}) public Subjects> testSubject() { return subjects(failure(new IllegalStateException()), success(1)); @@ -108,6 +114,15 @@ public void recoverEnsuresSuccess() { assertEquals((Integer) 1, Try.failure(new IllegalArgumentException()).recover(constantly(1))); } + @Test + public void orThrow() throws Throwable { + assertEquals((Integer) 1, trying(() -> 1).orThrow()); + + Throwable expected = new Exception("expected"); + thrown.expect(equalTo(expected)); + trying(() -> {throw expected;}).orThrow(); + } + @Test public void toMaybe() { assertEquals(just("foo"), Try.success("foo").toMaybe()); @@ -131,8 +146,8 @@ public void toEitherWithLeftMappingFunction() { @Test public void tryingCatchesAnyThrowableThrownDuringEvaluation() { IllegalStateException expected = new IllegalStateException(); - assertEquals(failure(expected), Try.trying(() -> {throw expected;})); + assertEquals(failure(expected), trying(() -> {throw expected;})); - assertEquals(success("foo"), Try.trying(() -> "foo")); + assertEquals(success("foo"), trying(() -> "foo")); } } \ No newline at end of file From 4c91720678e76b4bc7bc9696c5908a8c220ef65f Mon Sep 17 00:00:00 2001 From: jnape Date: Sat, 10 Mar 2018 14:52:00 -0600 Subject: [PATCH 35/52] Lens.Simple#both logically allows differently focused lenses --- src/main/java/com/jnape/palatable/lambda/lens/Lens.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/jnape/palatable/lambda/lens/Lens.java b/src/main/java/com/jnape/palatable/lambda/lens/Lens.java index badea035b..b0f84ca93 100644 --- a/src/main/java/com/jnape/palatable/lambda/lens/Lens.java +++ b/src/main/java/com/jnape/palatable/lambda/lens/Lens.java @@ -374,10 +374,11 @@ static Simple adapt(Lens lens) { * @param f the first lens * @param g the second lens * @param both lens larger values - * @param both lens smaller values + * @param lens f smaller values + * @param lens g smaller values * @return the dual-focus simple lens */ - static Lens.Simple> both(Lens f, Lens g) { + static Lens.Simple> both(Lens f, Lens g) { return adapt(Lens.both(f, g)); } From 150d9e6b0130590eb0a54b2a2609bf49c2bcca94 Mon Sep 17 00:00:00 2001 From: jnape Date: Sat, 10 Mar 2018 16:54:17 -0600 Subject: [PATCH 36/52] CheckedRunnable and CheckedSupplier convenience/conversion methods --- CHANGELOG.md | 1 + .../specialized/checked/CheckedFn1.java | 7 ++++ .../specialized/checked/CheckedRunnable.java | 33 +++++++++++++++++++ .../specialized/checked/CheckedSupplier.java | 22 +++++++++++++ 4 files changed, 63 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9b181298f..058dfef78 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/). - `Both`, for dually applying two functions and producing a `Tuple2` of their results - `Lens#both`, for dually focusing with two lenses at once - `IfThenElse`, an expression form for `if` statements +- `CheckedRunnable` and `CheckedSupplier` conversion and convenience methods ### Deprecated - `Either#trying` in favor of `Try#trying` diff --git a/src/main/java/com/jnape/palatable/lambda/functions/specialized/checked/CheckedFn1.java b/src/main/java/com/jnape/palatable/lambda/functions/specialized/checked/CheckedFn1.java index fb9710082..3d5a9176c 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/specialized/checked/CheckedFn1.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/specialized/checked/CheckedFn1.java @@ -26,5 +26,12 @@ default B apply(A a) { } } + /** + * A version of {@link Fn1#apply} that can throw checked exceptions. + * + * @param a the function argument + * @return the application of the argument to the function + * @throws T any exception that can be thrown by this method + */ B checkedApply(A a) throws T; } diff --git a/src/main/java/com/jnape/palatable/lambda/functions/specialized/checked/CheckedRunnable.java b/src/main/java/com/jnape/palatable/lambda/functions/specialized/checked/CheckedRunnable.java index f8fb8734e..2552c746e 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/specialized/checked/CheckedRunnable.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/specialized/checked/CheckedRunnable.java @@ -1,5 +1,8 @@ package com.jnape.palatable.lambda.functions.specialized.checked; +import com.jnape.palatable.lambda.adt.Unit; + +import static com.jnape.palatable.lambda.adt.Unit.UNIT; import static com.jnape.palatable.lambda.functions.specialized.checked.Runtime.throwChecked; /** @@ -21,5 +24,35 @@ default void run() { } } + /** + * A version of {@link Runnable#run()} that can throw checked exceptions. + * + * @throws T any exception that can be thrown by this method + */ void checkedRun() throws T; + + /** + * Convert this {@link CheckedRunnable} to a {@link CheckedSupplier} that returns {@link Unit}. + * + * @return the checked supplier + */ + default CheckedSupplier toSupplier() { + return () -> { + run(); + return UNIT; + }; + } + + /** + * Convenience static factory method for constructing a {@link CheckedRunnable} without an explicit cast or type + * attribution at the call site. + * + * @param runnable the checked runnable + * @param the inferred Throwable type + * @return the checked runnable + */ + static CheckedRunnable checked(CheckedRunnable runnable) { + return runnable::run; + } + } diff --git a/src/main/java/com/jnape/palatable/lambda/functions/specialized/checked/CheckedSupplier.java b/src/main/java/com/jnape/palatable/lambda/functions/specialized/checked/CheckedSupplier.java index 832bea3bc..b32f1e4b8 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/specialized/checked/CheckedSupplier.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/specialized/checked/CheckedSupplier.java @@ -31,4 +31,26 @@ default A get() { * @throws T any exception that can be thrown by this method */ A checkedGet() throws T; + + /** + * Convert this {@link CheckedSupplier} to a {@link CheckedRunnable}. + * + * @return the checked runnable + */ + default CheckedRunnable toRunnable() { + return this::get; + } + + /** + * Convenience static factory method for constructing a {@link CheckedSupplier} without an explicit cast or type + * attribution at the call site. + * + * @param supplier the checked supplier + * @param the inferred Throwable type + * @param the supplier return type + * @return the checked supplier + */ + static CheckedSupplier checked(CheckedSupplier supplier) { + return supplier::get; + } } From d2e0c1dc8a9e64b63b2a37ffb01adb43bc82ab58 Mon Sep 17 00:00:00 2001 From: jnape Date: Wed, 14 Mar 2018 01:31:15 -0500 Subject: [PATCH 37/52] Adding Either#trying back until I decide if I should deprecate it --- CHANGELOG.md | 3 --- .../com/jnape/palatable/lambda/adt/Either.java | 5 ----- .../com/jnape/palatable/lambda/adt/Maybe.java | 6 ++---- .../jnape/palatable/lambda/adt/EitherTest.java | 18 ++++++++++++++++++ 4 files changed, 20 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 058dfef78..909a79023 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,9 +29,6 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/). - `IfThenElse`, an expression form for `if` statements - `CheckedRunnable` and `CheckedSupplier` conversion and convenience methods -### Deprecated -- `Either#trying` in favor of `Try#trying` - ## [2.1.1] - 2018-01-16 ### Changed - ***Breaking Change***: Moved `Trampoline` and `RecursiveResult` to better package diff --git a/src/main/java/com/jnape/palatable/lambda/adt/Either.java b/src/main/java/com/jnape/palatable/lambda/adt/Either.java index c596662e4..76545bf68 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/Either.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/Either.java @@ -286,10 +286,7 @@ public static Either fromMaybe(Maybe maybe, Supplier leftFn) * @param the left parameter type * @param the right parameter type * @return the supplier result as a right value, or leftFn's mapping result as a left value - * @deprecated in favor of {@link Try#trying(CheckedSupplier)} */ - @Deprecated - @SuppressWarnings("unchecked") public static Either trying(CheckedSupplier supplier, Function leftFn) { return Try.trying(supplier::get).toEither(leftFn); @@ -303,9 +300,7 @@ public static Either trying(CheckedSupplier the left parameter type (the most contravariant exception that supplier might throw) * @param the right parameter type * @return the supplier result as a right value, or a left value of the thrown exception - * @deprecated in favor of {@link Try#trying(CheckedSupplier)} */ - @Deprecated public static Either trying(CheckedSupplier supplier) { return trying(supplier, id()); } diff --git a/src/main/java/com/jnape/palatable/lambda/adt/Maybe.java b/src/main/java/com/jnape/palatable/lambda/adt/Maybe.java index e5dad5f19..93a700061 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/Maybe.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/Maybe.java @@ -14,8 +14,6 @@ import java.util.function.Supplier; import static com.jnape.palatable.lambda.adt.Either.left; -import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; -import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; /** * The optional type, representing a potentially absent value. This is lambda's analog of {@link Optional}, supporting @@ -153,7 +151,7 @@ public final Maybe peek(Consumer consumer) { * @return "Just" the right value, or nothing */ public static Maybe fromEither(Either either) { - return either.match(constantly(nothing()), Maybe::just); + return either.toMaybe(); } /** @@ -164,7 +162,7 @@ public static Maybe fromEither(Either either) { * @return the equivalent Maybe instance */ public static Maybe fromOptional(Optional optional) { - return optional.map(id()).map(Maybe::just).orElse(Maybe.nothing()); + return optional.map(Maybe::just).orElse(Maybe.nothing()); } /** diff --git a/src/test/java/com/jnape/palatable/lambda/adt/EitherTest.java b/src/test/java/com/jnape/palatable/lambda/adt/EitherTest.java index c1af81677..407197a8d 100644 --- a/src/test/java/com/jnape/palatable/lambda/adt/EitherTest.java +++ b/src/test/java/com/jnape/palatable/lambda/adt/EitherTest.java @@ -168,6 +168,24 @@ public void fromMaybeDoesNotEvaluateLeftFnForRight() { assertThat(atomicInteger.get(), is(0)); } + @Test + public void monadicTryingLiftsCheckedSupplier() { + assertEquals(right(1), Either.trying(() -> 1)); + + Exception checkedException = new Exception("expected"); + assertEquals(left(checkedException), Either.trying(() -> { + throw checkedException; + })); + } + + @Test + public void dyadicTryingLiftsCheckedSupplierMappingAnyThrownExceptions() { + assertEquals(right(1), Either.trying(() -> 1, Throwable::getMessage)); + assertEquals(left("expected"), Either.trying(() -> { + throw new Exception("expected"); + }, Throwable::getMessage)); + } + @Test public void monadicPeekLiftsIOToTheRight() { Either left = left("foo"); From 36b0ce5c9847e7d9ea736573e397266fd584cfa6 Mon Sep 17 00:00:00 2001 From: Attila Domokos Date: Wed, 28 Mar 2018 15:42:07 -0500 Subject: [PATCH 38/52] Fix typo and clean up white space --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index d5f7e440c..dcfe6df73 100644 --- a/README.md +++ b/README.md @@ -366,11 +366,11 @@ public static void main(String[] args) { } ``` -In the previous example, if any of `parseId`, `lookupById`, or `getOccupation` fail, no further `flatMap` computations can succeed, so the result short-circuits to the first `left` value that is returned. This is completely predictable from the type signature of `Monad` and `Either`: `Either` is a `Monad`, so the single arity `flatMap` can have nothing to map in the case where there is no `R` value. With experience, it generally becomes quickly clear what the logical behavior of `flatMap` *must* be given the type signatures. +In the previous example, if any of `parseId`, `lookupById`, or `getOccupation` fail, no further `flatMap` computations can succeed, so the result short-circuits to the first `left` value that is returned. This is completely predictable from the type signature of `Monad` and `Either`: `Either` is a `Monad`, so the single arity `flatMap` can have nothing to map in the case where there is no `R` value. With experience, it generally becomes quickly clear what the logical behavior of `flatMap` *must* be given the type signatures. That's it. Monads are neither [elephants](http://james-iry.blogspot.com/2007/09/monads-are-elephants-part-1.html) nor are they [burritos](https://blog.plover.com/prog/burritos.html); they're simply types that support a) the ability to lift a value into them, and b) a chaining function `flatMap :: (a -> f b) -> f a -> f b` that can potentially return different instances of the same monad. If a type can do those two things (and obeys [the laws](https://wiki.haskell.org/Monad_laws)), it is a monad. -Further, if a type is a monad, it is necessarily an `Applicative`, which makes it necessariliy a `Functor`, so *lambda* enforces this tautology via a hierarchical constraint. +Further, if a type is a monad, it is necessarily an `Applicative`, which makes it necessarily a `Functor`, so *lambda* enforces this tautology via a hierarchical constraint. ### Traversables @@ -504,7 +504,7 @@ Optional.of(1).map(nullResultFn); // Optional.empty() Maybe.just(1).fmap(nullResultFn); // throws NullPointerException Maybe.just(1).flatMap(nullResultFn.andThen(Maybe::maybe)); // Nothing -``` +``` ### Heterogeneous Lists (HLists) @@ -601,7 +601,7 @@ HMap hmap = HMap.hMap(stringKey, "string value", Optional stringValue = hmap.get(stringKey); // Optional["string value"] Optional intValue = hmap.get(intKey); // Optional[1] Optional anotherIntValue = hmap.get(anotherIntKey); // Optional.empty -``` +``` ### CoProducts From f0009b832c1bbfd3bd41df3978f9461fed89dd18 Mon Sep 17 00:00:00 2001 From: jnape Date: Fri, 30 Mar 2018 17:12:46 -0500 Subject: [PATCH 39/52] Adding HMapLens, lenses for HMaps --- .../lambda/lens/lenses/HMapLens.java | 27 +++++++++++++++++ .../lambda/lens/lenses/HMapLensTest.java | 30 +++++++++++++++++++ 2 files changed, 57 insertions(+) create mode 100644 src/main/java/com/jnape/palatable/lambda/lens/lenses/HMapLens.java create mode 100644 src/test/java/com/jnape/palatable/lambda/lens/lenses/HMapLensTest.java diff --git a/src/main/java/com/jnape/palatable/lambda/lens/lenses/HMapLens.java b/src/main/java/com/jnape/palatable/lambda/lens/lenses/HMapLens.java new file mode 100644 index 000000000..88b8e9e48 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/lens/lenses/HMapLens.java @@ -0,0 +1,27 @@ +package com.jnape.palatable.lambda.lens.lenses; + +import com.jnape.palatable.lambda.adt.Maybe; +import com.jnape.palatable.lambda.adt.hmap.HMap; +import com.jnape.palatable.lambda.adt.hmap.TypeSafeKey; +import com.jnape.palatable.lambda.lens.Lens; + +import static com.jnape.palatable.lambda.lens.Lens.simpleLens; + +/** + * Lenses that operate on {@link HMap}s. + */ +public final class HMapLens { + private HMapLens() { + } + + /** + * A lens that focuses on a value at a {@link TypeSafeKey}<A> in an {@link HMap}, as a {@link Maybe}. + * + * @param key the key + * @param the value type at the key + * @return the lens + */ + public static Lens.Simple> valueAt(TypeSafeKey key) { + return simpleLens(m -> m.get(key), (m, maybeA) -> maybeA.fmap(a -> m.put(key, a)).orElseGet(() -> m.remove(key))); + } +} diff --git a/src/test/java/com/jnape/palatable/lambda/lens/lenses/HMapLensTest.java b/src/test/java/com/jnape/palatable/lambda/lens/lenses/HMapLensTest.java new file mode 100644 index 000000000..34dd1dbac --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/lens/lenses/HMapLensTest.java @@ -0,0 +1,30 @@ +package com.jnape.palatable.lambda.lens.lenses; + +import com.jnape.palatable.lambda.adt.hmap.TypeSafeKey; +import org.junit.Test; + +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.hmap.HMap.emptyHMap; +import static com.jnape.palatable.lambda.adt.hmap.HMap.hMap; +import static com.jnape.palatable.lambda.adt.hmap.HMap.singletonHMap; +import static com.jnape.palatable.lambda.adt.hmap.TypeSafeKey.typeSafeKey; +import static java.util.Arrays.asList; +import static testsupport.assertion.LensAssert.assertLensLawfulness; + +public class HMapLensTest { + + @Test + public void valueAt() { + TypeSafeKey key = typeSafeKey(); + assertLensLawfulness(HMapLens.valueAt(key), + asList(emptyHMap(), + singletonHMap(key, "foo"), + hMap(key, "foo", + typeSafeKey(), "bar"), + singletonHMap(typeSafeKey(), "bar")), + asList(nothing(), + just("foo"), + just("bar"))); + } +} \ No newline at end of file From cbac3f50a762427fe7108678480b6c74b4aef626 Mon Sep 17 00:00:00 2001 From: jnape Date: Sat, 14 Apr 2018 17:18:03 -0500 Subject: [PATCH 40/52] Finally adding ISO, isomorphisms between types --- CHANGELOG.md | 10 + .../lambda/functor/builtin/Exchange.java | 56 ++++ .../com/jnape/palatable/lambda/lens/Iso.java | 293 ++++++++++++++++++ .../com/jnape/palatable/lambda/lens/Lens.java | 139 +++------ .../jnape/palatable/lambda/lens/LensLike.java | 121 ++++++++ .../palatable/lambda/lens/functions/Over.java | 18 +- .../palatable/lambda/lens/functions/Set.java | 12 +- .../palatable/lambda/lens/functions/View.java | 12 +- .../palatable/lambda/lens/lenses/MapLens.java | 21 +- .../jnape/palatable/lambda/lens/IsoTest.java | 57 ++++ .../jnape/palatable/lambda/lens/LensTest.java | 19 +- .../lambda/lens/lenses/MapLensTest.java | 37 +-- .../java/testsupport/EqualityAwareIso.java | 72 +++++ .../java/testsupport/EqualityAwareLens.java | 6 +- .../testsupport/assertion/LensAssert.java | 6 +- 15 files changed, 713 insertions(+), 166 deletions(-) create mode 100644 src/main/java/com/jnape/palatable/lambda/functor/builtin/Exchange.java create mode 100644 src/main/java/com/jnape/palatable/lambda/lens/Iso.java create mode 100644 src/main/java/com/jnape/palatable/lambda/lens/LensLike.java create mode 100644 src/test/java/com/jnape/palatable/lambda/lens/IsoTest.java create mode 100644 src/test/java/testsupport/EqualityAwareIso.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 909a79023..ce9f78679 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/). - `HListLens#tail` is now covariant in `Tail` parameter - More functions now automatically deforest nested calls (`concat` `cons`, `cycle`, `distinct`, `drop`, `dropwhile`, `filter`, `map`, `reverse`, `snoc`, `take`, `takewhile`, `tail`) - `Flatten` calls `Iterator#hasNext` less aggressively, allowing for better laziness +- `Lens` subtypes `LensLike` +- `View`/`Set`/`Over` now only require `LensLike` ### Added - `BoundedBifunctor`, a `Bifunctor` super type that offers upper bounds for both parameters @@ -28,6 +30,14 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/). - `Lens#both`, for dually focusing with two lenses at once - `IfThenElse`, an expression form for `if` statements - `CheckedRunnable` and `CheckedSupplier` conversion and convenience methods +- `LensLike`, common capabilities that make a type usable as if it were a `Lens` +- `Iso`, isomorphisms between two types (invertible functions that are also lenses) +- `Exchange`, a `Profunctor` that can extract the morphisms from an `Iso` +- `HMapLens`, lenses focusing on `HMap` +- `MapLens#mappingValues(Iso)`, a lawful lens that maps the values of a `j.u.Map` + +### Deprecated +- `MapLens#mappingValues(Function)` is now deprecated in favor of the overload that takes an Iso ## [2.1.1] - 2018-01-16 ### Changed diff --git a/src/main/java/com/jnape/palatable/lambda/functor/builtin/Exchange.java b/src/main/java/com/jnape/palatable/lambda/functor/builtin/Exchange.java new file mode 100644 index 000000000..1a9439d6b --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functor/builtin/Exchange.java @@ -0,0 +1,56 @@ +package com.jnape.palatable.lambda.functor.builtin; + +import com.jnape.palatable.lambda.functor.Profunctor; +import com.jnape.palatable.lambda.lens.Iso; + +import java.util.function.Function; + +/** + * A profunctor used to extract the isomorphic functions an {@link Iso} is composed of. + * + * @param the smaller viewed value of an {@link Iso} + * @param the smaller viewing value of an {@link Iso} + * @param the larger viewing value of an {@link Iso} + * @param the larger viewed value of an {@link Iso} + */ +public final class Exchange implements Profunctor> { + private final Function sa; + private final Function bt; + + public Exchange(Function sa, Function bt) { + this.sa = sa; + this.bt = bt; + } + + public Function sa() { + return sa; + } + + public Function bt() { + return bt; + } + + @Override + public Exchange diMap(Function lFn, + Function rFn) { + return new Exchange<>(lFn.andThen(sa), bt.andThen(rFn)); + } + + @Override + @SuppressWarnings("unchecked") + public Exchange diMapL(Function fn) { + return (Exchange) Profunctor.super.diMapL(fn); + } + + @Override + @SuppressWarnings("unchecked") + public Exchange diMapR(Function fn) { + return (Exchange) Profunctor.super.diMapR(fn); + } + + @Override + @SuppressWarnings("unchecked") + public Exchange contraMap(Function fn) { + return (Exchange) Profunctor.super.contraMap(fn); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/lens/Iso.java b/src/main/java/com/jnape/palatable/lambda/lens/Iso.java new file mode 100644 index 000000000..fdc6e1d08 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/lens/Iso.java @@ -0,0 +1,293 @@ +package com.jnape.palatable.lambda.lens; + +import com.jnape.palatable.lambda.adt.hlist.Tuple2; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.Fn2; +import com.jnape.palatable.lambda.functor.Applicative; +import com.jnape.palatable.lambda.functor.Functor; +import com.jnape.palatable.lambda.functor.Profunctor; +import com.jnape.palatable.lambda.functor.builtin.Exchange; +import com.jnape.palatable.lambda.functor.builtin.Identity; +import com.jnape.palatable.lambda.lens.functions.Over; +import com.jnape.palatable.lambda.lens.functions.Set; +import com.jnape.palatable.lambda.lens.functions.View; +import com.jnape.palatable.lambda.monad.Monad; + +import java.util.function.Function; + +import static com.jnape.palatable.lambda.functions.Fn1.fn1; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; +import static com.jnape.palatable.lambda.lens.Iso.Simple.adapt; +import static com.jnape.palatable.lambda.lens.functions.View.view; + +/** + * An {@link Iso} (short for "isomorphism") is an invertible {@link Lens}: a {@link LensLike} encoding of a + * bi-directional focusing of two types, and like {@link Lens}es, can be {@link View}ed, + * {@link Set}, and {@link Over}ed. + *

+ * As an example, consider the isomorphism between valid {@link String}s and {@link Integer}s: + *

+ * {@code
+ * Iso stringIntIso = Iso.iso(Integer::parseInt, Object::toString);
+ * Integer asInt = view(stringIntIso, "123"); // 123
+ * String asString = view(stringIntIso.mirror(), 123); // "123"
+ * }
+ * 
+ * In the previous example, stringIntIso can be viewed as a {@link Lens}<String, String, Integer, + * Integer>, and can be {@link Iso#mirror}ed and viewed as a {@link Lens}<Integer, + * Integer, String, String>. + *

+ * As with {@link Lens}, variance is supported between S/T and A/B, and where these pairs do + * not vary, a {@link Simple} iso can be used (for instance, in the previous example, stringIntIso could + * have had the simplified Iso.Simple<String, Integer> type). + *

, FT extends Functor, + PAFB extends Profunctor, + PSFT extends Profunctor> PSFT apply(PAFB pafb); + + @Override + default , FB extends Functor> FT apply( + Function fn, S s) { + return this., Fn1>apply(fn1(fn)).apply(s); + } + + /** + * Convert this {@link Iso} into a {@link Lens}. + * + * @return the equivalent lens + */ + default Lens toLens() { + return new Lens() { + @Override + public , FB extends Functor> FT apply( + Function fn, S s) { + return Iso.this.apply(fn1(fn), s); + } + }; + } + + /** + * Flip this {@link Iso} around. + * + * @return the mirrored {@link Iso} + */ + default Iso mirror() { + return unIso().into((sa, bt) -> iso(bt, sa)); + } + + /** + * Destructure this {@link Iso} into the two functions S -< A and B -< T that + * constitute the isomorphism. + * + * @return the destructured iso + */ + default Tuple2, ? extends Function> unIso() { + return Tuple2.fill(this., Identity, Identity, Identity, + Exchange>, + Exchange>>apply(new Exchange<>(id(), Identity::new)).diMapR(Identity::runIdentity)) + .biMap(Exchange::sa, Exchange::bt); + } + + @Override + default Iso fmap(Function fn) { + return LensLike.super.fmap(fn).coerce(); + } + + @Override + default Iso pure(U u) { + return iso(view(this), constantly(u)); + } + + @Override + default Iso zip(Applicative, LensLike> appFn) { + return LensLike.super.zip(appFn).coerce(); + } + + @Override + default Iso discardL(Applicative> appB) { + return LensLike.super.discardL(appB).coerce(); + } + + @Override + default Iso discardR(Applicative> appB) { + return LensLike.super.discardR(appB).coerce(); + } + + @Override + default Iso flatMap(Function>> fn) { + return unIso().fmap(bt -> Fn2.fn2(fn1(bt.andThen(fn.>andThen(Applicative::coerce)) + .andThen(Iso::unIso) + .andThen(Tuple2::_2) + .andThen(Fn1::fn1)))) + .fmap(Fn2::uncurry) + .fmap(bbu -> bbu.diMapL(Tuple2::fill)) + .into(Iso::iso); + } + + @Override + default Iso diMapL(Function fn) { + return LensLike.super.diMapL(fn).coerce(); + } + + @Override + default Iso diMapR(Function fn) { + return LensLike.super.diMapR(fn).coerce(); + } + + @Override + default Iso diMap(Function lFn, + Function rFn) { + return LensLike.super.diMap(lFn, rFn).coerce(); + } + + @Override + default Iso contraMap(Function fn) { + return LensLike.super.contraMap(fn).coerce(); + } + + @Override + default Iso mapS(Function fn) { + return unIso().biMapL(f -> f.compose(fn)).into(Iso::iso); + } + + @Override + default Iso mapT(Function fn) { + return unIso().biMapR(f -> f.andThen(fn)).into(Iso::iso); + } + + @Override + default Iso mapA(Function fn) { + return unIso().biMapL(f -> f.andThen(fn)).into(Iso::iso); + } + + @Override + default Iso mapB(Function fn) { + return unIso().biMapR(f -> f.compose(fn)).into(Iso::iso); + } + + /** + * Left-to-right composition of {@link Iso}. + * + * @param f the iso to apply after this one + * @param the smaller type the first larger type can be viewed as + * @param the smaller type that can be viewed as the second larger type + * @return the composed {@link Iso} + */ + default Iso andThen(Iso f) { + return unIso().into((sa, bt) -> f.unIso().into((ac, db) -> iso(sa.andThen(ac), db.andThen(bt)))); + } + + /** + * Right-to-left composition of {@link Iso}. + * + * @param g the iso to apply before this one + * @param the larger type that can be viewed as the first smaller type + * @param the larger type the second smaller type can be viewed as + * @return the composed {@link Iso} + */ + default Iso compose(Iso g) { + return g.andThen(this); + } + + /** + * Static factory method for creating an iso from a function and it's inverse. + * + * @param f the function + * @param g f's inverse + * @param the larger type for focusing + * @param the larger type for mirrored focusing + * @param the smaller type for focusing + * @param the smaller type for mirrored focusing + * @return the iso + */ + static Iso iso(Function f, + Function g) { + return new Iso() { + @Override + @SuppressWarnings("unchecked") + public

, FT extends Functor, PAFB extends Profunctor, PSFT extends Profunctor> PSFT apply( + PAFB pafb) { + return (PSFT) pafb.diMap(f, fb -> (FT) fb.fmap(g)); + } + }; + } + + /** + * Static factory method for creating a simple {@link Iso} from a function and its inverse. + * + * @param f a function + * @param g f's inverse + * @param one side of the isomorphism + * @param the other side of the isomorphism + * @return the simple iso + */ + static Iso.Simple simpleIso(Function f, Function g) { + return adapt(iso(f, g)); + } + + /** + * A convenience type with a simplified type signature for common isos with both unified "larger" values and + * unified "smaller" values. + * + * @param the type of both "larger" values + * @param the type of both "smaller" values + */ + interface Simple extends Iso, LensLike.Simple { + + @Override + default Iso.Simple mirror() { + return Iso.Simple.adapt(Iso.super.mirror()); + } + + @Override + default Lens.Simple toLens() { + return Lens.Simple.adapt(Iso.super.toLens()); + } + + /** + * Compose two simple isos from right to left. + * + * @param g the other simple iso + * @param the other simple iso' larger type + * @return the composed simple iso + */ + default Iso.Simple compose(Iso.Simple g) { + return g.andThen(this); + } + + /** + * Compose two simple isos from left to right. + * + * @param f the other simple iso + * @param the other simple iso' smaller type + * @return the composed simple iso + */ + default Iso.Simple andThen(Iso.Simple f) { + return Iso.Simple.adapt(f.compose(this)); + } + + /** + * Adapt an {@link Iso} with the right variance to an {@link Iso.Simple}. + * + * @param iso the iso + * @param S/T + * @param A/B + * @return the simple iso + */ + @SuppressWarnings("unchecked") + static Iso.Simple adapt(Iso iso) { + return iso::apply; + } + } +} \ No newline at end of file diff --git a/src/main/java/com/jnape/palatable/lambda/lens/Lens.java b/src/main/java/com/jnape/palatable/lambda/lens/Lens.java index b0f84ca93..4b407ec8f 100644 --- a/src/main/java/com/jnape/palatable/lambda/lens/Lens.java +++ b/src/main/java/com/jnape/palatable/lambda/lens/Lens.java @@ -2,11 +2,9 @@ import com.jnape.palatable.lambda.adt.hlist.Tuple2; import com.jnape.palatable.lambda.functions.Fn1; -import com.jnape.palatable.lambda.functions.Fn2; import com.jnape.palatable.lambda.functions.builtin.fn2.Both; import com.jnape.palatable.lambda.functor.Applicative; import com.jnape.palatable.lambda.functor.Functor; -import com.jnape.palatable.lambda.functor.Profunctor; import com.jnape.palatable.lambda.monad.Monad; import java.util.function.BiFunction; @@ -138,29 +136,11 @@ * @param the type of the "smaller" update value */ @FunctionalInterface -public interface Lens extends Monad>, Profunctor> { - - , FB extends Functor> FT apply( - Function fn, S s); - - /** - * Fix this lens against some functor, producing a non-polymorphic runnable lens as an {@link Fn2}. - *

- * Although the Java type system does not allow enforceability, the functor instance FT should be the same as FB, - * only differentiating in their parameters. - * - * @param the common functor type of FT and FB - * @param the type of the lifted T - * @param the type of the lifted B - * @return the lens, "fixed" to the functor - */ - default , FB extends Functor> Fixed fix() { - return this::apply; - } +public interface Lens extends LensLike { @Override default Lens fmap(Function fn) { - return Monad.super.fmap(fn).coerce(); + return LensLike.super.fmap(fn).coerce(); } @Override @@ -169,85 +149,62 @@ default Lens pure(U u) { } @Override - default Lens zip(Applicative, Lens> appFn) { - return Monad.super.zip(appFn).coerce(); + default Lens zip(Applicative, LensLike> appFn) { + return LensLike.super.zip(appFn).coerce(); } @Override - default Lens discardL(Applicative> appB) { - return Monad.super.discardL(appB).coerce(); + default Lens discardL(Applicative> appB) { + return LensLike.super.discardL(appB).coerce(); } @Override - default Lens discardR(Applicative> appB) { - return Monad.super.discardR(appB).coerce(); + default Lens discardR(Applicative> appB) { + return LensLike.super.discardR(appB).coerce(); } @Override - default Lens flatMap(Function>> f) { + default Lens flatMap(Function>> f) { return lens(view(this), (s, b) -> set(f.apply(set(this, b, s)).>coerce(), b, s)); } @Override default Lens diMapL(Function fn) { - return (Lens) Profunctor.super.diMapL(fn); + return LensLike.super.diMapL(fn).coerce(); } @Override default Lens diMapR(Function fn) { - return (Lens) Profunctor.super.diMapR(fn); + return LensLike.super.diMapR(fn).coerce(); } @Override - default Lens diMap(Function lFn, Function rFn) { - return this.mapS(lFn).mapT(rFn); + default Lens diMap(Function lFn, + Function rFn) { + return LensLike.super.diMap(lFn, rFn).coerce(); } @Override default Lens contraMap(Function fn) { - return (Lens) Profunctor.super.contraMap(fn); + return LensLike.super.contraMap(fn).coerce(); } - /** - * Contravariantly map S to R, yielding a new lens. - * - * @param fn the mapping function - * @param the type of the new "larger" value for reading - * @return the new lens - */ + @Override default Lens mapS(Function fn) { return lens(view(this).compose(fn), (r, b) -> set(this, b, fn.apply(r))); } - /** - * Covariantly map T to U, yielding a new lens. - * - * @param fn the mapping function - * @param the type of the new "larger" value for putting - * @return the new lens - */ + @Override default Lens mapT(Function fn) { return fmap(fn); } - /** - * Covariantly map A to C, yielding a new lens. - * - * @param fn the mapping function - * @param the type of the new "smaller" value that is read - * @return the new lens - */ + @Override default Lens mapA(Function fn) { return andThen(lens(fn, (a, b) -> b)); } - /** - * Contravariantly map B to Z, yielding a new lens. - * - * @param fn the mapping function - * @param the type of the new "smaller" update value - * @return the new lens - */ + @Override default Lens mapB(Function fn) { return lens(view(this), (s, z) -> set(this, fn.apply(z), s)); } @@ -339,18 +296,26 @@ static Lens, Tuple2> both(Lens the type of both "smaller" values */ @FunctionalInterface - interface Simple extends Lens { - - @Override - default , FA extends Functor> Fixed fix() { - return this::apply; - } + interface Simple extends Lens, LensLike.Simple { - @SuppressWarnings("unchecked") + /** + * Compose two simple lenses from right to left. + * + * @param g the other simple lens + * @param the other simple lens' larger type + * @return the composed simple lens + */ default Lens.Simple compose(Lens.Simple g) { - return Lens.super.compose(g)::apply; + return Lens.Simple.adapt(Lens.super.compose(g)); } + /** + * Compose two simple lenses from left to right. + * + * @param f the other simple lens + * @param the other simple lens' smaller type + * @return the composed simple lens + */ default Lens.Simple andThen(Lens.Simple f) { return f.compose(this); } @@ -364,7 +329,7 @@ default Lens.Simple andThen(Lens.Simple f) { * @return the simple lens */ @SuppressWarnings("unchecked") - static Simple adapt(Lens lens) { + static Lens.Simple adapt(Lens lens) { return lens::apply; } @@ -379,37 +344,7 @@ static Simple adapt(Lens lens) { * @return the dual-focus simple lens */ static Lens.Simple> both(Lens f, Lens g) { - return adapt(Lens.both(f, g)); + return Lens.Simple.adapt(Lens.both(f, g)); } - - /** - * A convenience type with a simplified type signature for fixed simple lenses. - * - * @param the type of both "larger" values - * @param the type of both "smaller" values - * @param the type of the lifted s - * @param the type of the lifted A - */ - @FunctionalInterface - interface Fixed, FA extends Functor> - extends Lens.Fixed { - } - } - - /** - * A lens that has been fixed to a functor. Because the lens is no longer polymorphic, it can additionally be safely - * represented as an Fn2. - * - * @param the type of the "larger" value for reading - * @param the type of the "larger" value for putting - * @param the type of the "smaller" value that is read - * @param the type of the "smaller" update value - * @param the functor unification type between FT and FB - * @param the type of the lifted T - * @param the type of the lifted B - */ - @FunctionalInterface - interface Fixed, FB extends Functor> - extends Fn2, S, FT> { } } diff --git a/src/main/java/com/jnape/palatable/lambda/lens/LensLike.java b/src/main/java/com/jnape/palatable/lambda/lens/LensLike.java new file mode 100644 index 000000000..7b8b699f6 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/lens/LensLike.java @@ -0,0 +1,121 @@ +package com.jnape.palatable.lambda.lens; + +import com.jnape.palatable.lambda.functor.Applicative; +import com.jnape.palatable.lambda.functor.Functor; +import com.jnape.palatable.lambda.functor.Profunctor; +import com.jnape.palatable.lambda.monad.Monad; + +import java.util.function.Function; + +/** + * The generic supertype of all types that can be treated as lenses but should preserve type-specific return types in + * overrides. This type only exists to appease Java's unfortunate parametric type hierarchy constraints. If you're here, + * you're probably looking for {@link Lens} or {@link Iso}. + * + * @param the type of the "larger" value for reading + * @param the type of the "larger" value for putting + * @param the type of the "smaller" value that is read + * @param the type of the "smaller" update value + * @param the concrete lens subtype + * @see Lens + * @see Iso + */ +public interface LensLike extends Monad>, Profunctor> { + + , FB extends Functor> FT apply( + Function fn, S s); + + /** + * Contravariantly map S to R, yielding a new lens. + * + * @param fn the mapping function + * @param the type of the new "larger" value for reading + * @return the new lens + */ + LensLike mapS(Function fn); + + /** + * Covariantly map T to U, yielding a new lens. + * + * @param fn the mapping function + * @param the type of the new "larger" value for putting + * @return the new lens + */ + LensLike mapT(Function fn); + + /** + * Covariantly map A to C, yielding a new lens. + * + * @param fn the mapping function + * @param the type of the new "smaller" value that is read + * @return the new lens + */ + LensLike mapA(Function fn); + + /** + * Contravariantly map B to Z, yielding a new lens. + * + * @param fn the mapping function + * @param the type of the new "smaller" update value + * @return the new lens + */ + LensLike mapB(Function fn); + + @Override + LensLike flatMap(Function>> f); + + @Override + LensLike pure(U u); + + @Override + default LensLike fmap(Function fn) { + return Monad.super.fmap(fn).coerce(); + } + + @Override + default LensLike zip( + Applicative, LensLike> appFn) { + return Monad.super.zip(appFn).coerce(); + } + + @Override + default LensLike discardL(Applicative> appB) { + return Monad.super.discardL(appB).coerce(); + } + + @Override + default LensLike discardR(Applicative> appB) { + return Monad.super.discardR(appB).coerce(); + } + + @Override + default LensLike diMapL(Function fn) { + return (LensLike) Profunctor.super.diMapL(fn); + } + + @Override + default LensLike diMapR(Function fn) { + return (LensLike) Profunctor.super.diMapR(fn); + } + + @Override + default LensLike diMap(Function lFn, + Function rFn) { + return this.mapS(lFn).mapT(rFn); + } + + @Override + default LensLike contraMap(Function fn) { + return (LensLike) Profunctor.super.contraMap(fn); + } + + /** + * A simpler type signature for lenses where S/T and A/B are equivalent. + * + * @param the "larger" type + * @param the "smaller" type + * @param the concrete lens subtype + */ + interface Simple extends LensLike { + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/lens/functions/Over.java b/src/main/java/com/jnape/palatable/lambda/lens/functions/Over.java index 131aa5046..5c1de0104 100644 --- a/src/main/java/com/jnape/palatable/lambda/lens/functions/Over.java +++ b/src/main/java/com/jnape/palatable/lambda/lens/functions/Over.java @@ -4,7 +4,7 @@ import com.jnape.palatable.lambda.functions.Fn2; import com.jnape.palatable.lambda.functions.Fn3; import com.jnape.palatable.lambda.functor.builtin.Identity; -import com.jnape.palatable.lambda.lens.Lens; +import com.jnape.palatable.lambda.lens.LensLike; import java.util.function.Function; @@ -23,7 +23,7 @@ * @see Set * @see View */ -public final class Over implements Fn3, Function, S, T> { +public final class Over implements Fn3, Function, S, T> { private static final Over INSTANCE = new Over(); @@ -31,10 +31,8 @@ private Over() { } @Override - public T apply(Lens lens, Function fn, S s) { - return lens., Identity>fix() - .apply(fn.andThen((Function>) Identity::new), s) - .runIdentity(); + public T apply(LensLike lens, Function fn, S s) { + return lens., Identity>apply(fn.andThen((Function>) Identity::new), s).runIdentity(); } @SuppressWarnings("unchecked") @@ -42,17 +40,15 @@ public static Over over() { return (Over) INSTANCE; } - public static Fn2, S, T> over( - Lens lens) { + public static Fn2, S, T> over(LensLike lens) { return Over.over().apply(lens); } - public static Fn1 over(Lens lens, - Function fn) { + public static Fn1 over(LensLike lens, Function fn) { return over(lens).apply(fn); } - public static T over(Lens lens, Function fn, S s) { + public static T over(LensLike lens, Function fn, S s) { return over(lens, fn).apply(s); } } diff --git a/src/main/java/com/jnape/palatable/lambda/lens/functions/Set.java b/src/main/java/com/jnape/palatable/lambda/lens/functions/Set.java index b3c5ff243..07c97b804 100644 --- a/src/main/java/com/jnape/palatable/lambda/lens/functions/Set.java +++ b/src/main/java/com/jnape/palatable/lambda/lens/functions/Set.java @@ -4,7 +4,7 @@ import com.jnape.palatable.lambda.functions.Fn2; import com.jnape.palatable.lambda.functions.Fn3; import com.jnape.palatable.lambda.functor.builtin.Identity; -import com.jnape.palatable.lambda.lens.Lens; +import com.jnape.palatable.lambda.lens.LensLike; import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; import static com.jnape.palatable.lambda.lens.functions.Over.over; @@ -23,7 +23,7 @@ * @see Over * @see View */ -public final class Set implements Fn3, B, S, T> { +public final class Set implements Fn3, B, S, T> { private static final Set INSTANCE = new Set(); @@ -31,7 +31,7 @@ private Set() { } @Override - public T apply(Lens lens, B b, S s) { + public T apply(LensLike lens, B b, S s) { return over(lens, constantly(b), s); } @@ -40,15 +40,15 @@ public static Set set() { return INSTANCE; } - public static Fn2 set(Lens lens) { + public static Fn2 set(LensLike lens) { return Set.set().apply(lens); } - public static Fn1 set(Lens lens, B b) { + public static Fn1 set(LensLike lens, B b) { return set(lens).apply(b); } - public static T set(Lens lens, B b, S s) { + public static T set(LensLike lens, B b, S s) { return set(lens, b).apply(s); } } diff --git a/src/main/java/com/jnape/palatable/lambda/lens/functions/View.java b/src/main/java/com/jnape/palatable/lambda/lens/functions/View.java index 86e4990a0..a6d1cb883 100644 --- a/src/main/java/com/jnape/palatable/lambda/lens/functions/View.java +++ b/src/main/java/com/jnape/palatable/lambda/lens/functions/View.java @@ -3,7 +3,7 @@ import com.jnape.palatable.lambda.functions.Fn1; import com.jnape.palatable.lambda.functions.Fn2; import com.jnape.palatable.lambda.functor.builtin.Const; -import com.jnape.palatable.lambda.lens.Lens; +import com.jnape.palatable.lambda.lens.LensLike; /** * Given a lens and a "larger" value S, retrieve a "smaller" value A by lifting the lens into @@ -18,7 +18,7 @@ * @see Set * @see Over */ -public final class View implements Fn2, S, A> { +public final class View implements Fn2, S, A> { private static final View INSTANCE = new View(); @@ -26,8 +26,8 @@ private View() { } @Override - public A apply(Lens lens, S s) { - return lens., Const, Const>fix().apply(Const::new, s).runConst(); + public A apply(LensLike lens, S s) { + return lens., Const, Const>apply(Const::new, s).runConst(); } @SuppressWarnings("unchecked") @@ -35,11 +35,11 @@ public static View view() { return INSTANCE; } - public static Fn1 view(Lens lens) { + public static Fn1 view(LensLike lens) { return View.view().apply(lens); } - public static A view(Lens lens, S s) { + public static A view(LensLike lens, S s) { return view(lens).apply(s); } } diff --git a/src/main/java/com/jnape/palatable/lambda/lens/lenses/MapLens.java b/src/main/java/com/jnape/palatable/lambda/lens/lenses/MapLens.java index f45d535a4..cb393ac1f 100644 --- a/src/main/java/com/jnape/palatable/lambda/lens/lenses/MapLens.java +++ b/src/main/java/com/jnape/palatable/lambda/lens/lenses/MapLens.java @@ -3,6 +3,7 @@ import com.jnape.palatable.lambda.adt.Maybe; import com.jnape.palatable.lambda.adt.hlist.Tuple2; import com.jnape.palatable.lambda.functions.builtin.fn2.Filter; +import com.jnape.palatable.lambda.lens.Iso; import com.jnape.palatable.lambda.lens.Lens; import java.util.ArrayList; @@ -18,6 +19,7 @@ import static com.jnape.palatable.lambda.functions.builtin.fn2.Map.map; import static com.jnape.palatable.lambda.functions.builtin.fn2.ToCollection.toCollection; import static com.jnape.palatable.lambda.functions.builtin.fn2.ToMap.toMap; +import static com.jnape.palatable.lambda.lens.Lens.Simple.adapt; import static com.jnape.palatable.lambda.lens.Lens.simpleLens; import static com.jnape.palatable.lambda.lens.functions.View.view; import static com.jnape.palatable.lambda.lens.lenses.MaybeLens.unLiftA; @@ -77,7 +79,7 @@ public static Lens.Simple, Maybe> valueAt(K k) { */ @SuppressWarnings("unchecked") public static Lens.Simple, V> valueAt(K k, V defaultValue) { - return unLiftB(unLiftA(valueAt(k), defaultValue))::apply; + return adapt(unLiftB(unLiftA(valueAt(k), defaultValue))); } /** @@ -156,11 +158,12 @@ public static Lens.Simple, Map> inverted() { * @param the unfocused map value type * @param the focused map value types * @return a lens that focuses on a map while mapping its values + * @deprecated in favor of the lawful (and far more rational) {@link MapLens#mappingValues(Iso)} */ + @Deprecated public static Lens.Simple, Map> mappingValues(Function fn) { return simpleLens(m -> toMap(HashMap::new, map(t -> t.biMapR(fn), map(Tuple2::fromEntry, m.entrySet()))), (s, b) -> { - //todo: remove this madness upon arrival of either invertible functions or Iso Set retainKeys = Filter.>filter(kv -> eq(fn.apply(kv.getValue()), b.get(kv.getKey()))) .andThen(map(Map.Entry::getKey)) .andThen(toCollection(HashSet::new)) @@ -170,4 +173,18 @@ public static Lens.Simple, Map> mappingValues(Functi return copy; }); } + + /** + * A lens that focuses on a map while mapping its values with the mapping {@link Iso}. + * + * @param iso the mapping {@link Iso} + * @param the key type + * @param the unfocused map value type + * @param the focused map value types + * @return a lens that focuses on a map while mapping its values + */ + public static Lens.Simple, Map> mappingValues(Iso iso) { + return simpleLens(m -> toMap(HashMap::new, map(t -> t.biMapR(view(iso)), map(Tuple2::fromEntry, m.entrySet()))), + (s, b) -> view(mappingValues(iso.mirror()), b)); + } } diff --git a/src/test/java/com/jnape/palatable/lambda/lens/IsoTest.java b/src/test/java/com/jnape/palatable/lambda/lens/IsoTest.java new file mode 100644 index 000000000..016de15ee --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/lens/IsoTest.java @@ -0,0 +1,57 @@ +package com.jnape.palatable.lambda.lens; + +import com.jnape.palatable.lambda.adt.Maybe; +import com.jnape.palatable.traitor.annotations.TestTraits; +import com.jnape.palatable.traitor.runners.Traits; +import org.junit.Test; +import org.junit.runner.RunWith; +import testsupport.EqualityAwareIso; +import testsupport.traits.ApplicativeLaws; +import testsupport.traits.FunctorLaws; +import testsupport.traits.MonadLaws; + +import java.util.List; + +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.lens.Iso.iso; +import static com.jnape.palatable.lambda.lens.functions.Set.set; +import static com.jnape.palatable.lambda.lens.functions.View.view; +import static java.util.Arrays.asList; +import static java.util.stream.Collectors.toList; +import static org.junit.Assert.assertEquals; + +@RunWith(Traits.class) +public class IsoTest { + + private static final Iso, Integer, Double> ISO = + iso(Integer::parseInt, dbl -> dbl.toString().chars().mapToObj(x -> (char) x).collect(toList())); + + @TestTraits({FunctorLaws.class, ApplicativeLaws.class, MonadLaws.class}) + public Iso, Integer, Double> testSubject() { + return new EqualityAwareIso<>("123", 1.23d, ISO); + } + + @Test + public void lensLike() { + assertEquals((Integer) 123, view(ISO, "123")); + assertEquals(asList('1', '.', '2', '3'), set(ISO, 1.23d, "234")); + } + + @Test + public void mirrorFlipsIso() { + assertEquals(asList('1', '.', '2', '3'), view(ISO.mirror(), 1.23d)); + assertEquals((Integer) 240, set(ISO.mirror(), "240", 5.67d)); + } + + @Test + public void mapsIndividuallyOverParameters() { + Iso, Maybe>, Maybe, Maybe> mapped = ISO + .mapS((Maybe maybeS) -> maybeS.orElse("")) + .mapT(Maybe::maybe) + .mapA(Maybe::maybe) + .mapB((Maybe maybeD) -> maybeD.orElse(-1d)); + + assertEquals(just(1), view(mapped, just("1"))); + assertEquals(just(asList('1', '.', '2')), view(mapped.mirror(), just(1.2d))); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/lens/LensTest.java b/src/test/java/com/jnape/palatable/lambda/lens/LensTest.java index 0b7b4c5ae..14939edac 100644 --- a/src/test/java/com/jnape/palatable/lambda/lens/LensTest.java +++ b/src/test/java/com/jnape/palatable/lambda/lens/LensTest.java @@ -2,7 +2,6 @@ import com.jnape.palatable.lambda.adt.Maybe; import com.jnape.palatable.lambda.adt.hlist.Tuple2; -import com.jnape.palatable.lambda.functions.Fn1; import com.jnape.palatable.lambda.functor.builtin.Const; import com.jnape.palatable.lambda.functor.builtin.Identity; import com.jnape.palatable.traitor.annotations.TestTraits; @@ -56,17 +55,6 @@ public void viewsUnderConst() { assertEquals((Integer) 3, i); } - @Test - public void fix() { - Fn1> fn = s -> new Const<>(s.length()); - List s = singletonList("foo"); - - Integer fixedLensResult = LENS., Const>, Const>fix().apply(fn, s).runConst(); - Integer unfixedLensResult = LENS., Const>, Const>apply(fn, s).runConst(); - - assertEquals(unfixedLensResult, fixedLensResult); - } - @Test public void mapsIndividuallyOverParameters() { Lens lens = lens(s -> s.charAt(0), (s, b) -> s.length() == b); @@ -76,8 +64,11 @@ public void mapsIndividuallyOverParameters() { .mapA(Maybe::maybe) .mapB((Maybe maybeI) -> maybeI.orElse(-1)); - Lens.Fixed, Maybe, Maybe, Maybe, Identity, Identity>, Identity>> fixed = theGambit.fix(); - assertEquals(just(true), fixed.apply(maybeC -> new Identity<>(maybeC.fmap(c -> parseInt(Character.toString(c)))), just("321")).runIdentity()); + assertEquals(just(true), + theGambit.>, Identity>>apply( + maybeC -> new Identity<>(maybeC.fmap(c -> parseInt(Character.toString(c)))), + just("321")).runIdentity() + ); } @Test diff --git a/src/test/java/com/jnape/palatable/lambda/lens/lenses/MapLensTest.java b/src/test/java/com/jnape/palatable/lambda/lens/lenses/MapLensTest.java index aceb8c626..dc18edd90 100644 --- a/src/test/java/com/jnape/palatable/lambda/lens/lenses/MapLensTest.java +++ b/src/test/java/com/jnape/palatable/lambda/lens/lenses/MapLensTest.java @@ -10,6 +10,7 @@ import static com.jnape.palatable.lambda.adt.Maybe.just; import static com.jnape.palatable.lambda.adt.Maybe.nothing; +import static com.jnape.palatable.lambda.lens.Iso.iso; import static com.jnape.palatable.lambda.lens.functions.Set.set; import static com.jnape.palatable.lambda.lens.functions.View.view; import static com.jnape.palatable.lambda.lens.lenses.MapLens.keys; @@ -136,25 +137,21 @@ public void invertedFocusesOnMapWithKeysAndValuesSwitched() { } @Test - public void mappingValuesRetainsMapStructureWithMappedValues() { - Map m = unmodifiableMap(new HashMap() {{ - put("foo", "1"); - put("bar", "2"); - put("baz", "3"); - }}); - Lens.Simple, Map> mappingValues = mappingValues(Integer::parseInt); - - assertEquals(new HashMap() {{ - put("foo", 1); - put("bar", 2); - put("baz", 3); - }}, view(mappingValues, m)); - - Map updated = set(mappingValues, unmodifiableMap(new HashMap() {{ - put("foo", 2); - put("bar", 1); - put("baz", 3); - }}), m); - assertEquals(singletonMap("baz", "3"), updated); + public void mappingValuesWithIsoRetainsMapStructureWithMappedValues() { + assertLensLawfulness(mappingValues(iso(Integer::parseInt, Object::toString)), + asList(emptyMap(), + singletonMap("foo", "1"), + unmodifiableMap(new HashMap() {{ + put("foo", "1"); + put("bar", "2"); + put("baz", "3"); + }})), + asList(emptyMap(), + singletonMap("foo", 1), + unmodifiableMap(new HashMap() {{ + put("foo", 1); + put("bar", 2); + put("baz", 3); + }}))); } } \ No newline at end of file diff --git a/src/test/java/testsupport/EqualityAwareIso.java b/src/test/java/testsupport/EqualityAwareIso.java new file mode 100644 index 000000000..0efc7f039 --- /dev/null +++ b/src/test/java/testsupport/EqualityAwareIso.java @@ -0,0 +1,72 @@ +package testsupport; + +import com.jnape.palatable.lambda.functor.Applicative; +import com.jnape.palatable.lambda.functor.Functor; +import com.jnape.palatable.lambda.functor.Profunctor; +import com.jnape.palatable.lambda.lens.Iso; +import com.jnape.palatable.lambda.lens.LensLike; +import com.jnape.palatable.lambda.monad.Monad; + +import java.util.Objects; +import java.util.function.Function; + +import static com.jnape.palatable.lambda.functions.builtin.fn2.Both.both; +import static com.jnape.palatable.lambda.lens.functions.View.view; + +public final class EqualityAwareIso implements Iso { + private final S s; + private final B b; + private final Iso iso; + + public EqualityAwareIso(S s, B b, Iso iso) { + this.s = s; + this.b = b; + this.iso = iso; + } + + @Override + public

, FT extends Functor, PAFB extends Profunctor, PSFT extends Profunctor> PSFT apply( + PAFB pafb) { + return iso.apply(pafb); + } + + @Override + public , FB extends Functor> FT apply( + Function fn, S s) { + return iso.apply(fn, s); + } + + @Override + public EqualityAwareIso fmap(Function fn) { + return new EqualityAwareIso<>(s, b, iso.fmap(fn)); + } + + @Override + public EqualityAwareIso pure(U u) { + return new EqualityAwareIso<>(s, b, iso.pure(u)); + } + + @Override + public EqualityAwareIso zip( + Applicative, LensLike> appFn) { + return new EqualityAwareIso<>(s, b, iso.zip(appFn)); + } + + @Override + public EqualityAwareIso flatMap( + Function>> fn) { + return new EqualityAwareIso<>(s, b, iso.flatMap(fn)); + } + + @Override + @SuppressWarnings("unchecked") + public boolean equals(Object other) { + if (other instanceof EqualityAwareIso) { + Iso that = (EqualityAwareIso) other; + Boolean sameForward = both(view(this), view(that)).apply(s).into(Objects::equals); + Boolean sameReverse = both(view(this.mirror()), view(that.mirror())).apply(b).into(Objects::equals); + return sameForward && sameReverse; + } + return false; + } +} diff --git a/src/test/java/testsupport/EqualityAwareLens.java b/src/test/java/testsupport/EqualityAwareLens.java index f70f96c37..e8a8cb52f 100644 --- a/src/test/java/testsupport/EqualityAwareLens.java +++ b/src/test/java/testsupport/EqualityAwareLens.java @@ -4,6 +4,7 @@ import com.jnape.palatable.lambda.functor.Functor; import com.jnape.palatable.lambda.functor.builtin.Const; import com.jnape.palatable.lambda.lens.Lens; +import com.jnape.palatable.lambda.lens.LensLike; import com.jnape.palatable.lambda.monad.Monad; import java.util.Objects; @@ -25,7 +26,8 @@ public , FB extends Functor> F } @Override - public EqualityAwareLens flatMap(Function>> f) { + public EqualityAwareLens flatMap( + Function>> f) { return new EqualityAwareLens<>(s, lens.flatMap(f)); } @@ -41,7 +43,7 @@ public EqualityAwareLens pure(U u) { @Override public EqualityAwareLens zip( - Applicative, Lens> appFn) { + Applicative, LensLike> appFn) { return new EqualityAwareLens<>(s, lens.zip(appFn)); } diff --git a/src/test/java/testsupport/assertion/LensAssert.java b/src/test/java/testsupport/assertion/LensAssert.java index 3f2e80ae8..e89678afb 100644 --- a/src/test/java/testsupport/assertion/LensAssert.java +++ b/src/test/java/testsupport/assertion/LensAssert.java @@ -4,7 +4,7 @@ import com.jnape.palatable.lambda.adt.hlist.Tuple2; import com.jnape.palatable.lambda.functions.Fn2; import com.jnape.palatable.lambda.functions.builtin.fn2.Map; -import com.jnape.palatable.lambda.lens.Lens; +import com.jnape.palatable.lambda.lens.LensLike; import com.jnape.palatable.lambda.monoid.builtin.Present; import java.util.Objects; @@ -23,7 +23,7 @@ public final class LensAssert { - public static void assertLensLawfulness(Lens lens, Iterable ss, Iterable bs) { + public static void assertLensLawfulness(LensLike lens, Iterable ss, Iterable bs) { Iterable> cases = cartesianProduct(ss, bs); Present.present((x, y) -> join("\n\n", x, y)) .reduceLeft(asList(falsify("You get back what you put in", (s, b) -> view(lens, set(lens, b, s)), (s, b) -> b, cases), @@ -37,7 +37,7 @@ private static Maybe falsify(String label, Fn2 l, Fn2 return Map., Maybe>map(into((s, b) -> { X x = l.apply(s, b); X y = r.apply(s, b); - return Objects.equals(x, y) ? nothing() : just(format("S <%s>, B <%s> (%s != %s)", s, b, x, y)); + return Objects.equals(x, y) ? nothing() : just(format("S <%s>, B <%s> (%s != %s)", s, b, x, y)); })) .fmap(catMaybes()) .fmap(reduceLeft((x, y) -> x + "\n\t - " + y)) From e5da83a9d04dc67ee65687f17a4471eee71b730f Mon Sep 17 00:00:00 2001 From: jnape Date: Sat, 14 Apr 2018 17:43:50 -0500 Subject: [PATCH 41/52] Monad#zip default implementation sequences in reverse (better) order --- CHANGELOG.md | 1 + .../java/com/jnape/palatable/lambda/functor/Applicative.java | 2 +- src/main/java/com/jnape/palatable/lambda/monad/Monad.java | 4 +--- .../java/com/jnape/palatable/lambda/adt/hlist/Tuple2Test.java | 2 +- .../java/com/jnape/palatable/lambda/adt/hlist/Tuple5Test.java | 2 +- .../java/com/jnape/palatable/lambda/adt/hlist/Tuple6Test.java | 2 +- .../java/com/jnape/palatable/lambda/adt/hlist/Tuple7Test.java | 2 +- .../java/com/jnape/palatable/lambda/adt/hlist/Tuple8Test.java | 2 +- .../palatable/lambda/functions/builtin/fn3/LiftA2Test.java | 2 +- 9 files changed, 9 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ce9f78679..2d3c49778 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/). ### Changed - ***Breaking Change***: `Sequence` now has two more type parameters to aid in inference - ***Breaking Change***: `Traversable#traverse` now has three more type parameters to aid in inference +- ***Breaking Change***: `Monad#zip` now forces `m a -> b` before `m a` in default `Applicative#zip` implementation; this is only breaking for types that are sensitive to computation order (the resulting values are the same) - `Bifunctor` is now a `BoundedBifunctor` where both parameter upper bounds are `Object` - `Peek2` now accepts the more general `BoundedBifunctor` - `Identity`, `Compose`, and `Const` functors all have better `toString` implementations diff --git a/src/main/java/com/jnape/palatable/lambda/functor/Applicative.java b/src/main/java/com/jnape/palatable/lambda/functor/Applicative.java index f345cdc2d..224963a83 100644 --- a/src/main/java/com/jnape/palatable/lambda/functor/Applicative.java +++ b/src/main/java/com/jnape/palatable/lambda/functor/Applicative.java @@ -61,7 +61,7 @@ default Applicative fmap(Function fn) { * @return appB */ default Applicative discardL(Applicative appB) { - return appB.zip(zip(pure(constantly(id())))); + return appB.zip(fmap(constantly(id()))); } /** diff --git a/src/main/java/com/jnape/palatable/lambda/monad/Monad.java b/src/main/java/com/jnape/palatable/lambda/monad/Monad.java index 180211979..11b18d84c 100644 --- a/src/main/java/com/jnape/palatable/lambda/monad/Monad.java +++ b/src/main/java/com/jnape/palatable/lambda/monad/Monad.java @@ -4,8 +4,6 @@ import java.util.function.Function; -import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; - /** * Monads are {@link Applicative} functors that support a flattening operation to unwrap M<M<A>> * -> M<A>. This flattening operation, coupled with {@link Applicative#zip(Applicative)}, gives rise to @@ -55,7 +53,7 @@ default Monad fmap(Function fn) { */ @Override default Monad zip(Applicative, M> appFn) { - return fmap(a -> appFn., M>>coerce().fmap(f -> f.apply(a))).flatMap(id()); + return appFn., M>>coerce().flatMap(ab -> fmap(ab::apply)); } /** 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 9638d745e..36f9a44cc 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 @@ -105,7 +105,7 @@ public void staticFactoryMethodFromMapEntry() { public void zipPrecedence() { Tuple2 a = tuple("foo", 1); Tuple2> b = tuple("bar", x -> x + 1); - assertEquals(tuple("foo", 2), a.zip(b)); + assertEquals(tuple("bar", 2), a.zip(b)); } @Test 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 64179b33a..248dd03e1 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 @@ -92,7 +92,7 @@ public void fill() { public void zipPrecedence() { Tuple5 a = tuple("foo", 1, 2, 3, 4); Tuple5> b = tuple("bar", 2, 3, 4, x -> x + 1); - assertEquals(tuple("foo", 1, 2, 3, 5), a.zip(b)); + assertEquals(tuple("bar", 2, 3, 4, 5), a.zip(b)); } @Test 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 ee2020c4d..bd31aca1a 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 @@ -96,7 +96,7 @@ public void fill() { public void zipPrecedence() { Tuple6 a = tuple("foo", 1, 2, 3, 4, 5); Tuple6> b = tuple("bar", 2, 3, 4, 5, x -> x + 1); - assertEquals(tuple("foo", 1, 2, 3, 4, 6), a.zip(b)); + assertEquals(tuple("bar", 2, 3, 4, 5, 6), a.zip(b)); } @Test 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 4025e470b..a62a36691 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 @@ -99,7 +99,7 @@ public void into() { public void zipPrecedence() { Tuple7 a = tuple("foo", 1, 2, 3, 4, 5, 6); Tuple7> b = tuple("bar", 2, 3, 4, 5, 6, x -> x + 1); - assertEquals(tuple("foo", 1, 2, 3, 4, 5, 7), a.zip(b)); + assertEquals(tuple("bar", 2, 3, 4, 5, 6, 7), a.zip(b)); } @Test 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 a127cbc08..c19ee8a58 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 @@ -102,7 +102,7 @@ public void into() { public void zipPrecedence() { Tuple8 a = tuple("foo", 1, 2, 3, 4, 5, 6, 7); Tuple8> b = tuple("bar", 2, 3, 4, 5, 6, 7, x -> x + 1); - assertEquals(tuple("foo", 1, 2, 3, 4, 5, 6, 8), a.zip(b)); + assertEquals(tuple("bar", 2, 3, 4, 5, 6, 7, 8), a.zip(b)); } @Test diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/LiftA2Test.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/LiftA2Test.java index 159878c0c..93556ebe9 100644 --- a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/LiftA2Test.java +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/LiftA2Test.java @@ -16,7 +16,7 @@ public class LiftA2Test { public void liftsAndAppliesDyadicFunctionToTwoApplicatives() { BiFunction add = (x, y) -> x + y; assertEquals(right(3), liftA2(add, right(1), right(2)).coerce()); - assertEquals(tuple(2, 5), liftA2(add, tuple(1, 2), tuple(2, 3)).coerce()); + assertEquals(tuple(1, 5), liftA2(add, tuple(1, 2), tuple(2, 3)).coerce()); assertEquals(new Identity<>(3), liftA2(add, new Identity<>(1), new Identity<>(2))); } } \ No newline at end of file From 92db2a3ee411efb9db0dede85f01cdb23b4a47f8 Mon Sep 17 00:00:00 2001 From: jnape Date: Sun, 15 Apr 2018 20:46:28 -0500 Subject: [PATCH 42/52] Iso.Simple overrides discardR --- .../java/com/jnape/palatable/lambda/lens/Iso.java | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/jnape/palatable/lambda/lens/Iso.java b/src/main/java/com/jnape/palatable/lambda/lens/Iso.java index fdc6e1d08..97cbacd48 100644 --- a/src/main/java/com/jnape/palatable/lambda/lens/Iso.java +++ b/src/main/java/com/jnape/palatable/lambda/lens/Iso.java @@ -247,7 +247,7 @@ interface Simple extends Iso, LensLike.Simple { @Override default Iso.Simple mirror() { - return Iso.Simple.adapt(Iso.super.mirror()); + return adapt(Iso.super.mirror()); } @Override @@ -255,6 +255,11 @@ default Lens.Simple toLens() { return Lens.Simple.adapt(Iso.super.toLens()); } + @Override + default Iso.Simple discardR(Applicative> appB) { + return adapt(Iso.super.discardR(appB)); + } + /** * Compose two simple isos from right to left. * @@ -263,7 +268,7 @@ default Lens.Simple toLens() { * @return the composed simple iso */ default Iso.Simple compose(Iso.Simple g) { - return g.andThen(this); + return Iso.Simple.adapt(Iso.super.compose(g)); } /** @@ -274,7 +279,7 @@ default Iso.Simple compose(Iso.Simple g) { * @return the composed simple iso */ default Iso.Simple andThen(Iso.Simple f) { - return Iso.Simple.adapt(f.compose(this)); + return adapt(f.compose(this)); } /** From e080f28ab2acf4ff832caa51f69fb53e61b8358b Mon Sep 17 00:00:00 2001 From: jnape Date: Sun, 22 Apr 2018 14:15:04 -0500 Subject: [PATCH 43/52] Adding Under, the inverse of Over for Isos --- CHANGELOG.md | 1 + .../lambda/lens/functions/Under.java | 52 +++++++++++++++++++ .../lambda/lens/functions/UnderTest.java | 22 ++++++++ 3 files changed, 75 insertions(+) create mode 100644 src/main/java/com/jnape/palatable/lambda/lens/functions/Under.java create mode 100644 src/test/java/com/jnape/palatable/lambda/lens/functions/UnderTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 2d3c49778..d52af3aa6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,6 +36,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/). - `Exchange`, a `Profunctor` that can extract the morphisms from an `Iso` - `HMapLens`, lenses focusing on `HMap` - `MapLens#mappingValues(Iso)`, a lawful lens that maps the values of a `j.u.Map` +- `Under`, the inverse of `Over` for `Iso` ### Deprecated - `MapLens#mappingValues(Function)` is now deprecated in favor of the overload that takes an Iso diff --git a/src/main/java/com/jnape/palatable/lambda/lens/functions/Under.java b/src/main/java/com/jnape/palatable/lambda/lens/functions/Under.java new file mode 100644 index 000000000..11c37a1e5 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/lens/functions/Under.java @@ -0,0 +1,52 @@ +package com.jnape.palatable.lambda.lens.functions; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.Fn2; +import com.jnape.palatable.lambda.functions.Fn3; +import com.jnape.palatable.lambda.lens.Iso; +import com.jnape.palatable.lambda.lens.LensLike; + +import java.util.function.Function; + +/** + * The inverse of {@link Over}: given an {@link Iso}, a function from T to S, and a "smaller" + * value B, return a "smaller" value A by traversing around the type ring (B -> T + * -> S -> A). + *

+ * Note this is only possible for {@link Iso}s and not general {@link LensLike}s because of the mandatory need for the + * correspondence B -> T. + * + * @param the larger type for focusing + * @param the larger type for mirrored focusing + * @param the smaller type for focusing + * @param the smaller type for mirrored focusing + */ +public final class Under implements Fn3, Function, B, A> { + + private static final Under INSTANCE = new Under(); + + private Under() { + } + + @Override + public A apply(Iso iso, Function fn, B b) { + return iso.unIso().into((sa, bt) -> bt.fmap(fn).fmap(sa)).apply(b); + } + + @SuppressWarnings("unchecked") + public static Under under() { + return INSTANCE; + } + + public static Fn2, B, A> under(Iso iso) { + return Under.under().apply(iso); + } + + public static Fn1 under(Iso iso, Function fn) { + return under(iso).apply(fn); + } + + public static A under(Iso iso, Function fn, B b) { + return under(iso, fn).apply(b); + } +} diff --git a/src/test/java/com/jnape/palatable/lambda/lens/functions/UnderTest.java b/src/test/java/com/jnape/palatable/lambda/lens/functions/UnderTest.java new file mode 100644 index 000000000..58d44f19f --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/lens/functions/UnderTest.java @@ -0,0 +1,22 @@ +package com.jnape.palatable.lambda.lens.functions; + +import com.jnape.palatable.lambda.lens.Iso; +import org.junit.Test; + +import java.util.Collections; +import java.util.List; +import java.util.Set; + +import static com.jnape.palatable.lambda.lens.Iso.iso; +import static com.jnape.palatable.lambda.lens.functions.Under.under; +import static java.util.Collections.singletonList; +import static org.junit.Assert.assertEquals; + +public class UnderTest { + + @Test + public void reversesTypeRingWalk() { + Iso, Set, String, Integer> iso = iso(xs -> xs.get(0), Collections::singleton); + assertEquals("1", under(iso, set -> singletonList(set.iterator().next().toString()), 1)); + } +} \ No newline at end of file From 6ffb777ffecdde8864385eda9232eb7100e02f1a Mon Sep 17 00:00:00 2001 From: jnape Date: Sun, 22 Apr 2018 15:08:26 -0500 Subject: [PATCH 44/52] Iso#unIso returns Fn1s --- src/main/java/com/jnape/palatable/lambda/lens/Iso.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/jnape/palatable/lambda/lens/Iso.java b/src/main/java/com/jnape/palatable/lambda/lens/Iso.java index 97cbacd48..56caf718e 100644 --- a/src/main/java/com/jnape/palatable/lambda/lens/Iso.java +++ b/src/main/java/com/jnape/palatable/lambda/lens/Iso.java @@ -72,7 +72,7 @@ default Lens toLens() { @Override public , FB extends Functor> FT apply( Function fn, S s) { - return Iso.this.apply(fn1(fn), s); + return Iso.this.apply(fn, s); } }; } @@ -92,11 +92,11 @@ default Iso mirror() { * * @return the destructured iso */ - default Tuple2, ? extends Function> unIso() { + default Tuple2, Fn1> unIso() { return Tuple2.fill(this., Identity, Identity, Identity, Exchange>, Exchange>>apply(new Exchange<>(id(), Identity::new)).diMapR(Identity::runIdentity)) - .biMap(Exchange::sa, Exchange::bt); + .biMap(e -> fn1(e.sa()), e -> fn1(e.bt())); } @Override From 22b8789b52ff1256b6e8e9126bcf869613ad8744 Mon Sep 17 00:00:00 2001 From: jnape Date: Sun, 22 Apr 2018 15:26:18 -0500 Subject: [PATCH 45/52] TypeSafeKey is now dually parametric, an ISO, and can map values --- CHANGELOG.md | 5 + .../jnape/palatable/lambda/adt/hmap/HMap.java | 46 +++++--- .../lambda/adt/hmap/TypeSafeKey.java | 103 ++++++++++++++++-- .../lambda/lens/lenses/HMapLens.java | 4 +- .../palatable/lambda/adt/hmap/HMapTest.java | 60 +++++----- .../palatable/lambda/adt/hmap/SimpleTest.java | 14 +++ .../lambda/adt/hmap/TypeSafeKeyTest.java | 40 ++++++- .../lambda/lens/lenses/HMapLensTest.java | 2 +- .../lambda/monoid/builtin/PutAllTest.java | 4 +- 9 files changed, 210 insertions(+), 68 deletions(-) create mode 100644 src/test/java/com/jnape/palatable/lambda/adt/hmap/SimpleTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md index d52af3aa6..d8b9a0700 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/). - ***Breaking Change***: `Sequence` now has two more type parameters to aid in inference - ***Breaking Change***: `Traversable#traverse` now has three more type parameters to aid in inference - ***Breaking Change***: `Monad#zip` now forces `m a -> b` before `m a` in default `Applicative#zip` implementation; this is only breaking for types that are sensitive to computation order (the resulting values are the same) +- ***Breaking Change***: `TypeSafeKey` is now dually parametric (single parameter analog is preserved in `TypeSafeKey.Simple`) - `Bifunctor` is now a `BoundedBifunctor` where both parameter upper bounds are `Object` - `Peek2` now accepts the more general `BoundedBifunctor` - `Identity`, `Compose`, and `Const` functors all have better `toString` implementations @@ -17,6 +18,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/). - `Flatten` calls `Iterator#hasNext` less aggressively, allowing for better laziness - `Lens` subtypes `LensLike` - `View`/`Set`/`Over` now only require `LensLike` +- `HMap#keys` now returns a `Set` +- `HMap#values` now returns a `Collection` ### Added - `BoundedBifunctor`, a `Bifunctor` super type that offers upper bounds for both parameters @@ -37,6 +40,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/). - `HMapLens`, lenses focusing on `HMap` - `MapLens#mappingValues(Iso)`, a lawful lens that maps the values of a `j.u.Map` - `Under`, the inverse of `Over` for `Iso` +- `TypeSafeKey` is an `Iso` and supports mapping +- `TypeSafeKey.Simple`, the single parameter version of `TypeSafeKey` ### Deprecated - `MapLens#mappingValues(Function)` is now deprecated in favor of the overload that takes an Iso diff --git a/src/main/java/com/jnape/palatable/lambda/adt/hmap/HMap.java b/src/main/java/com/jnape/palatable/lambda/adt/hmap/HMap.java index 48d60db79..264868485 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/hmap/HMap.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/hmap/HMap.java @@ -3,14 +3,21 @@ import com.jnape.palatable.lambda.adt.Maybe; import com.jnape.palatable.lambda.adt.hlist.Tuple2; +import java.util.ArrayList; +import java.util.Collection; import java.util.HashMap; +import java.util.HashSet; import java.util.Iterator; +import java.util.List; import java.util.Map; import java.util.NoSuchElementException; import java.util.Objects; +import java.util.Set; import java.util.function.Consumer; +import static com.jnape.palatable.lambda.adt.Maybe.maybe; import static com.jnape.palatable.lambda.functions.builtin.fn2.Map.map; +import static com.jnape.palatable.lambda.lens.functions.View.view; import static java.util.Collections.emptyMap; import static java.util.Collections.singletonMap; @@ -35,12 +42,12 @@ private HMap(Map table) { * Retrieve the value at this key. * * @param key the key - * @param the value type + * @param the value type * @return Maybe the value at this key */ @SuppressWarnings("unchecked") - public Maybe get(TypeSafeKey key) { - return Maybe.maybe((T) table.get(key)); + public Maybe get(TypeSafeKey key) { + return maybe((A) table.get(key)).fmap(view(key)); } /** @@ -51,7 +58,7 @@ public Maybe get(TypeSafeKey key) { * @return the value at this key * @throws NoSuchElementException if the key is unmapped */ - public V demand(TypeSafeKey key) throws NoSuchElementException { + public V demand(TypeSafeKey key) throws NoSuchElementException { return get(key).orElseThrow(() -> new NoSuchElementException("Demanded value for key " + key + ", but couldn't find one.")); } @@ -63,8 +70,8 @@ public V demand(TypeSafeKey key) throws NoSuchElementException { * @param the value type * @return the updated HMap */ - public HMap put(TypeSafeKey key, V value) { - return alter(t -> t.put(key, value)); + public HMap put(TypeSafeKey key, V value) { + return alter(t -> t.put(key, view(key.mirror(), value))); } /** @@ -109,20 +116,23 @@ public HMap removeAll(HMap hMap) { /** * Retrieve all the mapped keys. + *

+ * Note that unlike with {@link Map#keySet()}, the resulting key set is not "live"; in fact + * that is, alterations to the resulting key set have no effect on the backing {@link HMap}. * - * @return an Iterable of all the mapped keys + * @return a {@link Set} of all the mapped keys */ - public Iterable keys() { - return map(Tuple2::_1, this); + public Set keys() { + return new HashSet<>(table.keySet()); } /** * Retrieve all the mapped values. * - * @return an Iterable of all the mapped values + * @return a {@link List} of all the mapped values */ - public Iterable values() { - return map(Tuple2::_2, this); + public Collection values() { + return new ArrayList<>(table.values()); } /** @@ -184,7 +194,7 @@ public static HMap emptyHMap() { * @param the only mapped value type * @return a singleton HMap */ - public static HMap singletonHMap(TypeSafeKey key, V value) { + public static HMap singletonHMap(TypeSafeKey key, V value) { return new HMap(singletonMap(key, value)); } @@ -199,8 +209,8 @@ public static HMap singletonHMap(TypeSafeKey key, V value) { * @param value2's type * @return an HMap with the given associations */ - public static HMap hMap(TypeSafeKey key1, V1 value1, - TypeSafeKey key2, V2 value2) { + public static HMap hMap(TypeSafeKey key1, V1 value1, + TypeSafeKey key2, V2 value2) { return singletonHMap(key1, value1).put(key2, value2); } @@ -218,9 +228,9 @@ public static HMap hMap(TypeSafeKey key1, V1 value1, * @param value3's type * @return an HMap with the given associations */ - public static HMap hMap(TypeSafeKey key1, V1 value1, - TypeSafeKey key2, V2 value2, - TypeSafeKey key3, V3 value3) { + public static HMap hMap(TypeSafeKey key1, V1 value1, + TypeSafeKey key2, V2 value2, + TypeSafeKey key3, V3 value3) { return hMap(key1, value1, key2, value2).put(key3, value3); } } diff --git a/src/main/java/com/jnape/palatable/lambda/adt/hmap/TypeSafeKey.java b/src/main/java/com/jnape/palatable/lambda/adt/hmap/TypeSafeKey.java index 576b87d5c..aaa23b594 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/hmap/TypeSafeKey.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/hmap/TypeSafeKey.java @@ -1,25 +1,104 @@ package com.jnape.palatable.lambda.adt.hmap; +import com.jnape.palatable.lambda.functor.Applicative; +import com.jnape.palatable.lambda.functor.Functor; +import com.jnape.palatable.lambda.functor.Profunctor; +import com.jnape.palatable.lambda.lens.Iso; +import com.jnape.palatable.lambda.lens.LensLike; + /** - * An interface representing a parametrized key for use in HMaps. The parameter specifies the type of the value stored - * at this binding inside the HMap. + * An interface representing a parametrized key for use in {@link HMap}s. The parameter specifies the type of the value + * stored at this binding inside the {@link HMap}. *

- * This is intentionally an interface so user-defined implementations are possible; however, it's important to note that - * all hopes of type-safety hinge on equality being implemented such that no two TypeSafeKeys with differing parameters - * may be considered equal. Reference equality is used here as the default, as that is sufficient. + * This is intentionally an interface so user-defined implementations are possible; however, it's important to note + * that all hopes of type-safety hinge on equality being implemented such that no two {@link TypeSafeKey}s with + * differing value-type parameters may be considered equal. Reference equality is used here as the default, as that is + * sufficient. * - * @param The type of the value that this key maps to inside an HMap + * @param The type of the value that this key maps to inside an {@link HMap} */ -public interface TypeSafeKey { +public interface TypeSafeKey extends Iso.Simple { + + @Override + default TypeSafeKey discardR(Applicative> appB) { + Iso.Simple discarded = Iso.Simple.super.discardR(appB); + return new TypeSafeKey() { + @Override + public

, FT extends Functor, PAFB extends Profunctor, PSFT extends Profunctor> PSFT apply( + PAFB pafb) { + return discarded.apply(pafb); + } + + @Override + public int hashCode() { + return TypeSafeKey.this.hashCode(); + } + + @Override + public boolean equals(Object obj) { + return TypeSafeKey.this.equals(obj); + } + }; + } /** - * Static factory method for creating a unique type-safe key + * Left-to-right composition of this {@link TypeSafeKey} with some other {@link Iso}. Because the first parameter + * fundamentally represents an already stored value type, this is the only composition that is possible for + * {@link TypeSafeKey}, which is why only this (and not {@link Iso#compose(Iso)}) is overridden. + *

+ * Particularly of note is the fact that values stored at this key are still stored as their original manifest + * type, and are not duplicated - which is to say, putting a value at a key, yielding a new key via composition, + * and putting a new value at the new key still only results in a single entry in the {@link HMap}. Additionally, + * all previous keys involved in the new key's composition are still able to resolve the value in their native type. * - * @param the type of value stored at this key + * @param f the other simple iso + * @param the new value type + * @return the new {@link TypeSafeKey} + */ + @Override + default TypeSafeKey andThen(Iso.Simple f) { + Iso.Simple composed = Iso.Simple.super.andThen(f); + return new TypeSafeKey() { + @Override + public

, FT extends Functor, PAFB extends Profunctor, PSFT extends Profunctor> PSFT apply( + PAFB pafb) { + return composed.apply(pafb); + } + + @Override + public int hashCode() { + return TypeSafeKey.this.hashCode(); + } + + @Override + public boolean equals(Object obj) { + return TypeSafeKey.this.equals(obj); + } + }; + } + + /** + * Static factory method for creating a simple type-safe key + * + * @param the type of value stored at this key * @return a unique type-safe key */ - static TypeSafeKey typeSafeKey() { - return new TypeSafeKey() { + static Simple typeSafeKey() { + return new TypeSafeKey.Simple() { + @Override + @SuppressWarnings("unchecked") + public

, FT extends Functor, PAFB extends Profunctor, PSFT extends Profunctor> PSFT apply( + PAFB pafb) { + return (PSFT) pafb; + } }; } -} + + /** + * A simplified {@link TypeSafeKey} that can only view a value of type A as an A. + * + * @param The type of the value that this key maps to inside an {@link HMap} + */ + interface Simple extends TypeSafeKey { + } +} \ No newline at end of file diff --git a/src/main/java/com/jnape/palatable/lambda/lens/lenses/HMapLens.java b/src/main/java/com/jnape/palatable/lambda/lens/lenses/HMapLens.java index 88b8e9e48..c7fd953da 100644 --- a/src/main/java/com/jnape/palatable/lambda/lens/lenses/HMapLens.java +++ b/src/main/java/com/jnape/palatable/lambda/lens/lenses/HMapLens.java @@ -15,13 +15,13 @@ private HMapLens() { } /** - * A lens that focuses on a value at a {@link TypeSafeKey}<A> in an {@link HMap}, as a {@link Maybe}. + * A lens that focuses on a value at a {@link TypeSafeKey.Simple}<A> in an {@link HMap}, as a {@link Maybe}. * * @param key the key * @param the value type at the key * @return the lens */ - public static Lens.Simple> valueAt(TypeSafeKey key) { + public static Lens.Simple> valueAt(TypeSafeKey key) { return simpleLens(m -> m.get(key), (m, maybeA) -> maybeA.fmap(a -> m.put(key, a)).orElseGet(() -> m.remove(key))); } } diff --git a/src/test/java/com/jnape/palatable/lambda/adt/hmap/HMapTest.java b/src/test/java/com/jnape/palatable/lambda/adt/hmap/HMapTest.java index 7bc55ff77..8495b059f 100644 --- a/src/test/java/com/jnape/palatable/lambda/adt/hmap/HMapTest.java +++ b/src/test/java/com/jnape/palatable/lambda/adt/hmap/HMapTest.java @@ -24,7 +24,7 @@ public class HMapTest { @Test public void getForPresentKey() { - TypeSafeKey stringKey = typeSafeKey(); + TypeSafeKey stringKey = typeSafeKey(); assertEquals(just("string value"), singletonHMap(stringKey, "string value").get(stringKey)); } @@ -38,14 +38,14 @@ public void getForAbsentKey() { @Test public void getForPresentKeyWithNullValue() { - TypeSafeKey stringKey = typeSafeKey(); + TypeSafeKey stringKey = typeSafeKey(); assertEquals(nothing(), singletonHMap(stringKey, null).get(stringKey)); } @Test public void put() { - TypeSafeKey stringKey = typeSafeKey(); + TypeSafeKey stringKey = typeSafeKey(); assertEquals(singletonHMap(stringKey, "string value"), emptyHMap().put(stringKey, "string value")); @@ -57,9 +57,9 @@ public void put() { @Test public void putAll() { - TypeSafeKey stringKey1 = typeSafeKey(); - TypeSafeKey stringKey2 = typeSafeKey(); - TypeSafeKey intKey = typeSafeKey(); + TypeSafeKey stringKey1 = typeSafeKey(); + TypeSafeKey stringKey2 = typeSafeKey(); + TypeSafeKey intKey = typeSafeKey(); HMap left = hMap(stringKey1, "string value", intKey, 1); @@ -78,8 +78,8 @@ public void putAll() { @Test public void remove() { - TypeSafeKey stringKey1 = typeSafeKey(); - TypeSafeKey stringKey2 = typeSafeKey(); + TypeSafeKey stringKey1 = typeSafeKey(); + TypeSafeKey stringKey2 = typeSafeKey(); assertEquals(emptyHMap(), emptyHMap() .put(stringKey1, "string value") @@ -94,8 +94,8 @@ public void remove() { @Test public void removeAll() { - TypeSafeKey stringKey1 = typeSafeKey(); - TypeSafeKey stringKey2 = typeSafeKey(); + TypeSafeKey stringKey1 = typeSafeKey(); + TypeSafeKey stringKey2 = typeSafeKey(); HMap hMap1 = hMap(stringKey1, "foo", stringKey2, "bar"); @@ -107,9 +107,9 @@ public void removeAll() { @Test public void containsKey() { - TypeSafeKey stringKey1 = typeSafeKey(); - TypeSafeKey stringKey2 = typeSafeKey(); - TypeSafeKey intKey = typeSafeKey(); + TypeSafeKey stringKey1 = typeSafeKey(); + TypeSafeKey stringKey2 = typeSafeKey(); + TypeSafeKey intKey = typeSafeKey(); HMap hMap = singletonHMap(stringKey1, "string"); @@ -120,7 +120,7 @@ public void containsKey() { @Test public void demandForPresentKey() { - TypeSafeKey stringKey = typeSafeKey(); + TypeSafeKey stringKey = typeSafeKey(); assertEquals("string value", singletonHMap(stringKey, "string value").demand(stringKey)); } @@ -132,8 +132,8 @@ public void demandForAbsentKey() { @Test public void toMap() { - TypeSafeKey stringKey = typeSafeKey(); - TypeSafeKey intKey = typeSafeKey(); + TypeSafeKey stringKey = typeSafeKey(); + TypeSafeKey intKey = typeSafeKey(); assertEquals(new HashMap() {{ put(stringKey, "string"); @@ -144,7 +144,7 @@ public void toMap() { @Test public void iteratesKVPairsAsTuples() { - TypeSafeKey stringKey = typeSafeKey(); + TypeSafeKey stringKey = typeSafeKey(); assertThat(singletonHMap(stringKey, "string value"), iterates(tuple(stringKey, "string value"))); @@ -152,7 +152,7 @@ public void iteratesKVPairsAsTuples() { @Test public void keys() { - TypeSafeKey stringKey = typeSafeKey(); + TypeSafeKey stringKey = typeSafeKey(); assertThat(singletonHMap(stringKey, "string value").keys(), iterates(stringKey)); @@ -166,9 +166,9 @@ public void values() { @Test public void convenienceStaticFactoryMethods() { - TypeSafeKey stringKey = typeSafeKey(); - TypeSafeKey intKey = typeSafeKey(); - TypeSafeKey floatKey = typeSafeKey(); + TypeSafeKey stringKey = typeSafeKey(); + TypeSafeKey intKey = typeSafeKey(); + TypeSafeKey floatKey = typeSafeKey(); assertEquals(emptyHMap().put(stringKey, "string value"), singletonHMap(stringKey, "string value")); assertEquals(emptyHMap().put(stringKey, "string value").put(intKey, 1), @@ -183,22 +183,22 @@ public void convenienceStaticFactoryMethods() { @Test @SuppressWarnings("EqualsWithItself") public void equality() { - assertTrue(emptyHMap().equals(emptyHMap())); + assertEquals(emptyHMap(), emptyHMap()); - TypeSafeKey stringKey = typeSafeKey(); - assertTrue(emptyHMap().put(stringKey, "one").equals(emptyHMap().put(stringKey, "one"))); + TypeSafeKey stringKey = typeSafeKey(); + assertEquals(emptyHMap().put(stringKey, "one"), emptyHMap().put(stringKey, "one")); - assertFalse(emptyHMap().equals(emptyHMap().put(stringKey, "string key"))); - assertFalse(emptyHMap().put(stringKey, "string key").equals(emptyHMap())); - assertFalse(emptyHMap().put(typeSafeKey(), "one").equals(emptyHMap().put(typeSafeKey(), "one"))); - assertFalse(emptyHMap().put(typeSafeKey(), "one").equals(emptyHMap().put(typeSafeKey(), 1))); - assertFalse(emptyHMap().put(typeSafeKey(), 1).equals(emptyHMap().put(typeSafeKey(), "one"))); + assertNotEquals(emptyHMap(), emptyHMap().put(stringKey, "string key")); + assertNotEquals(emptyHMap().put(stringKey, "string key"), emptyHMap()); + assertNotEquals(emptyHMap().put(typeSafeKey(), "one"), emptyHMap().put(typeSafeKey(), "one")); + assertNotEquals(emptyHMap().put(typeSafeKey(), "one"), emptyHMap().put(typeSafeKey(), 1)); + assertNotEquals(emptyHMap().put(typeSafeKey(), 1), emptyHMap().put(typeSafeKey(), "one")); } @Test public void hashCodeUsesDecentDistribution() { assertEquals(emptyHMap().hashCode(), emptyHMap().hashCode()); - TypeSafeKey stringKey = typeSafeKey(); + TypeSafeKey stringKey = typeSafeKey(); assertEquals(singletonHMap(stringKey, "string value").hashCode(), singletonHMap(stringKey, "string value").hashCode()); diff --git a/src/test/java/com/jnape/palatable/lambda/adt/hmap/SimpleTest.java b/src/test/java/com/jnape/palatable/lambda/adt/hmap/SimpleTest.java new file mode 100644 index 000000000..d3a908a99 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/adt/hmap/SimpleTest.java @@ -0,0 +1,14 @@ +package com.jnape.palatable.lambda.adt.hmap; + +import org.junit.Test; + +import static com.jnape.palatable.lambda.adt.hmap.TypeSafeKey.typeSafeKey; +import static org.junit.Assert.assertNotEquals; + +public class SimpleTest { + + @Test + public void usesReferenceEquality() { + assertNotEquals(typeSafeKey(), typeSafeKey()); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/adt/hmap/TypeSafeKeyTest.java b/src/test/java/com/jnape/palatable/lambda/adt/hmap/TypeSafeKeyTest.java index dbd29379e..577902c8c 100644 --- a/src/test/java/com/jnape/palatable/lambda/adt/hmap/TypeSafeKeyTest.java +++ b/src/test/java/com/jnape/palatable/lambda/adt/hmap/TypeSafeKeyTest.java @@ -2,13 +2,47 @@ import org.junit.Test; +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.adt.hmap.HMap.emptyHMap; import static com.jnape.palatable.lambda.adt.hmap.TypeSafeKey.typeSafeKey; -import static org.junit.Assert.assertFalse; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; +import static com.jnape.palatable.lambda.lens.Iso.simpleIso; +import static java.util.Arrays.asList; +import static org.junit.Assert.assertEquals; +import static testsupport.assertion.LensAssert.assertLensLawfulness; + public class TypeSafeKeyTest { @Test - public void usesReferenceEquality() { - assertFalse(typeSafeKey().equals(typeSafeKey())); + public void lensLawfulness() { + assertLensLawfulness(TypeSafeKey.typeSafeKey().andThen(simpleIso(Integer::parseInt, Object::toString)), + asList("123", "0"), + asList(456, -1)); + } + + @Test + public void compositionMapsOriginalValueInAndOutOfHMap() { + TypeSafeKey.Simple stringKey = typeSafeKey(); + TypeSafeKey intKey = stringKey.andThen(simpleIso(Integer::parseInt, Object::toString)); + HMap map = emptyHMap().put(stringKey, "123"); + + assertEquals(just("123"), map.get(stringKey)); + assertEquals(just(123), map.get(intKey)); + + HMap updated = map.put(intKey, 456); + assertEquals(just("456"), updated.get(stringKey)); + assertEquals(just(456), updated.get(intKey)); + + assertEquals(1, updated.keys().size()); + } + + @Test + public void discardRPreservesTypeSafeKey() { + TypeSafeKey.Simple stringKey = typeSafeKey(); + TypeSafeKey discardedKey = stringKey.discardR(simpleIso(id(), id())); + HMap map = emptyHMap().put(stringKey, "123"); + + assertEquals(just("123"), map.get(discardedKey)); } } \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/lens/lenses/HMapLensTest.java b/src/test/java/com/jnape/palatable/lambda/lens/lenses/HMapLensTest.java index 34dd1dbac..66073427e 100644 --- a/src/test/java/com/jnape/palatable/lambda/lens/lenses/HMapLensTest.java +++ b/src/test/java/com/jnape/palatable/lambda/lens/lenses/HMapLensTest.java @@ -16,7 +16,7 @@ public class HMapLensTest { @Test public void valueAt() { - TypeSafeKey key = typeSafeKey(); + TypeSafeKey.Simple key = typeSafeKey(); assertLensLawfulness(HMapLens.valueAt(key), asList(emptyHMap(), singletonHMap(key, "foo"), diff --git a/src/test/java/com/jnape/palatable/lambda/monoid/builtin/PutAllTest.java b/src/test/java/com/jnape/palatable/lambda/monoid/builtin/PutAllTest.java index fc5500ac5..bd664b01d 100644 --- a/src/test/java/com/jnape/palatable/lambda/monoid/builtin/PutAllTest.java +++ b/src/test/java/com/jnape/palatable/lambda/monoid/builtin/PutAllTest.java @@ -19,8 +19,8 @@ public void identity() { @Test public void monoid() { - TypeSafeKey stringKey = typeSafeKey(); - TypeSafeKey intKey = typeSafeKey(); + TypeSafeKey stringKey = typeSafeKey(); + TypeSafeKey intKey = typeSafeKey(); HMap x = singletonHMap(stringKey, "string"); HMap y = singletonHMap(intKey, 1); From b51fd9b1d97007cd8b840b45672049c5cd772e6a Mon Sep 17 00:00:00 2001 From: jnape Date: Thu, 26 Apr 2018 15:52:25 -0500 Subject: [PATCH 46/52] More javadocs for TypeSafeKey --- .../com/jnape/palatable/lambda/adt/hmap/TypeSafeKey.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/jnape/palatable/lambda/adt/hmap/TypeSafeKey.java b/src/main/java/com/jnape/palatable/lambda/adt/hmap/TypeSafeKey.java index aaa23b594..a71e4c04c 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/hmap/TypeSafeKey.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/hmap/TypeSafeKey.java @@ -7,15 +7,17 @@ import com.jnape.palatable.lambda.lens.LensLike; /** - * An interface representing a parametrized key for use in {@link HMap}s. The parameter specifies the type of the value - * stored at this binding inside the {@link HMap}. + * An interface representing a parametrized key for use in {@link HMap}s. Additionally, every {@link TypeSafeKey} is an + * {@link Iso} from the type the value is stored as to the type it's viewed and set as (on the way in / on the way out). + * This allows multiple keys to map to the same value, but to view the value as different types. *

* This is intentionally an interface so user-defined implementations are possible; however, it's important to note * that all hopes of type-safety hinge on equality being implemented such that no two {@link TypeSafeKey}s with * differing value-type parameters may be considered equal. Reference equality is used here as the default, as that is * sufficient. * - * @param The type of the value that this key maps to inside an {@link HMap} + * @param The raw type of the value that this key maps to inside an {@link HMap} + * @param The mapped type of the value that this key maps to inside an {@link HMap} */ public interface TypeSafeKey extends Iso.Simple { From 11d0b6a339389291822ee96eb08f110a64d902ec Mon Sep 17 00:00:00 2001 From: jnape Date: Thu, 26 Apr 2018 16:14:19 -0500 Subject: [PATCH 47/52] Unfoldr is now lazier --- CHANGELOG.md | 1 + .../lambda/iteration/UnfoldingIterator.java | 11 ++++++++--- .../lambda/iteration/UnfoldingIteratorTest.java | 16 ++++++++++++++++ 3 files changed, 25 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d8b9a0700..6a63bf8b3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/). - `View`/`Set`/`Over` now only require `LensLike` - `HMap#keys` now returns a `Set` - `HMap#values` now returns a `Collection` +- `Unfoldr` is now lazier, deferring all computations until `hasNext/next` calls ### Added - `BoundedBifunctor`, a `Bifunctor` super type that offers upper bounds for both parameters diff --git a/src/main/java/com/jnape/palatable/lambda/iteration/UnfoldingIterator.java b/src/main/java/com/jnape/palatable/lambda/iteration/UnfoldingIterator.java index 8bcdebe12..2c8790f1d 100644 --- a/src/main/java/com/jnape/palatable/lambda/iteration/UnfoldingIterator.java +++ b/src/main/java/com/jnape/palatable/lambda/iteration/UnfoldingIterator.java @@ -10,15 +10,19 @@ public class UnfoldingIterator extends ImmutableIterator { private final Function>> function; + private B seed; private Maybe> maybeAcc; - public UnfoldingIterator(Function>> function, B b) { + public UnfoldingIterator(Function>> function, B seed) { this.function = function; - maybeAcc = function.apply(b); + this.seed = seed; } @Override public boolean hasNext() { + if (maybeAcc == null) + maybeAcc = function.apply(seed); + return maybeAcc.fmap(constantly(true)).orElse(false); } @@ -30,7 +34,8 @@ public A next() { Tuple2 acc = maybeAcc.orElseThrow(NoSuchElementException::new); A next = acc._1(); - maybeAcc = function.apply(acc._2()); + seed = acc._2(); + maybeAcc = null; return next; } } diff --git a/src/test/java/com/jnape/palatable/lambda/iteration/UnfoldingIteratorTest.java b/src/test/java/com/jnape/palatable/lambda/iteration/UnfoldingIteratorTest.java index 2f13f2176..68fa1ec9c 100644 --- a/src/test/java/com/jnape/palatable/lambda/iteration/UnfoldingIteratorTest.java +++ b/src/test/java/com/jnape/palatable/lambda/iteration/UnfoldingIteratorTest.java @@ -5,10 +5,13 @@ import com.jnape.palatable.lambda.functions.Fn1; import org.junit.Test; +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.hlist.HList.tuple; import static org.hamcrest.core.Is.is; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThat; public class UnfoldingIteratorTest { @@ -24,4 +27,17 @@ public void hasNextIfFunctionProducesPresentValue() { public void doesNotHaveNextIfFunctionProducesEmptyValue() { assertThat(new UnfoldingIterator(x -> nothing(), 0).hasNext(), is(false)); } + + @Test + public void defersNextCallForAsLongAsPossible() { + AtomicInteger invocations = new AtomicInteger(0); + UnfoldingIterator iterator = new UnfoldingIterator<>(x -> { + invocations.incrementAndGet(); + return just(tuple(x.toString(), x + 1)); + }, 1); + + assertEquals(0, invocations.get()); + iterator.next(); + assertEquals(1, invocations.get()); + } } \ No newline at end of file From b2d598e2886626ae7bcb1545e2aa27cf35e54b61 Mon Sep 17 00:00:00 2001 From: jnape Date: Thu, 26 Apr 2018 16:26:33 -0500 Subject: [PATCH 48/52] Either#trying that supports CheckedRunnable --- CHANGELOG.md | 1 + .../jnape/palatable/lambda/adt/Either.java | 34 +++++++++++++++++-- .../palatable/lambda/adt/EitherTest.java | 16 +++++++++ 3 files changed, 48 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6a63bf8b3..45cb48d9e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -43,6 +43,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/). - `Under`, the inverse of `Over` for `Iso` - `TypeSafeKey` is an `Iso` and supports mapping - `TypeSafeKey.Simple`, the single parameter version of `TypeSafeKey` +- `Either#trying` overloads that accept `CheckedRunnable` ### Deprecated - `MapLens#mappingValues(Function)` is now deprecated in favor of the overload that takes an Iso diff --git a/src/main/java/com/jnape/palatable/lambda/adt/Either.java b/src/main/java/com/jnape/palatable/lambda/adt/Either.java index 76545bf68..0fbcf2dd5 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/Either.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/Either.java @@ -4,6 +4,7 @@ import com.jnape.palatable.lambda.functions.builtin.fn2.Peek; import com.jnape.palatable.lambda.functions.builtin.fn2.Peek2; import com.jnape.palatable.lambda.functions.specialized.checked.CheckedFn1; +import com.jnape.palatable.lambda.functions.specialized.checked.CheckedRunnable; import com.jnape.palatable.lambda.functions.specialized.checked.CheckedSupplier; import com.jnape.palatable.lambda.functor.Applicative; import com.jnape.palatable.lambda.functor.Bifunctor; @@ -21,9 +22,9 @@ import static java.util.Arrays.asList; /** - * The binary tagged union. General semantics tend to connote "success" values via the right value and "failure" values - * via the left values. Eithers are both Functors over their right value and - * Bifunctors over both values. + * The binary tagged union, implemented as a specialized {@link CoProduct2}. General semantics tend to connote "success" + * values via the right value and "failure" values via the left values. {@link Either}s are both {@link Monad}s and + * {@link Traversable}s over their right value and are {@link Bifunctor}s over both values. * * @param The left parameter type * @param The right parameter type @@ -305,6 +306,33 @@ public static Either trying(CheckedSupplier return trying(supplier, id()); } + /** + * Attempt to execute the {@link CheckedRunnable}, returning {@link Unit} in a right value. If the runnable throws + * an exception, apply leftFn to it, wrap it in a left value, and return it. + * + * @param runnable the runnable + * @param leftFn a function mapping E to L + * @param the most contravariant exception that the runnable might throw + * @param the left parameter type + * @return {@link Unit} as a right value, or leftFn's mapping result as a left value + */ + public static Either trying(CheckedRunnable runnable, + Function leftFn) { + return Try.trying(runnable).toEither(leftFn); + } + + /** + * Attempt to execute the {@link CheckedRunnable}, returning {@link Unit} in a right value. If the runnable throws + * exception, wrap it in a left value and return it. + * + * @param runnable the runnable + * @param the left parameter type (the most contravariant exception that runnable might throw) + * @return {@link Unit} as a right value, or a left value of the thrown exception + */ + public static Either trying(CheckedRunnable runnable) { + return trying(runnable, id()); + } + /** * Static factory method for creating a left value. * diff --git a/src/test/java/com/jnape/palatable/lambda/adt/EitherTest.java b/src/test/java/com/jnape/palatable/lambda/adt/EitherTest.java index 407197a8d..a06282ba6 100644 --- a/src/test/java/com/jnape/palatable/lambda/adt/EitherTest.java +++ b/src/test/java/com/jnape/palatable/lambda/adt/EitherTest.java @@ -22,6 +22,7 @@ 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.traitor.framework.Subjects.subjects; import static org.hamcrest.core.Is.is; import static org.junit.Assert.assertEquals; @@ -186,6 +187,21 @@ public void dyadicTryingLiftsCheckedSupplierMappingAnyThrownExceptions() { }, Throwable::getMessage)); } + @Test + public void dyadicTryingWithRunnable() { + assertEquals(right(UNIT), Either.trying(() -> {}, Throwable::getMessage)); + assertEquals(left("expected"), Either.trying(() -> { + throw new IllegalStateException("expected"); + }, Throwable::getMessage)); + } + + @Test + public void monadTryingWithRunnable() { + assertEquals(right(UNIT), Either.trying(() -> {})); + IllegalStateException expected = new IllegalStateException("expected"); + assertEquals(left(expected), Either.trying(() -> {throw expected;})); + } + @Test public void monadicPeekLiftsIOToTheRight() { Either left = left("foo"); From 1db219c8e5a59c5734763aefe9d9fa1ea3b3f174 Mon Sep 17 00:00:00 2001 From: jnape Date: Sat, 28 Apr 2018 14:31:03 -0500 Subject: [PATCH 49/52] Lens#both specialized for simple lenses --- .../java/com/jnape/palatable/lambda/lens/Lens.java | 14 ++++++++++++++ .../com/jnape/palatable/lambda/lens/LensTest.java | 9 +++++++++ 2 files changed, 23 insertions(+) diff --git a/src/main/java/com/jnape/palatable/lambda/lens/Lens.java b/src/main/java/com/jnape/palatable/lambda/lens/Lens.java index 4b407ec8f..9b0f54774 100644 --- a/src/main/java/com/jnape/palatable/lambda/lens/Lens.java +++ b/src/main/java/com/jnape/palatable/lambda/lens/Lens.java @@ -288,6 +288,20 @@ static Lens, Tuple2> both(Lens cd.biMap(set(f), set(g)).into(Fn1::compose).apply(s)); } + /** + * Dually focus on two simple lenses at the same time. + * + * @param f the first lens + * @param g the second lens + * @param both larger values + * @param both smaller viewing values + * @param both smaller setting values + * @return the dual-focus simple lens + */ + static Lens.Simple> both(Lens.Simple f, Lens.Simple g) { + return adapt(both((Lens) f, g)); + } + /** * A convenience type with a simplified type signature for common lenses with both unified "larger" values and * unified "smaller" values. diff --git a/src/test/java/com/jnape/palatable/lambda/lens/LensTest.java b/src/test/java/com/jnape/palatable/lambda/lens/LensTest.java index 14939edac..2ab9e8028 100644 --- a/src/test/java/com/jnape/palatable/lambda/lens/LensTest.java +++ b/src/test/java/com/jnape/palatable/lambda/lens/LensTest.java @@ -94,4 +94,13 @@ public void bothSplitsFocusBetweenLenses() { assertEquals(tuple('a', 3), view(both, "abc")); assertEquals("zb", set(both, tuple('z', 2), "abc")); } + + @Test + public void bothForSimpleLenses() { + Lens.Simple stringToInt = simpleLens(Integer::parseInt, (s, i) -> s + i.toString()); + Lens.Simple stringToChar = simpleLens(s -> s.charAt(0), (s, c) -> s + c.toString()); + + assertEquals(tuple(3, '3'), view(both(stringToInt, stringToChar), "3")); + assertEquals("133", set(both(stringToInt, stringToChar), tuple(3, '3'), "1")); + } } \ No newline at end of file From fdcadccbde4a483844760aa2fa072cfd50df9b13 Mon Sep 17 00:00:00 2001 From: jnape Date: Thu, 3 May 2018 18:59:07 -0500 Subject: [PATCH 50/52] Present is a singleton --- .../com/jnape/palatable/lambda/monoid/builtin/Present.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/jnape/palatable/lambda/monoid/builtin/Present.java b/src/main/java/com/jnape/palatable/lambda/monoid/builtin/Present.java index d01911cf0..aa1433c77 100644 --- a/src/main/java/com/jnape/palatable/lambda/monoid/builtin/Present.java +++ b/src/main/java/com/jnape/palatable/lambda/monoid/builtin/Present.java @@ -27,6 +27,8 @@ */ public final class Present implements MonoidFactory, Maybe> { + private static final Present INSTANCE = new Present<>(); + private Present() { } @@ -36,8 +38,9 @@ public Monoid> apply(Semigroup aSemigroup) { nothing()); } + @SuppressWarnings("unchecked") public static Present present() { - return new Present<>(); + return INSTANCE; } public static Monoid> present(Semigroup semigroup) { From f552f1a9fd3cde9ca992619e1f08797746d4c808 Mon Sep 17 00:00:00 2001 From: jnape Date: Fri, 4 May 2018 02:28:23 -0500 Subject: [PATCH 51/52] Updating javadoc and CHANGELOG --- CHANGELOG.md | 1 + src/main/java/com/jnape/palatable/lambda/adt/hmap/HMap.java | 1 + 2 files changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 45cb48d9e..405f7c8f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/). - `HMap#keys` now returns a `Set` - `HMap#values` now returns a `Collection` - `Unfoldr` is now lazier, deferring all computations until `hasNext/next` calls +- `Present` is now a singleton ### Added - `BoundedBifunctor`, a `Bifunctor` super type that offers upper bounds for both parameters diff --git a/src/main/java/com/jnape/palatable/lambda/adt/hmap/HMap.java b/src/main/java/com/jnape/palatable/lambda/adt/hmap/HMap.java index 264868485..83fc47f11 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/hmap/HMap.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/hmap/HMap.java @@ -42,6 +42,7 @@ private HMap(Map table) { * Retrieve the value at this key. * * @param key the key + * @param the value type * @param the value type * @return Maybe the value at this key */ From f666a2a12b5bd086cdc78003f7ffe4238f02140e Mon Sep 17 00:00:00 2001 From: jnape Date: Fri, 4 May 2018 02:32:00 -0500 Subject: [PATCH 52/52] [maven-release-plugin] prepare release lambda-3.0.0 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index a0c32680e..d990937e0 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ lambda - 2.1.2-SNAPSHOT + 3.0.0 jar Lambda

+ * For more information, read about isos. + * + * @param the larger type for focusing + * @param the larger type for mirrored focusing + * @param the smaller type for focusing + * @param the smaller type for mirrored focusing + */ +@FunctionalInterface +public interface Iso extends LensLike { + +