diff --git a/CHANGELOG.md b/CHANGELOG.md index 60491f794..405f7c8f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,52 @@ 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 +- ***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 +- `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`) +- `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` +- `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 +- `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` +- `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 +- `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` +- `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 + +## [2.1.1] - 2018-01-16 +### Changed - ***Breaking Change***: Moved `Trampoline` and `RecursiveResult` to better package ## [2.1.0] - 2018-01-14 @@ -254,7 +300,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..dcfe6df73 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 @@ -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 @@ -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 diff --git a/pom.xml b/pom.xml index 94b3a6847..d990937e0 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ lambda - 2.1.1 + 3.0.0 jar Lambda 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..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 @@ -224,31 +225,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 +258,7 @@ public Applicative, App> traverse( * * @return Maybe the right value */ - public Maybe toMaybe() { + public final Maybe toMaybe() { return projectB(); } @@ -287,14 +288,9 @@ public static Either fromMaybe(Maybe maybe, Supplier leftFn) * @param the right parameter type * @return the supplier result as a right value, or leftFn's mapping result as a left value */ - @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::get).toEither(leftFn); } /** @@ -310,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/main/java/com/jnape/palatable/lambda/adt/Maybe.java b/src/main/java/com/jnape/palatable/lambda/adt/Maybe.java index 9bfc7eed0..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 @@ -134,11 +132,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. * @@ -158,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(); } /** @@ -169,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()); } /** @@ -223,10 +216,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 +253,9 @@ public Maybe flatMap(Function> f) { @Override @SuppressWarnings("unchecked") - public Applicative, App> traverse( - Function> fn, - Function, ? extends Applicative, App>> pure) { - return (Applicative, App>) pure.apply(nothing()); + 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 new file mode 100644 index 000000000..f4ffc5846 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/adt/These.java @@ -0,0 +1,240 @@ +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))); + } + + @Override + @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())); + } + + /** + * {@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/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..0a2169f09 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/adt/Try.java @@ -0,0 +1,318 @@ +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 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()}. + * + * @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 >, 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 + 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 A orThrow() throws T { + throw 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 A orThrow() throws T { + return 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/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 00083fac4..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(); } /** @@ -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 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/adt/hmap/HMap.java b/src/main/java/com/jnape/palatable/lambda/adt/hmap/HMap.java index 48d60db79..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 @@ -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,13 @@ private HMap(Map table) { * Retrieve the value at this key. * * @param key the key - * @param the value type + * @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 +59,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 +71,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 +117,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 +195,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 +210,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 +229,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..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 @@ -1,25 +1,106 @@ 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. 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 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 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 { +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/functions/builtin/fn1/Cycle.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Cycle.java index 440d50041..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.iterators.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/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/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/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/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..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.iterators.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/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/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..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.iterators.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/functions/builtin/fn2/Filter.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Filter.java index ebad91eb5..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.iterators.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/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/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); } } 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..a7e512cc7 --- /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 the {@link Iterable} element type + */ +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/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..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.iterators.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/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/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/Sequence.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Sequence.java index 3232736e7..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 @@ -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(); @@ -35,44 +40,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 traversable.traverse(id(), pure); } @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/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..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.iterators.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/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/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..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.iterators.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/functions/builtin/fn2/TakeWhile.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/TakeWhile.java index e14f5caf8..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.iterators.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/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/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/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/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/functions/specialized/checked/CheckedFn1.java b/src/main/java/com/jnape/palatable/lambda/functions/specialized/checked/CheckedFn1.java index f91b83aa4..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 @@ -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 @@ -25,11 +27,11 @@ default B apply(A a) { } /** - * A version of {@link Fn1} that can throw checked exceptions. + * A version of {@link Fn1#apply} 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 + * @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 new file mode 100644 index 000000000..2552c746e --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/specialized/checked/CheckedRunnable.java @@ -0,0 +1,58 @@ +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; + +/** + * 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); + } + } + + /** + * 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 dd38fc165..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 @@ -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,29 @@ 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; + + /** + * 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; + } } 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/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/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/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..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 @@ -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. @@ -85,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()); } /** @@ -131,4 +120,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/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/functor/builtin/Identity.java b/src/main/java/com/jnape/palatable/lambda/functor/builtin/Identity.java index def56eea0..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 @@ -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/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/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/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/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/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/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/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..b545a296f --- /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 final 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/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/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 85% 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..40458cd48 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; @@ -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/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/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/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/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/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/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/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/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/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/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/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/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 new file mode 100644 index 000000000..7c084c7ab --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/iteration/SnocIterator.java @@ -0,0 +1,28 @@ +package com.jnape.palatable.lambda.iteration; + +import java.util.Iterator; +import java.util.NoSuchElementException; + +public final class SnocIterator implements Iterator { + + private final Iterator as; + private final Iterator snocs; + + public SnocIterator(Iterator as, Iterator snocs) { + this.as = as; + this.snocs = snocs; + } + + @Override + public boolean hasNext() { + return as.hasNext() || snocs.hasNext(); + } + + @Override + public A next() { + if (!hasNext()) + throw new NoSuchElementException(); + + return as.hasNext() ? as.next() : snocs.next(); + } +} 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/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 77% 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..2c8790f1d 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; @@ -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/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/iterators/ConcatenatingIterator.java b/src/main/java/com/jnape/palatable/lambda/iterators/ConcatenatingIterator.java deleted file mode 100644 index cd58f6f74..000000000 --- a/src/main/java/com/jnape/palatable/lambda/iterators/ConcatenatingIterator.java +++ /dev/null @@ -1,47 +0,0 @@ -package com.jnape.palatable.lambda.iterators; - -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/iterators/SnocIterator.java b/src/main/java/com/jnape/palatable/lambda/iterators/SnocIterator.java deleted file mode 100644 index 2506bd658..000000000 --- a/src/main/java/com/jnape/palatable/lambda/iterators/SnocIterator.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.jnape.palatable.lambda.iterators; - -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; - - public SnocIterator(A last, Iterable inits) { - this.last = last; - initsSupplier = inits::iterator; - } - - @Override - public boolean hasNext() { - if (inits == null) - queueAndDeforest(); - - return inits.hasNext() || lasts.hasNext(); - } - - @Override - 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(); - } -} 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..56caf718e --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/lens/Iso.java @@ -0,0 +1,298 @@ +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). + *

+ * 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 { + +

, 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(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, Fn1> unIso() { + return Tuple2.fill(this., Identity, Identity, Identity, + Exchange>, + Exchange>>apply(new Exchange<>(id(), Identity::new)).diMapR(Identity::runIdentity)) + .biMap(e -> fn1(e.sa()), e -> fn1(e.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 adapt(Iso.super.mirror()); + } + + @Override + 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. + * + * @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 Iso.Simple.adapt(Iso.super.compose(g)); + } + + /** + * 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 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 7042b603d..9b0f54774 100644 --- a/src/main/java/com/jnape/palatable/lambda/lens/Lens.java +++ b/src/main/java/com/jnape/palatable/lambda/lens/Lens.java @@ -1,15 +1,15 @@ package com.jnape.palatable.lambda.lens; -import com.jnape.palatable.lambda.functions.Fn2; +import com.jnape.palatable.lambda.adt.hlist.Tuple2; +import com.jnape.palatable.lambda.functions.Fn1; +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; 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; @@ -136,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 @@ -167,87 +149,64 @@ 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 compose(lens(fn, (r, t) -> t)); + 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 andThen(lens(id(), (a, z) -> fn.apply(z))); + return lens(view(this), (s, z) -> set(this, fn.apply(z), s)); } /** @@ -312,6 +271,37 @@ 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)); + } + + /** + * 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. @@ -320,55 +310,55 @@ static Lens.Simple simpleLens(Function gett * @param the type of both "smaller" values */ @FunctionalInterface - interface Simple extends Lens { + interface Simple extends Lens, LensLike.Simple { - @Override - default , FA extends Functor> Fixed fix() { - return this::apply; - } - - @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); } + /** + * 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) { + static Lens.Simple adapt(Lens lens) { return lens::apply; } /** - * A convenience type with a simplified type signature for fixed simple lenses. + * Specialization of {@link Lens#both(Lens, Lens)} for 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 + * @param f the first lens + * @param g the second lens + * @param both lens larger values + * @param lens f smaller values + * @param lens g smaller values + * @return the dual-focus simple lens */ - @FunctionalInterface - interface Fixed, FA extends Functor> - extends Lens.Fixed { + static Lens.Simple> both(Lens f, Lens g) { + return Lens.Simple.adapt(Lens.both(f, g)); } } - - /** - * 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/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/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/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)); } } 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..c7fd953da --- /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.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) { + return simpleLens(m -> m.get(key), (m, maybeA) -> maybeA.fmap(a -> m.put(key, a)).orElseGet(() -> m.remove(key))); + } +} 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/main/java/com/jnape/palatable/lambda/monad/Monad.java b/src/main/java/com/jnape/palatable/lambda/monad/Monad.java index 770566cbf..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)); } /** @@ -63,7 +61,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 +69,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 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..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.iterators.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/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) { 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/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"); 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 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..362671cf6 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/adt/TryTest.java @@ -0,0 +1,153 @@ +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.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +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.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; +import static testsupport.matchers.LeftMatcher.isLeftThat; + +@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)); + } + + @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 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()); + 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), trying(() -> {throw expected;})); + + assertEquals(success("foo"), trying(() -> "foo")); + } +} \ No newline at end of file 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/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/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/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/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 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/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/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 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 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 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 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 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 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/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/iterators/ConsingIteratorTest.java b/src/test/java/com/jnape/palatable/lambda/iteration/ConsingIteratorTest.java similarity index 95% 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..68f99d943 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; @@ -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/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/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/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 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..52ab7899f --- /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.Deforesting; + +@RunWith(Traits.class) +public class DroppingIterableTest { + + @TestTraits({Deforesting.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/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 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 70% 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..7288e8fbc 100644 --- a/src/test/java/com/jnape/palatable/lambda/iterators/FlatteningIteratorTest.java +++ b/src/test/java/com/jnape/palatable/lambda/iteration/FlatteningIteratorTest.java @@ -1,7 +1,9 @@ -package com.jnape.palatable.lambda.iterators; +package com.jnape.palatable.lambda.iteration; 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 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/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 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/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 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/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 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/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 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/iteration/SnocIterableTest.java b/src/test/java/com/jnape/palatable/lambda/iteration/SnocIterableTest.java new file mode 100644 index 000000000..258e872a3 --- /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.Deforesting; + +@RunWith(Traits.class) +public class SnocIterableTest { + + @TestTraits({Deforesting.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/iterators/SnocIteratorTest.java b/src/test/java/com/jnape/palatable/lambda/iteration/SnocIteratorTest.java similarity index 50% 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..6fca49817 100644 --- a/src/test/java/com/jnape/palatable/lambda/iterators/SnocIteratorTest.java +++ b/src/test/java/com/jnape/palatable/lambda/iteration/SnocIteratorTest.java @@ -1,16 +1,10 @@ -package com.jnape.palatable.lambda.iterators; +package com.jnape.palatable.lambda.iteration; 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 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 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 61% 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..68fa1ec9c 100644 --- a/src/test/java/com/jnape/palatable/lambda/iterators/UnfoldingIteratorTest.java +++ b/src/test/java/com/jnape/palatable/lambda/iteration/UnfoldingIteratorTest.java @@ -1,14 +1,17 @@ -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; 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 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/com/jnape/palatable/lambda/iterators/ConcatenatingIteratorTest.java b/src/test/java/com/jnape/palatable/lambda/iterators/ConcatenatingIteratorTest.java deleted file mode 100644 index 47c360f0e..000000000 --- a/src/test/java/com/jnape/palatable/lambda/iterators/ConcatenatingIteratorTest.java +++ /dev/null @@ -1,99 +0,0 @@ -package com.jnape.palatable.lambda.iterators; - -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 = 50000; - 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 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 df8e40942..2ab9e8028 100644 --- a/src/test/java/com/jnape/palatable/lambda/lens/LensTest.java +++ b/src/test/java/com/jnape/palatable/lambda/lens/LensTest.java @@ -1,7 +1,7 @@ package com.jnape.palatable.lambda.lens; import com.jnape.palatable.lambda.adt.Maybe; -import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.adt.hlist.Tuple2; import com.jnape.palatable.lambda.functor.builtin.Const; import com.jnape.palatable.lambda.functor.builtin.Identity; import com.jnape.palatable.traitor.annotations.TestTraits; @@ -18,7 +18,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; @@ -52,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); @@ -72,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 @@ -89,4 +84,23 @@ 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")); + } + + @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 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 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..66073427e --- /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.Simple 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 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/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); 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/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)) diff --git a/src/test/java/testsupport/traits/Deforesting.java b/src/test/java/testsupport/traits/Deforesting.java new file mode 100644 index 000000000..cb38b87ef --- /dev/null +++ b/src/test/java/testsupport/traits/Deforesting.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 Deforesting implements Trait, Iterable>> { + + @Override + public void test(Fn1, Iterable> fn) { + times(10_000, fn, repeat(1)).iterator().next(); + } +} 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))))))");