From 7aaf1670d3c7491dd878787828b9aee82e12f962 Mon Sep 17 00:00:00 2001 From: Michael A Date: Thu, 30 May 2019 17:15:18 -0500 Subject: [PATCH 01/39] Comonads --- .DS_Store | Bin 0 -> 6148 bytes .../palatable/lambda/comonad/Comonad.java | 90 +++++++++++ .../lambda/comonad/builtin/ComonadEnv.java | 59 ++++++++ .../lambda/comonad/builtin/ComonadStore.java | 87 +++++++++++ .../lambda/comonad/builtin/ComonadTraced.java | 37 +++++ .../functions/specialized/Cokleisli.java | 53 +++++++ .../palatable/lambda/functor/builtin/Env.java | 140 ++++++++++++++++++ .../lambda/functor/builtin/Identity.java | 21 ++- .../lambda/functor/builtin/Store.java | 85 +++++++++++ .../lambda/functor/builtin/Traced.java | 85 +++++++++++ .../lambda/functor/builtin/EnvTest.java | 19 +++ .../lambda/functor/builtin/IdentityTest.java | 7 +- .../lambda/functor/builtin/StoreTest.java | 63 ++++++++ .../lambda/functor/builtin/TracedTest.java | 65 ++++++++ src/test/java/testsupport/EquatableW.java | 49 ++++++ .../java/testsupport/traits/ComonadLaws.java | 59 ++++++++ 16 files changed, 913 insertions(+), 6 deletions(-) create mode 100644 .DS_Store create mode 100644 src/main/java/com/jnape/palatable/lambda/comonad/Comonad.java create mode 100644 src/main/java/com/jnape/palatable/lambda/comonad/builtin/ComonadEnv.java create mode 100644 src/main/java/com/jnape/palatable/lambda/comonad/builtin/ComonadStore.java create mode 100644 src/main/java/com/jnape/palatable/lambda/comonad/builtin/ComonadTraced.java create mode 100644 src/main/java/com/jnape/palatable/lambda/functions/specialized/Cokleisli.java create mode 100644 src/main/java/com/jnape/palatable/lambda/functor/builtin/Env.java create mode 100644 src/main/java/com/jnape/palatable/lambda/functor/builtin/Store.java create mode 100644 src/main/java/com/jnape/palatable/lambda/functor/builtin/Traced.java create mode 100644 src/test/java/com/jnape/palatable/lambda/functor/builtin/EnvTest.java create mode 100644 src/test/java/com/jnape/palatable/lambda/functor/builtin/StoreTest.java create mode 100644 src/test/java/com/jnape/palatable/lambda/functor/builtin/TracedTest.java create mode 100644 src/test/java/testsupport/EquatableW.java create mode 100644 src/test/java/testsupport/traits/ComonadLaws.java diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..de5fc53990f435abad0808330fef4a844df945b2 GIT binary patch literal 6148 zcmeH~F^LL|d(t0@2UW`S!Tku=OfJ-;({to>)nK zk%75DdH(StZPNiy=_3~Vxh+TmDIf);fE17d zD^ehj@!ft!&!k6@0#aZd3i$V-(495eI^)y95F-FNupGv9%o1er0$G!-lNFlf^kCU) zF@|_O+R2jF)nx1J?XVm^EbnYS#n7y`!wM6c)qsK&kOB(@wml#H{6EwGn*SFqN~M4l zcryiTIDMUte5pKJUtZ7am#q4_(aE@+;nz<96F-U%^f2xhUywD~I$5FVM<8TSkOKdz Fz!Qau5~2VA literal 0 HcmV?d00001 diff --git a/src/main/java/com/jnape/palatable/lambda/comonad/Comonad.java b/src/main/java/com/jnape/palatable/lambda/comonad/Comonad.java new file mode 100644 index 000000000..2ea5f88fe --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/comonad/Comonad.java @@ -0,0 +1,90 @@ +package com.jnape.palatable.lambda.comonad; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.builtin.fn1.Downcast; +import com.jnape.palatable.lambda.functor.Functor; +import com.jnape.palatable.lambda.monad.Monad; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; + +/** + * Comonads are {@link Functor}s that support an {@link Comonad#extract()} operation + * to yield a specific value, and an {@link Comonad#extend(Fn1)} which extends + * a function which uses the global state of the Comonad to produce a new local result + * at each point to yield a new Comonad. + *

+ * Comonad laws: + *

    + *
  • left identity: w.extend(wa -> wa.extract()).equals(w)
  • + *
  • right identity: w.extend(f).extract().equals(f.apply(w))
  • + *
  • associativity: w.extend(f).extend(g).equals(w.extend(wa -> g(wa.extend(f))))
  • + *
+ * + * @param the type of the value the Comonad stores + * @param the unification parameter to type-constrain Comonads to themselves (as an upside-down `M`, used for {@link Monad}) + */ +public interface Comonad> extends Functor { + + /** + * Extract an A from the Comonad. Often Comonads feature some sort of cursor, and this will yield the element at the cursor. + * + * @return the current A + */ + A extract(); + + /** + * A version of {@link Comonad#extend} to be implemented by a class extending the interface; {@link Comonad#extend} + * takes care of some of the type inference based off of this method. See {@link Comonad#extend} for details. + * + * @param f the function using the global state Comonad<A, W> to produce a B + * @param the resulting B at each point in the resulting Comonad<B, W> + * @return the new Comonad instance + */ + Comonad extendImpl(Fn1, ? extends B> f); + + /** + * Extend a function Fn<Comonad<A, W>, B> over a Comonad. This allows for computations which use global knowledge to yield a local result. + *

+ * For example, think of blurring an image, where the new pixel relies on the surrounding pixels, or of producing the next step in a generic cellular automaton. + * + * @param f the function using the global state Comonad<A, W> to produce a B + * @param the resulting B at each point in the resulting Comonad<B, W> + * @param the type of the initial Comonad<A, W> + * @return the new Comonad instance + * + * Default implementation in terms of duplicate would be `return this.duplicate().fmap(f);` + * + */ + default > Comonad extend(Fn1 f) { + return extendImpl(f.contraMap(Downcast::>downcast)); + } + + /** + * {@inheritDoc} + */ + @Override + default Comonad fmap(Fn1 fn) { + return extend(wa -> fn.apply(wa.extract())); + } + + /** + * Duplicate a Comonad<A, W> to a Comonad<Comonad<A, W>, W>. + *

+ * Essentially, for the case of a non-empty data structure with a cursor, this produces that data structure with the cursor at all possible locations. + *

+ * It may be worth finding a way to do this lazily, as a Comonad can often be thought of as representing a state space (potentially infinite), with a Monad then used to traverse a specific path through it. + *

+ * `extend` is left to be implemented, and `duplicate` is given a definition based off of it, in symmetry with `Monad`. + * However, it might be better to reverse this for `Comonad`, as there may be a more natural definition for `duplicate` in an implementation for the interface. + *

+ * + * @param w the Comonad to be duplicated + * @param the value type of the Comonad + * @param the Comonad unification parameter + * @return the unfolded Comonad across all possible cursors + */ + static > Comonad, W> duplicate(Comonad w) { + return w.extend(id()); + } +} + diff --git a/src/main/java/com/jnape/palatable/lambda/comonad/builtin/ComonadEnv.java b/src/main/java/com/jnape/palatable/lambda/comonad/builtin/ComonadEnv.java new file mode 100644 index 000000000..df9217e28 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/comonad/builtin/ComonadEnv.java @@ -0,0 +1,59 @@ +package com.jnape.palatable.lambda.comonad.builtin; + +import com.jnape.palatable.lambda.adt.product.Product2; +import com.jnape.palatable.lambda.comonad.Comonad; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functor.builtin.Env; + +/** + * An interface for an Env {@link Comonad}, which provides some additional context through {@link ComonadEnv#ask}. + * In the simplest case, for {@link Env}, this is a {@link Product2} with the additional semantics that {@link Product2#_1} + * is the environment, and {@link Product2#_2} the value extracted from the Comonad. + * + * @param the environment type + * @param the value type + */ +public interface ComonadEnv> extends Comonad, Product2 { + /** + * Retrieve the environment. + * + * @return the environment of type E + */ + E ask(); + + /** + * Retrieve a mapped environment. + * + * @param the return type of the environment + * @param f the mapping function over the environment + * @return the new environment R + */ + default R asks(Fn1 f) { + return f.apply(this.ask()); + } + + /** + * Map the environment type of a ComonadEnv. + * + * @param f the mapping function of the environment + * @param the new environment type + * @return a new instance of a ComonadEnv with environment R and the same value A as previously + */ + ComonadEnv mapEnv(Fn1 f); + + /** + * Retrieve the environment as the first element. + */ + default E _1() { + return ask(); + } + + /** + * Retrieve the comonadic extraction value as the second element. + * + * @return the second element + */ + default A _2() { + return extract(); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/comonad/builtin/ComonadStore.java b/src/main/java/com/jnape/palatable/lambda/comonad/builtin/ComonadStore.java new file mode 100644 index 000000000..98344ae63 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/comonad/builtin/ComonadStore.java @@ -0,0 +1,87 @@ +package com.jnape.palatable.lambda.comonad.builtin; + +import com.jnape.palatable.lambda.comonad.Comonad; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.builtin.fn1.Id; +import com.jnape.palatable.lambda.functor.Functor; +import com.jnape.palatable.lambda.functor.builtin.Store; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Downcast.downcast; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Upcast.upcast; + +/** + * An interface for an Store {@link Comonad}, which can be thought of as providing a lookup function and a current input. + * A concrete class is provided in {@link Store}. + * + * @param the type of the storage and the cursor + * @param the type of the value produced + */ +public interface ComonadStore> extends Comonad { + /** + * Retrieve a value from a mapped cursor. + * + * @param f the relative change to make to the cursor before retrieving a value + * @return a retrieved value A + */ + A peeks(Fn1 f); + + /** + * Retrieve a value from a cursor. + * + * @param s the cursor to extract the value from + * @return a retrieved value A + */ + default A peek(S s) { + return peeks(constantly(s)); + }; + + /** + * Retrieve a value from the current cursor. + * + * @return a retrieved value A + */ + default A pos() { + return peeks(id()); + } + + /** + * Produce a new store by providing a relative change to the cursor. + * + * This can be implemented using the Comonad implementation as: + * return downcast(this.extend((Fn1<WA, A>) s -> s.peeks(f))); + * but is left unimplemented in the interface to prevent conflicts from using `seeks` to implement {@link Comonad#extendImpl}. + * + * @param f the relative change to make to the cursor before retrieving a value + * @return a new ComonadStore with updated cursor + */ + ComonadStore seeks(Fn1 f); + + /** + * Produce a new store at a given cursor. + * + * @param s the cursor to extract the value from + * @return a new ComonadStore with updated cursor + */ + default ComonadStore seek(S s) { + return seeks(constantly(s)); + }; + + /** + * Retrieve a {@link Functor} of value(s) by perturbing the cursor. + * + * @param the functor to be mapped over + * @param f the function for perturbing the cursor + * @return a Functor F of value(s) A + */ + > Functor experiment(Fn1> f); + + /** + * {@inheritDoc} + */ + @Override + default A extract() { + return pos(); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/comonad/builtin/ComonadTraced.java b/src/main/java/com/jnape/palatable/lambda/comonad/builtin/ComonadTraced.java new file mode 100644 index 000000000..886da414d --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/comonad/builtin/ComonadTraced.java @@ -0,0 +1,37 @@ +package com.jnape.palatable.lambda.comonad.builtin; + +import com.jnape.palatable.lambda.comonad.Comonad; +import com.jnape.palatable.lambda.functor.builtin.Traced; +import com.jnape.palatable.lambda.monoid.Monoid; + +/** + * An interface for an Traced {@link Comonad}, which monoidally combines input to a trace function through a {@link Monoid} instance. + * A concrete class is provided in {@link Traced}. + * + * @param the type of the input values to the trace function + * @param the type of the output from the trace function + */ +public interface ComonadTraced, B, W extends Comonad> extends Comonad { + /** + * Run a provided trace function. + * + * @param a the value to provide to the tracer. + * @return the value output by the trace function + */ + B runTrace(A a); + + /** + * Retrieve the {@link Monoid} instance for the input type A. + * + * @return a {@link Monoid} instance for A + */ + Monoid getMonoid(); + + /** + * {@inheritDoc} + */ + @Override + default B extract() { + return runTrace(getMonoid().identity()); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functions/specialized/Cokleisli.java b/src/main/java/com/jnape/palatable/lambda/functions/specialized/Cokleisli.java new file mode 100644 index 000000000..0f171b976 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/specialized/Cokleisli.java @@ -0,0 +1,53 @@ +package com.jnape.palatable.lambda.functions.specialized; + +import com.jnape.palatable.lambda.comonad.Comonad; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.builtin.fn1.Downcast; + +/** + * The Cokleisli arrow of a {@link Comonad}, as a convenience wrapper around a {@link Fn1}<WA, B>. + * This can be thought of as a fixed, portable {@link Comonad#extend(Fn1)}. + * + * @param the extraction value of the input {@link Comonad} + * @param the {@link Comonad} unification parameter + * @param the output argument type + */ +@FunctionalInterface +public interface Cokleisli> extends Fn1, B> { + + /** + * Left-to-right composition of two compatible {@link Cokleisli} arrows, yielding a new {@link Cokleisli} arrow. + * + * @param after the arrow to execute after this one + * @param the new return parameter type + * @return the composition of the two arrows as a new {@link Cokleisli} arrow + */ + default Cokleisli andThen(Cokleisli after) { + return wa -> after.apply(wa.extend(this)); + } + + /** + * Right-to-left composition of two compatible {@link Cokleisli} arrows, yielding a new {@link Cokleisli} arrow. + * + * @param before the arrow to execute before this one + * @param the new input argument type + * @return the composition of the two arrows as a new {@link Cokleisli} arrow + */ + default Cokleisli compose(Cokleisli before) { + return wz -> this.apply(wz.extend(before)); + } + + /** + * Adapt a compatible function into a {@link Cokleisli} arrow. + * + * @param fn the function + * @param the input parameter type + * @param the output argument type + * @param the {@link Comonad} unification parameter + * @param the input {@link Comonad} type + * @return the function adapted as a {@link Cokleisli} arrow + */ + static , WA extends Comonad> Cokleisli cokleisli(Fn1 fn) { + return fn.contraMap((Fn1, ? extends WA>) Downcast::downcast)::apply; + } +} \ No newline at end of file diff --git a/src/main/java/com/jnape/palatable/lambda/functor/builtin/Env.java b/src/main/java/com/jnape/palatable/lambda/functor/builtin/Env.java new file mode 100644 index 000000000..2dabb2912 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functor/builtin/Env.java @@ -0,0 +1,140 @@ +package com.jnape.palatable.lambda.functor.builtin; + +import com.jnape.palatable.lambda.adt.product.Product2; +import com.jnape.palatable.lambda.comonad.Comonad; +import com.jnape.palatable.lambda.comonad.builtin.ComonadEnv; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.builtin.fn1.Id; +import com.jnape.palatable.lambda.functor.Bifunctor; +import com.jnape.palatable.lambda.functor.Functor; + +import java.util.Objects; + +import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; + +/** + * A concrete implementation of the {@link ComonadEnv} interface. + * + * @param the environment type + * @param the value type + */ +public final class Env implements Comonad>, ComonadEnv>, Bifunctor> { + private E env; + private A value; + + private Env(E e, A a) { + this.env = e; + this.value = a; + } + + + /** + * Constructor function for an Env. + * + * @param env the environment provided as context to value + * @param value the primary value to be extracted + * @param the type of the environment + * @param the type of the value + * @return a new instance of Env<E, A> + */ + public static Env env(E env, A value) { + return new Env<>(env, value); + } + + /** + * {@inheritDoc} + */ + @Override + public final E ask() { + return this.env; + }; + + /** + * {@inheritDoc} + */ + @Override + public final R asks(Fn1 f) { + return f.apply(this.ask()); + } + + /** + * {@inheritDoc} + */ + @Override + public Env mapEnv(Fn1 f) { + return env(f.apply(this.env), value); + } + + /** + * {@inheritDoc} + */ + @Override + public A extract() { + return this.value; + } + + /** + * {@inheritDoc} + */ + @Override + public >> Env extend(Fn1 f) { + return ComonadEnv.super.extend(f).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public Comonad> extendImpl(Fn1>, ? extends B> f) { + return env(env, f.apply(this)); + } + + /** + * {@inheritDoc} + */ + @Override + public Env fmap(Fn1 fn) { + return env(env, fn.apply(value)); + } + + /** + * {@inheritDoc} + */ + @Override + public Env biMapL(Fn1 fn) { + return mapEnv(fn); + } + + /** + * {@inheritDoc} + */ + @Override + public Env biMapR(Fn1 fn) { + return fmap(fn); + } + + /** + * {@inheritDoc} + */ + @Override + public Env biMap(Fn1 lFn, Fn1 rFn) { + return env(lFn.apply(env), rFn.apply(value)); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean equals(Object other) { + return other instanceof Env && Objects.equals(value, ((Env) other).value) && Objects.equals(env, ((Env) other).env); + } + + /** + * {@inheritDoc} + */ + @Override + public int hashCode() { + return Objects.hash(tuple(env, value)); + } +} 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 dba617294..1732d4497 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 @@ -1,5 +1,6 @@ package com.jnape.palatable.lambda.functor.builtin; +import com.jnape.palatable.lambda.comonad.Comonad; import com.jnape.palatable.lambda.functions.Fn1; import com.jnape.palatable.lambda.functor.Applicative; import com.jnape.palatable.lambda.monad.Monad; @@ -7,12 +8,14 @@ import java.util.Objects; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; + /** * A functor over some value of type A that can be mapped over and retrieved later. * * @param the value type */ -public final class Identity implements Monad>, Traversable> { +public final class Identity implements Monad>, Traversable>, Comonad> { private final A a; @@ -37,6 +40,22 @@ public Identity flatMap(Fn1>> f return f.apply(runIdentity()).coerce(); } + /** + * {@inheritDoc} + */ + @Override + public A extract() { + return a; + } + + /** + * {@inheritDoc} + */ + @Override + public Comonad> extendImpl(Fn1>, ? extends B> f) { + return fmap(constantly(f.apply(this))); + } + /** * {@inheritDoc} */ diff --git a/src/main/java/com/jnape/palatable/lambda/functor/builtin/Store.java b/src/main/java/com/jnape/palatable/lambda/functor/builtin/Store.java new file mode 100644 index 000000000..230eef0c4 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functor/builtin/Store.java @@ -0,0 +1,85 @@ +package com.jnape.palatable.lambda.functor.builtin; + +import com.jnape.palatable.lambda.comonad.Comonad; +import com.jnape.palatable.lambda.comonad.builtin.ComonadStore; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functor.Functor; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; + +/** + * A concrete implementation of the {@link ComonadStore} interface. + * + * @param the store type + * @param the retrieved value type + */ +public final class Store implements Comonad>, ComonadStore> { + private Fn1 storage; + private S cursor; + + private Store(Fn1 f, S s) { + this.storage = f; + this.cursor = s; + } + + /** + * Constructor function for Store + * + * @param f the lookup function from storage to a retrieved value + * @param s the current cursor for looking up a value from f + * @param the type of the storage + * @param the type of the retrieved value + * @return a new instance of Store<S, A> + */ + public static Store store(Fn1 f, S s) { + return new Store<>(f, s); + } + + /** + * {@inheritDoc} + */ + @Override + public final A peeks(Fn1 f) { + return storage.contraMap(f).apply(cursor); + } + + /** + * {@inheritDoc} + */ + @Override + public final Store seeks(Fn1 f) { + return store(storage, f.apply(cursor)); + } + + /** + * {@inheritDoc} + */ + @Override + public > Functor experiment(Fn1> f) { + return f.apply(cursor).fmap(c -> peeks(constantly(c))); + } + + /** + * {@inheritDoc} + */ + @Override + public Store fmap(Fn1 fn) { + return ComonadStore.super.fmap(fn).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public Comonad> extendImpl(Fn1>, ? extends B> f) { + return store(s -> f.apply(store(storage, s)), cursor); + } + + /** + * {@inheritDoc} + */ + @Override + public >> Store extend(Fn1 f) { + return ComonadStore.super.extend(f).coerce(); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functor/builtin/Traced.java b/src/main/java/com/jnape/palatable/lambda/functor/builtin/Traced.java new file mode 100644 index 000000000..01d9ce542 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functor/builtin/Traced.java @@ -0,0 +1,85 @@ +package com.jnape.palatable.lambda.functor.builtin; + +import com.jnape.palatable.lambda.comonad.Comonad; +import com.jnape.palatable.lambda.comonad.builtin.ComonadTraced; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.monoid.Monoid; + +/** + * A concrete implementation of the {@link ComonadTraced} interface. + * + * @param the type of the input to the {@link Traced#trace} function + * @param the {@link Monoid} instance for A + * @param the type of the output to the {@link Traced#trace} function + */ +public final class Traced, B> implements Comonad>, ComonadTraced> { + private Fn1 trace; + private Monoid aMonoid; + + private Traced(Fn1 t, Monoid m) { + this.trace = t; + this.aMonoid = m; + } + + /** + * Constructor function for Traced. + * + * @param t the trace function + * @param m the {@link Monoid} instance for the input to t + * @param the input type of t + * @param the {@link Monoid} type of m + * @param the output type of t + * @return a new instance of Traced<A, M, B> + */ + public static , B> Traced traced(Fn1 t, Monoid m) { + return new Traced<>(t, m); + } + + /** + * {@inheritDoc} + */ + @Override + public final B runTrace(A a) { + return trace.apply(a); + } + + /** + * {@inheritDoc} + */ + @Override + public Monoid getMonoid() { + return aMonoid; + } + + /** + * {@inheritDoc} + */ + @Override + public Comonad> fmap(Fn1 fn) { + return ComonadTraced.super.fmap(fn).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public B extract() { + return trace.apply(aMonoid.identity()); + } + + /** + * {@inheritDoc} + */ + @Override + public Comonad> extendImpl(Fn1>, ? extends C> f) { + return traced(a -> f.apply(traced(trace.diMapL(aMonoid.apply(a)), aMonoid)), aMonoid); + } + + /** + * {@inheritDoc} + */ + @Override + public >> Traced extend(Fn1 f) { + return ComonadTraced.super.extend(f).coerce(); + } +} diff --git a/src/test/java/com/jnape/palatable/lambda/functor/builtin/EnvTest.java b/src/test/java/com/jnape/palatable/lambda/functor/builtin/EnvTest.java new file mode 100644 index 000000000..1199c1b9f --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functor/builtin/EnvTest.java @@ -0,0 +1,19 @@ +package com.jnape.palatable.lambda.functor.builtin; + +import com.jnape.palatable.traitor.annotations.TestTraits; +import com.jnape.palatable.traitor.runners.Traits; +import org.junit.runner.RunWith; +import testsupport.traits.BifunctorLaws; +import testsupport.traits.ComonadLaws; +import testsupport.traits.FunctorLaws; + +import static com.jnape.palatable.lambda.functor.builtin.Env.env; + +@RunWith(Traits.class) +public class EnvTest { + + @TestTraits({FunctorLaws.class, BifunctorLaws.class, ComonadLaws.class}) + public Env testSubject() { + return env(new Object(), new Object()); + } +} diff --git a/src/test/java/com/jnape/palatable/lambda/functor/builtin/IdentityTest.java b/src/test/java/com/jnape/palatable/lambda/functor/builtin/IdentityTest.java index 242a5aaa0..e0a87ebcf 100644 --- a/src/test/java/com/jnape/palatable/lambda/functor/builtin/IdentityTest.java +++ b/src/test/java/com/jnape/palatable/lambda/functor/builtin/IdentityTest.java @@ -3,15 +3,12 @@ import com.jnape.palatable.traitor.annotations.TestTraits; import com.jnape.palatable.traitor.runners.Traits; import org.junit.runner.RunWith; -import testsupport.traits.ApplicativeLaws; -import testsupport.traits.FunctorLaws; -import testsupport.traits.MonadLaws; -import testsupport.traits.TraversableLaws; +import testsupport.traits.*; @RunWith(Traits.class) public class IdentityTest { - @TestTraits({FunctorLaws.class, ApplicativeLaws.class, MonadLaws.class, TraversableLaws.class}) + @TestTraits({FunctorLaws.class, ApplicativeLaws.class, MonadLaws.class, TraversableLaws.class, ComonadLaws.class}) public Identity testSubject() { return new Identity<>(""); } diff --git a/src/test/java/com/jnape/palatable/lambda/functor/builtin/StoreTest.java b/src/test/java/com/jnape/palatable/lambda/functor/builtin/StoreTest.java new file mode 100644 index 000000000..f061fae0b --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functor/builtin/StoreTest.java @@ -0,0 +1,63 @@ +package com.jnape.palatable.lambda.functor.builtin; + +import com.jnape.palatable.lambda.adt.Unit; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.traversable.LambdaIterable; +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.EquatableW; +import testsupport.traits.ComonadLaws; +import testsupport.traits.FunctorLaws; + +import static com.jnape.palatable.lambda.adt.Unit.UNIT; +import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; +import static com.jnape.palatable.lambda.functor.builtin.Store.store; +import static java.util.Arrays.asList; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.Assert.assertEquals; +import static testsupport.matchers.IterableMatcher.iterates; + +@RunWith(Traits.class) +public class StoreTest { + + @TestTraits({FunctorLaws.class, ComonadLaws.class}) + public EquatableW, ?> testSubject() { + Store baseStore = store(id(), UNIT); + return new EquatableW<>(baseStore, store -> tuple(store.extract(), store.peeks(constantly(UNIT)))); + } + + @Test + public void pos() { + Store store = store(x -> x + 2, 1); + Integer i = 3; + assertEquals(i, store.pos()); + } + + @Test + public void peek() { + Store store = store(x -> x + 2, 1); + Integer i = 5; + assertEquals(i, store.peek(3)); + } + + @Test + public void peeks() { + Fn1 integerIntegerFn1 = x -> x + 2; + Store store = store(integerIntegerFn1, 1); + Integer i = -7; + assertEquals(i, store.peeks(x -> x - 10)); + } + + @Test + public void experimentIterable() { + Fn1 integerIntegerFn1 = x -> x + 2; + Store store = store(integerIntegerFn1, 1); + Fn1> f = x -> LambdaIterable.wrap(asList(x + 1, x - 1)); + LambdaIterable exp = store.experiment(f).coerce(); + assertThat(exp.unwrap(), iterates(4,2)); + } +} diff --git a/src/test/java/com/jnape/palatable/lambda/functor/builtin/TracedTest.java b/src/test/java/com/jnape/palatable/lambda/functor/builtin/TracedTest.java new file mode 100644 index 000000000..50c1dfad0 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functor/builtin/TracedTest.java @@ -0,0 +1,65 @@ +package com.jnape.palatable.lambda.functor.builtin; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.builtin.fn1.Id; +import com.jnape.palatable.lambda.functions.specialized.Cokleisli; +import com.jnape.palatable.lambda.monoid.Monoid; +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.EquatableW; +import testsupport.traits.ComonadLaws; +import testsupport.traits.FunctorLaws; + +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.specialized.Cokleisli.cokleisli; +import static com.jnape.palatable.lambda.functor.builtin.Traced.traced; +import static junit.framework.TestCase.assertEquals; + +@RunWith(Traits.class) +public class TracedTest { + private Monoid objectMonoid = Monoid.monoid((o1, o2) -> o1, new Object()); + private Monoid stringMonoid = Monoid.monoid((s1, s2) -> s1 + s2, ""); + private Monoid productMonoid = Monoid.monoid((i1, i2) -> i1 * i2, 1); + + @TestTraits({FunctorLaws.class, ComonadLaws.class}) + public EquatableW, ?>, ?> testSubject() { + Traced, Object> traced = traced(constantly(new Object()), objectMonoid); + return new EquatableW<>(traced, t -> tuple(t.extract(), t.runTrace(new Object()))); + } + + @Test + public void tracedStringEmpty() { + Traced, String> tracer = traced(Id.id(), stringMonoid); + assertEquals(tracer.extract(), ""); + } + + @Test + public void tracedStringAppends() { + Traced, String> tracer = traced(Id.id(), stringMonoid); + Cokleisli, ?>> cokleisli = cokleisli(t2 -> t2.extract() + " world"); + Cokleisli, ?>> cokleisli1 = cokleisli(t1 -> t1.extract() + "hello"); + String applied = cokleisli1.andThen(cokleisli).apply(tracer); + assertEquals(applied, "hello world"); + } + + @Test + public void tracedProductStartsFromOne() { + Traced, Integer> tracer = traced(i -> i + 2, productMonoid); + Integer extract = tracer.extract(); + Integer i = 3; + assertEquals(i, extract); + } + + @Test + public void tracedProductAppendingStartsFromOne() { + Traced, Integer> tracer = traced(i -> i + 1, productMonoid); + Cokleisli, ?>> cokleisli = cokleisli((Fn1, Integer>, Integer>) t1 -> t1.runTrace(2) * 2); + Cokleisli, ?>> cokleisli1 = cokleisli((Fn1, Integer>, Integer>) t2 -> t2.runTrace(3) + 3); + Integer applied = (cokleisli).andThen(cokleisli1).apply(tracer); + Integer i = 17; + assertEquals(i, applied); + } +} diff --git a/src/test/java/testsupport/EquatableW.java b/src/test/java/testsupport/EquatableW.java new file mode 100644 index 000000000..1f3663a55 --- /dev/null +++ b/src/test/java/testsupport/EquatableW.java @@ -0,0 +1,49 @@ +package testsupport; + +import com.jnape.palatable.lambda.comonad.Comonad; +import com.jnape.palatable.lambda.functions.Fn1; + +import java.util.Objects; + +import static com.jnape.palatable.lambda.functions.Fn1.fn1; + +public final class EquatableW, A> implements Comonad> { + + private final Comonad wa; + private final Fn1 equatable; + + public EquatableW(Comonad wa, Fn1 equatable) { + this.wa = wa; + this.equatable = equatable; + } + + @Override + public A extract() { + return wa.extract(); + } + + @Override + public Comonad> extendImpl(Fn1>, ? extends B> f) { + return new EquatableW<>(wa.extendImpl(f.contraMap(fn1(eq -> new EquatableW<>(eq, equatable)))), equatable); + } + + @Override + public EquatableW fmap(Fn1 fn) { + return new EquatableW<>(wa.fmap(fn), equatable); + } + + @Override + @SuppressWarnings("unchecked") + public boolean equals(Object other) { + if (other instanceof EquatableW) { + EquatableW that = (EquatableW) other; + return Objects.equals(equatable.apply((W) wa), that.equatable.apply((W) that.wa)); + } + return false; + } + + @Override + public int hashCode() { + return super.hashCode(); + } +} diff --git a/src/test/java/testsupport/traits/ComonadLaws.java b/src/test/java/testsupport/traits/ComonadLaws.java new file mode 100644 index 000000000..c9fd024f3 --- /dev/null +++ b/src/test/java/testsupport/traits/ComonadLaws.java @@ -0,0 +1,59 @@ +package testsupport.traits; + +import com.jnape.palatable.lambda.adt.Maybe; +import com.jnape.palatable.lambda.comonad.Comonad; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.io.IO; +import com.jnape.palatable.lambda.monoid.builtin.Present; +import com.jnape.palatable.traitor.traits.Trait; + +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.adt.Maybe.nothing; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Upcast.upcast; +import static java.util.Arrays.asList; + +public class ComonadLaws> implements Trait> { + @Override + public void test(Comonad w) { + Present.present((x, y) -> x + "\n\t - " + y) + ., Maybe>>foldMap(f -> f.apply(w), asList( + this::testLeftIdentity, + this::testRightIdentity, + this::testAssociativity, + this::testDuplicate) + ) + .peek(s -> IO.throwing(new AssertionError("The following Comonad laws did not hold for instance of " + + w.getClass() + ": \n\t - " + s))); + } + + private Maybe testLeftIdentity(Comonad w) { + Fn1, ?> fn = Comonad::extract; + return w.extend(fn).equals(w) + ? nothing() + : just("left identity w.extend(wa -> wa.extract()).equals(w)"); + } + + private Maybe testRightIdentity(Comonad w) { + Fn1, Object> fn = constantly(new Object()); + return w.extend(fn).extract().equals(fn.apply(w)) + ? nothing() + : just("right identity: w.extend(f).extract().equals(f.apply(w))"); + } + + private Maybe testAssociativity(Comonad w) { + Fn1, Object> f = constantly(new Object()); + Fn1, Object> g = constantly(new Object()); + return w.extend(f).extend(g).equals(w.extend((Comonad wa) -> g.apply(wa.extend(f).fmap(upcast())))) + ? nothing() + : just("associativity: w.extend(f).extend(g).equals(w.extend(wa -> g(wa.extend(f))))"); + } + + private Maybe testDuplicate(Comonad w) { + Comonad, W> wwa = Comonad.duplicate(w).fmap(wa -> wa.fmap(upcast())); + boolean equals = wwa.extract().equals(w); + return equals + ? nothing() + : just("duplicate: (Comonad.duplicate(w).extract()).equals(w)"); + } +} From 4a0b1f4b1cb3d0dc9d0939cc23cf244d39f00e3b Mon Sep 17 00:00:00 2001 From: Michael A Date: Thu, 30 May 2019 17:15:18 -0500 Subject: [PATCH 02/39] Comonads --- .DS_Store | Bin 0 -> 6148 bytes .../palatable/lambda/comonad/Comonad.java | 90 +++++++++++ .../lambda/comonad/builtin/ComonadEnv.java | 59 ++++++++ .../lambda/comonad/builtin/ComonadStore.java | 87 +++++++++++ .../lambda/comonad/builtin/ComonadTraced.java | 37 +++++ .../functions/specialized/Cokleisli.java | 53 +++++++ .../palatable/lambda/functor/builtin/Env.java | 140 ++++++++++++++++++ .../lambda/functor/builtin/Identity.java | 22 ++- .../lambda/functor/builtin/Store.java | 85 +++++++++++ .../lambda/functor/builtin/Traced.java | 85 +++++++++++ .../lambda/functor/builtin/EnvTest.java | 19 +++ .../lambda/functor/builtin/IdentityTest.java | 8 +- .../lambda/functor/builtin/StoreTest.java | 63 ++++++++ .../lambda/functor/builtin/TracedTest.java | 65 ++++++++ src/test/java/testsupport/EquatableW.java | 49 ++++++ .../java/testsupport/traits/ComonadLaws.java | 60 ++++++++ 16 files changed, 914 insertions(+), 8 deletions(-) create mode 100644 .DS_Store create mode 100644 src/main/java/com/jnape/palatable/lambda/comonad/Comonad.java create mode 100644 src/main/java/com/jnape/palatable/lambda/comonad/builtin/ComonadEnv.java create mode 100644 src/main/java/com/jnape/palatable/lambda/comonad/builtin/ComonadStore.java create mode 100644 src/main/java/com/jnape/palatable/lambda/comonad/builtin/ComonadTraced.java create mode 100644 src/main/java/com/jnape/palatable/lambda/functions/specialized/Cokleisli.java create mode 100644 src/main/java/com/jnape/palatable/lambda/functor/builtin/Env.java create mode 100644 src/main/java/com/jnape/palatable/lambda/functor/builtin/Store.java create mode 100644 src/main/java/com/jnape/palatable/lambda/functor/builtin/Traced.java create mode 100644 src/test/java/com/jnape/palatable/lambda/functor/builtin/EnvTest.java create mode 100644 src/test/java/com/jnape/palatable/lambda/functor/builtin/StoreTest.java create mode 100644 src/test/java/com/jnape/palatable/lambda/functor/builtin/TracedTest.java create mode 100644 src/test/java/testsupport/EquatableW.java create mode 100644 src/test/java/testsupport/traits/ComonadLaws.java diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..de5fc53990f435abad0808330fef4a844df945b2 GIT binary patch literal 6148 zcmeH~F^LL|d(t0@2UW`S!Tku=OfJ-;({to>)nK zk%75DdH(StZPNiy=_3~Vxh+TmDIf);fE17d zD^ehj@!ft!&!k6@0#aZd3i$V-(495eI^)y95F-FNupGv9%o1er0$G!-lNFlf^kCU) zF@|_O+R2jF)nx1J?XVm^EbnYS#n7y`!wM6c)qsK&kOB(@wml#H{6EwGn*SFqN~M4l zcryiTIDMUte5pKJUtZ7am#q4_(aE@+;nz<96F-U%^f2xhUywD~I$5FVM<8TSkOKdz Fz!Qau5~2VA literal 0 HcmV?d00001 diff --git a/src/main/java/com/jnape/palatable/lambda/comonad/Comonad.java b/src/main/java/com/jnape/palatable/lambda/comonad/Comonad.java new file mode 100644 index 000000000..2ea5f88fe --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/comonad/Comonad.java @@ -0,0 +1,90 @@ +package com.jnape.palatable.lambda.comonad; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.builtin.fn1.Downcast; +import com.jnape.palatable.lambda.functor.Functor; +import com.jnape.palatable.lambda.monad.Monad; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; + +/** + * Comonads are {@link Functor}s that support an {@link Comonad#extract()} operation + * to yield a specific value, and an {@link Comonad#extend(Fn1)} which extends + * a function which uses the global state of the Comonad to produce a new local result + * at each point to yield a new Comonad. + *

+ * Comonad laws: + *

    + *
  • left identity: w.extend(wa -> wa.extract()).equals(w)
  • + *
  • right identity: w.extend(f).extract().equals(f.apply(w))
  • + *
  • associativity: w.extend(f).extend(g).equals(w.extend(wa -> g(wa.extend(f))))
  • + *
+ * + * @param the type of the value the Comonad stores + * @param the unification parameter to type-constrain Comonads to themselves (as an upside-down `M`, used for {@link Monad}) + */ +public interface Comonad> extends Functor { + + /** + * Extract an A from the Comonad. Often Comonads feature some sort of cursor, and this will yield the element at the cursor. + * + * @return the current A + */ + A extract(); + + /** + * A version of {@link Comonad#extend} to be implemented by a class extending the interface; {@link Comonad#extend} + * takes care of some of the type inference based off of this method. See {@link Comonad#extend} for details. + * + * @param f the function using the global state Comonad<A, W> to produce a B + * @param the resulting B at each point in the resulting Comonad<B, W> + * @return the new Comonad instance + */ + Comonad extendImpl(Fn1, ? extends B> f); + + /** + * Extend a function Fn<Comonad<A, W>, B> over a Comonad. This allows for computations which use global knowledge to yield a local result. + *

+ * For example, think of blurring an image, where the new pixel relies on the surrounding pixels, or of producing the next step in a generic cellular automaton. + * + * @param f the function using the global state Comonad<A, W> to produce a B + * @param the resulting B at each point in the resulting Comonad<B, W> + * @param the type of the initial Comonad<A, W> + * @return the new Comonad instance + * + * Default implementation in terms of duplicate would be `return this.duplicate().fmap(f);` + * + */ + default > Comonad extend(Fn1 f) { + return extendImpl(f.contraMap(Downcast::>downcast)); + } + + /** + * {@inheritDoc} + */ + @Override + default Comonad fmap(Fn1 fn) { + return extend(wa -> fn.apply(wa.extract())); + } + + /** + * Duplicate a Comonad<A, W> to a Comonad<Comonad<A, W>, W>. + *

+ * Essentially, for the case of a non-empty data structure with a cursor, this produces that data structure with the cursor at all possible locations. + *

+ * It may be worth finding a way to do this lazily, as a Comonad can often be thought of as representing a state space (potentially infinite), with a Monad then used to traverse a specific path through it. + *

+ * `extend` is left to be implemented, and `duplicate` is given a definition based off of it, in symmetry with `Monad`. + * However, it might be better to reverse this for `Comonad`, as there may be a more natural definition for `duplicate` in an implementation for the interface. + *

+ * + * @param w the Comonad to be duplicated + * @param the value type of the Comonad + * @param the Comonad unification parameter + * @return the unfolded Comonad across all possible cursors + */ + static > Comonad, W> duplicate(Comonad w) { + return w.extend(id()); + } +} + diff --git a/src/main/java/com/jnape/palatable/lambda/comonad/builtin/ComonadEnv.java b/src/main/java/com/jnape/palatable/lambda/comonad/builtin/ComonadEnv.java new file mode 100644 index 000000000..df9217e28 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/comonad/builtin/ComonadEnv.java @@ -0,0 +1,59 @@ +package com.jnape.palatable.lambda.comonad.builtin; + +import com.jnape.palatable.lambda.adt.product.Product2; +import com.jnape.palatable.lambda.comonad.Comonad; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functor.builtin.Env; + +/** + * An interface for an Env {@link Comonad}, which provides some additional context through {@link ComonadEnv#ask}. + * In the simplest case, for {@link Env}, this is a {@link Product2} with the additional semantics that {@link Product2#_1} + * is the environment, and {@link Product2#_2} the value extracted from the Comonad. + * + * @param the environment type + * @param the value type + */ +public interface ComonadEnv> extends Comonad, Product2 { + /** + * Retrieve the environment. + * + * @return the environment of type E + */ + E ask(); + + /** + * Retrieve a mapped environment. + * + * @param the return type of the environment + * @param f the mapping function over the environment + * @return the new environment R + */ + default R asks(Fn1 f) { + return f.apply(this.ask()); + } + + /** + * Map the environment type of a ComonadEnv. + * + * @param f the mapping function of the environment + * @param the new environment type + * @return a new instance of a ComonadEnv with environment R and the same value A as previously + */ + ComonadEnv mapEnv(Fn1 f); + + /** + * Retrieve the environment as the first element. + */ + default E _1() { + return ask(); + } + + /** + * Retrieve the comonadic extraction value as the second element. + * + * @return the second element + */ + default A _2() { + return extract(); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/comonad/builtin/ComonadStore.java b/src/main/java/com/jnape/palatable/lambda/comonad/builtin/ComonadStore.java new file mode 100644 index 000000000..98344ae63 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/comonad/builtin/ComonadStore.java @@ -0,0 +1,87 @@ +package com.jnape.palatable.lambda.comonad.builtin; + +import com.jnape.palatable.lambda.comonad.Comonad; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.builtin.fn1.Id; +import com.jnape.palatable.lambda.functor.Functor; +import com.jnape.palatable.lambda.functor.builtin.Store; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Downcast.downcast; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Upcast.upcast; + +/** + * An interface for an Store {@link Comonad}, which can be thought of as providing a lookup function and a current input. + * A concrete class is provided in {@link Store}. + * + * @param the type of the storage and the cursor + * @param the type of the value produced + */ +public interface ComonadStore> extends Comonad { + /** + * Retrieve a value from a mapped cursor. + * + * @param f the relative change to make to the cursor before retrieving a value + * @return a retrieved value A + */ + A peeks(Fn1 f); + + /** + * Retrieve a value from a cursor. + * + * @param s the cursor to extract the value from + * @return a retrieved value A + */ + default A peek(S s) { + return peeks(constantly(s)); + }; + + /** + * Retrieve a value from the current cursor. + * + * @return a retrieved value A + */ + default A pos() { + return peeks(id()); + } + + /** + * Produce a new store by providing a relative change to the cursor. + * + * This can be implemented using the Comonad implementation as: + * return downcast(this.extend((Fn1<WA, A>) s -> s.peeks(f))); + * but is left unimplemented in the interface to prevent conflicts from using `seeks` to implement {@link Comonad#extendImpl}. + * + * @param f the relative change to make to the cursor before retrieving a value + * @return a new ComonadStore with updated cursor + */ + ComonadStore seeks(Fn1 f); + + /** + * Produce a new store at a given cursor. + * + * @param s the cursor to extract the value from + * @return a new ComonadStore with updated cursor + */ + default ComonadStore seek(S s) { + return seeks(constantly(s)); + }; + + /** + * Retrieve a {@link Functor} of value(s) by perturbing the cursor. + * + * @param the functor to be mapped over + * @param f the function for perturbing the cursor + * @return a Functor F of value(s) A + */ + > Functor experiment(Fn1> f); + + /** + * {@inheritDoc} + */ + @Override + default A extract() { + return pos(); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/comonad/builtin/ComonadTraced.java b/src/main/java/com/jnape/palatable/lambda/comonad/builtin/ComonadTraced.java new file mode 100644 index 000000000..886da414d --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/comonad/builtin/ComonadTraced.java @@ -0,0 +1,37 @@ +package com.jnape.palatable.lambda.comonad.builtin; + +import com.jnape.palatable.lambda.comonad.Comonad; +import com.jnape.palatable.lambda.functor.builtin.Traced; +import com.jnape.palatable.lambda.monoid.Monoid; + +/** + * An interface for an Traced {@link Comonad}, which monoidally combines input to a trace function through a {@link Monoid} instance. + * A concrete class is provided in {@link Traced}. + * + * @param the type of the input values to the trace function + * @param the type of the output from the trace function + */ +public interface ComonadTraced, B, W extends Comonad> extends Comonad { + /** + * Run a provided trace function. + * + * @param a the value to provide to the tracer. + * @return the value output by the trace function + */ + B runTrace(A a); + + /** + * Retrieve the {@link Monoid} instance for the input type A. + * + * @return a {@link Monoid} instance for A + */ + Monoid getMonoid(); + + /** + * {@inheritDoc} + */ + @Override + default B extract() { + return runTrace(getMonoid().identity()); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functions/specialized/Cokleisli.java b/src/main/java/com/jnape/palatable/lambda/functions/specialized/Cokleisli.java new file mode 100644 index 000000000..0f171b976 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/specialized/Cokleisli.java @@ -0,0 +1,53 @@ +package com.jnape.palatable.lambda.functions.specialized; + +import com.jnape.palatable.lambda.comonad.Comonad; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.builtin.fn1.Downcast; + +/** + * The Cokleisli arrow of a {@link Comonad}, as a convenience wrapper around a {@link Fn1}<WA, B>. + * This can be thought of as a fixed, portable {@link Comonad#extend(Fn1)}. + * + * @param the extraction value of the input {@link Comonad} + * @param the {@link Comonad} unification parameter + * @param the output argument type + */ +@FunctionalInterface +public interface Cokleisli> extends Fn1, B> { + + /** + * Left-to-right composition of two compatible {@link Cokleisli} arrows, yielding a new {@link Cokleisli} arrow. + * + * @param after the arrow to execute after this one + * @param the new return parameter type + * @return the composition of the two arrows as a new {@link Cokleisli} arrow + */ + default Cokleisli andThen(Cokleisli after) { + return wa -> after.apply(wa.extend(this)); + } + + /** + * Right-to-left composition of two compatible {@link Cokleisli} arrows, yielding a new {@link Cokleisli} arrow. + * + * @param before the arrow to execute before this one + * @param the new input argument type + * @return the composition of the two arrows as a new {@link Cokleisli} arrow + */ + default Cokleisli compose(Cokleisli before) { + return wz -> this.apply(wz.extend(before)); + } + + /** + * Adapt a compatible function into a {@link Cokleisli} arrow. + * + * @param fn the function + * @param the input parameter type + * @param the output argument type + * @param the {@link Comonad} unification parameter + * @param the input {@link Comonad} type + * @return the function adapted as a {@link Cokleisli} arrow + */ + static , WA extends Comonad> Cokleisli cokleisli(Fn1 fn) { + return fn.contraMap((Fn1, ? extends WA>) Downcast::downcast)::apply; + } +} \ No newline at end of file diff --git a/src/main/java/com/jnape/palatable/lambda/functor/builtin/Env.java b/src/main/java/com/jnape/palatable/lambda/functor/builtin/Env.java new file mode 100644 index 000000000..2dabb2912 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functor/builtin/Env.java @@ -0,0 +1,140 @@ +package com.jnape.palatable.lambda.functor.builtin; + +import com.jnape.palatable.lambda.adt.product.Product2; +import com.jnape.palatable.lambda.comonad.Comonad; +import com.jnape.palatable.lambda.comonad.builtin.ComonadEnv; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.builtin.fn1.Id; +import com.jnape.palatable.lambda.functor.Bifunctor; +import com.jnape.palatable.lambda.functor.Functor; + +import java.util.Objects; + +import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; + +/** + * A concrete implementation of the {@link ComonadEnv} interface. + * + * @param the environment type + * @param the value type + */ +public final class Env implements Comonad>, ComonadEnv>, Bifunctor> { + private E env; + private A value; + + private Env(E e, A a) { + this.env = e; + this.value = a; + } + + + /** + * Constructor function for an Env. + * + * @param env the environment provided as context to value + * @param value the primary value to be extracted + * @param the type of the environment + * @param the type of the value + * @return a new instance of Env<E, A> + */ + public static Env env(E env, A value) { + return new Env<>(env, value); + } + + /** + * {@inheritDoc} + */ + @Override + public final E ask() { + return this.env; + }; + + /** + * {@inheritDoc} + */ + @Override + public final R asks(Fn1 f) { + return f.apply(this.ask()); + } + + /** + * {@inheritDoc} + */ + @Override + public Env mapEnv(Fn1 f) { + return env(f.apply(this.env), value); + } + + /** + * {@inheritDoc} + */ + @Override + public A extract() { + return this.value; + } + + /** + * {@inheritDoc} + */ + @Override + public >> Env extend(Fn1 f) { + return ComonadEnv.super.extend(f).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public Comonad> extendImpl(Fn1>, ? extends B> f) { + return env(env, f.apply(this)); + } + + /** + * {@inheritDoc} + */ + @Override + public Env fmap(Fn1 fn) { + return env(env, fn.apply(value)); + } + + /** + * {@inheritDoc} + */ + @Override + public Env biMapL(Fn1 fn) { + return mapEnv(fn); + } + + /** + * {@inheritDoc} + */ + @Override + public Env biMapR(Fn1 fn) { + return fmap(fn); + } + + /** + * {@inheritDoc} + */ + @Override + public Env biMap(Fn1 lFn, Fn1 rFn) { + return env(lFn.apply(env), rFn.apply(value)); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean equals(Object other) { + return other instanceof Env && Objects.equals(value, ((Env) other).value) && Objects.equals(env, ((Env) other).env); + } + + /** + * {@inheritDoc} + */ + @Override + public int hashCode() { + return Objects.hash(tuple(env, value)); + } +} 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 3ee6a0dbb..ba2945c7b 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 @@ -1,5 +1,6 @@ package com.jnape.palatable.lambda.functor.builtin; +import com.jnape.palatable.lambda.comonad.Comonad; import com.jnape.palatable.lambda.functions.Fn1; import com.jnape.palatable.lambda.functions.recursion.RecursiveResult; import com.jnape.palatable.lambda.functions.specialized.Pure; @@ -10,6 +11,7 @@ import java.util.Objects; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; import static com.jnape.palatable.lambda.functions.recursion.Trampoline.trampoline; /** @@ -17,7 +19,7 @@ * * @param the value type */ -public final class Identity implements MonadRec>, Traversable> { +public final class Identity implements MonadRec>, Traversable>, Comonad> { private final A a; @@ -42,6 +44,22 @@ public Identity flatMap(Fn1>> f return f.apply(runIdentity()).coerce(); } + /** + * {@inheritDoc} + */ + @Override + public A extract() { + return a; + } + + /** + * {@inheritDoc} + */ + @Override + public Comonad> extendImpl(Fn1>, ? extends B> f) { + return fmap(constantly(f.apply(this))); + } + /** * {@inheritDoc} */ @@ -108,7 +126,7 @@ AppTrav extends Applicative> AppTrav traverse(Fn1 Identity trampolineM(Fn1, Identity>> fn) { return new Identity<>(trampoline(a -> fn.apply(a).>>coerce().runIdentity(), - runIdentity())); + runIdentity())); } @Override diff --git a/src/main/java/com/jnape/palatable/lambda/functor/builtin/Store.java b/src/main/java/com/jnape/palatable/lambda/functor/builtin/Store.java new file mode 100644 index 000000000..230eef0c4 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functor/builtin/Store.java @@ -0,0 +1,85 @@ +package com.jnape.palatable.lambda.functor.builtin; + +import com.jnape.palatable.lambda.comonad.Comonad; +import com.jnape.palatable.lambda.comonad.builtin.ComonadStore; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functor.Functor; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; + +/** + * A concrete implementation of the {@link ComonadStore} interface. + * + * @param the store type + * @param the retrieved value type + */ +public final class Store implements Comonad>, ComonadStore> { + private Fn1 storage; + private S cursor; + + private Store(Fn1 f, S s) { + this.storage = f; + this.cursor = s; + } + + /** + * Constructor function for Store + * + * @param f the lookup function from storage to a retrieved value + * @param s the current cursor for looking up a value from f + * @param the type of the storage + * @param the type of the retrieved value + * @return a new instance of Store<S, A> + */ + public static Store store(Fn1 f, S s) { + return new Store<>(f, s); + } + + /** + * {@inheritDoc} + */ + @Override + public final A peeks(Fn1 f) { + return storage.contraMap(f).apply(cursor); + } + + /** + * {@inheritDoc} + */ + @Override + public final Store seeks(Fn1 f) { + return store(storage, f.apply(cursor)); + } + + /** + * {@inheritDoc} + */ + @Override + public > Functor experiment(Fn1> f) { + return f.apply(cursor).fmap(c -> peeks(constantly(c))); + } + + /** + * {@inheritDoc} + */ + @Override + public Store fmap(Fn1 fn) { + return ComonadStore.super.fmap(fn).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public Comonad> extendImpl(Fn1>, ? extends B> f) { + return store(s -> f.apply(store(storage, s)), cursor); + } + + /** + * {@inheritDoc} + */ + @Override + public >> Store extend(Fn1 f) { + return ComonadStore.super.extend(f).coerce(); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functor/builtin/Traced.java b/src/main/java/com/jnape/palatable/lambda/functor/builtin/Traced.java new file mode 100644 index 000000000..01d9ce542 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functor/builtin/Traced.java @@ -0,0 +1,85 @@ +package com.jnape.palatable.lambda.functor.builtin; + +import com.jnape.palatable.lambda.comonad.Comonad; +import com.jnape.palatable.lambda.comonad.builtin.ComonadTraced; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.monoid.Monoid; + +/** + * A concrete implementation of the {@link ComonadTraced} interface. + * + * @param the type of the input to the {@link Traced#trace} function + * @param the {@link Monoid} instance for A + * @param the type of the output to the {@link Traced#trace} function + */ +public final class Traced, B> implements Comonad>, ComonadTraced> { + private Fn1 trace; + private Monoid aMonoid; + + private Traced(Fn1 t, Monoid m) { + this.trace = t; + this.aMonoid = m; + } + + /** + * Constructor function for Traced. + * + * @param t the trace function + * @param m the {@link Monoid} instance for the input to t + * @param the input type of t + * @param the {@link Monoid} type of m + * @param the output type of t + * @return a new instance of Traced<A, M, B> + */ + public static , B> Traced traced(Fn1 t, Monoid m) { + return new Traced<>(t, m); + } + + /** + * {@inheritDoc} + */ + @Override + public final B runTrace(A a) { + return trace.apply(a); + } + + /** + * {@inheritDoc} + */ + @Override + public Monoid getMonoid() { + return aMonoid; + } + + /** + * {@inheritDoc} + */ + @Override + public Comonad> fmap(Fn1 fn) { + return ComonadTraced.super.fmap(fn).coerce(); + } + + /** + * {@inheritDoc} + */ + @Override + public B extract() { + return trace.apply(aMonoid.identity()); + } + + /** + * {@inheritDoc} + */ + @Override + public Comonad> extendImpl(Fn1>, ? extends C> f) { + return traced(a -> f.apply(traced(trace.diMapL(aMonoid.apply(a)), aMonoid)), aMonoid); + } + + /** + * {@inheritDoc} + */ + @Override + public >> Traced extend(Fn1 f) { + return ComonadTraced.super.extend(f).coerce(); + } +} diff --git a/src/test/java/com/jnape/palatable/lambda/functor/builtin/EnvTest.java b/src/test/java/com/jnape/palatable/lambda/functor/builtin/EnvTest.java new file mode 100644 index 000000000..1199c1b9f --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functor/builtin/EnvTest.java @@ -0,0 +1,19 @@ +package com.jnape.palatable.lambda.functor.builtin; + +import com.jnape.palatable.traitor.annotations.TestTraits; +import com.jnape.palatable.traitor.runners.Traits; +import org.junit.runner.RunWith; +import testsupport.traits.BifunctorLaws; +import testsupport.traits.ComonadLaws; +import testsupport.traits.FunctorLaws; + +import static com.jnape.palatable.lambda.functor.builtin.Env.env; + +@RunWith(Traits.class) +public class EnvTest { + + @TestTraits({FunctorLaws.class, BifunctorLaws.class, ComonadLaws.class}) + public Env testSubject() { + return env(new Object(), new Object()); + } +} diff --git a/src/test/java/com/jnape/palatable/lambda/functor/builtin/IdentityTest.java b/src/test/java/com/jnape/palatable/lambda/functor/builtin/IdentityTest.java index 80e48fc15..c6bcab970 100644 --- a/src/test/java/com/jnape/palatable/lambda/functor/builtin/IdentityTest.java +++ b/src/test/java/com/jnape/palatable/lambda/functor/builtin/IdentityTest.java @@ -4,11 +4,7 @@ import com.jnape.palatable.traitor.runners.Traits; import org.junit.Test; import org.junit.runner.RunWith; -import testsupport.traits.ApplicativeLaws; -import testsupport.traits.FunctorLaws; -import testsupport.traits.MonadLaws; -import testsupport.traits.MonadRecLaws; -import testsupport.traits.TraversableLaws; +import testsupport.traits.*; import static com.jnape.palatable.lambda.functor.builtin.Identity.pureIdentity; import static org.junit.Assert.assertEquals; @@ -16,7 +12,7 @@ @RunWith(Traits.class) public class IdentityTest { - @TestTraits({FunctorLaws.class, ApplicativeLaws.class, MonadLaws.class, TraversableLaws.class, MonadRecLaws.class}) + @TestTraits({FunctorLaws.class, ApplicativeLaws.class, MonadLaws.class, TraversableLaws.class, MonadRecLaws.class, ComonadLaws.class}) public Identity testSubject() { return new Identity<>(""); } diff --git a/src/test/java/com/jnape/palatable/lambda/functor/builtin/StoreTest.java b/src/test/java/com/jnape/palatable/lambda/functor/builtin/StoreTest.java new file mode 100644 index 000000000..f061fae0b --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functor/builtin/StoreTest.java @@ -0,0 +1,63 @@ +package com.jnape.palatable.lambda.functor.builtin; + +import com.jnape.palatable.lambda.adt.Unit; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.traversable.LambdaIterable; +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.EquatableW; +import testsupport.traits.ComonadLaws; +import testsupport.traits.FunctorLaws; + +import static com.jnape.palatable.lambda.adt.Unit.UNIT; +import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; +import static com.jnape.palatable.lambda.functor.builtin.Store.store; +import static java.util.Arrays.asList; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.Assert.assertEquals; +import static testsupport.matchers.IterableMatcher.iterates; + +@RunWith(Traits.class) +public class StoreTest { + + @TestTraits({FunctorLaws.class, ComonadLaws.class}) + public EquatableW, ?> testSubject() { + Store baseStore = store(id(), UNIT); + return new EquatableW<>(baseStore, store -> tuple(store.extract(), store.peeks(constantly(UNIT)))); + } + + @Test + public void pos() { + Store store = store(x -> x + 2, 1); + Integer i = 3; + assertEquals(i, store.pos()); + } + + @Test + public void peek() { + Store store = store(x -> x + 2, 1); + Integer i = 5; + assertEquals(i, store.peek(3)); + } + + @Test + public void peeks() { + Fn1 integerIntegerFn1 = x -> x + 2; + Store store = store(integerIntegerFn1, 1); + Integer i = -7; + assertEquals(i, store.peeks(x -> x - 10)); + } + + @Test + public void experimentIterable() { + Fn1 integerIntegerFn1 = x -> x + 2; + Store store = store(integerIntegerFn1, 1); + Fn1> f = x -> LambdaIterable.wrap(asList(x + 1, x - 1)); + LambdaIterable exp = store.experiment(f).coerce(); + assertThat(exp.unwrap(), iterates(4,2)); + } +} diff --git a/src/test/java/com/jnape/palatable/lambda/functor/builtin/TracedTest.java b/src/test/java/com/jnape/palatable/lambda/functor/builtin/TracedTest.java new file mode 100644 index 000000000..50c1dfad0 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functor/builtin/TracedTest.java @@ -0,0 +1,65 @@ +package com.jnape.palatable.lambda.functor.builtin; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.builtin.fn1.Id; +import com.jnape.palatable.lambda.functions.specialized.Cokleisli; +import com.jnape.palatable.lambda.monoid.Monoid; +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.EquatableW; +import testsupport.traits.ComonadLaws; +import testsupport.traits.FunctorLaws; + +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.specialized.Cokleisli.cokleisli; +import static com.jnape.palatable.lambda.functor.builtin.Traced.traced; +import static junit.framework.TestCase.assertEquals; + +@RunWith(Traits.class) +public class TracedTest { + private Monoid objectMonoid = Monoid.monoid((o1, o2) -> o1, new Object()); + private Monoid stringMonoid = Monoid.monoid((s1, s2) -> s1 + s2, ""); + private Monoid productMonoid = Monoid.monoid((i1, i2) -> i1 * i2, 1); + + @TestTraits({FunctorLaws.class, ComonadLaws.class}) + public EquatableW, ?>, ?> testSubject() { + Traced, Object> traced = traced(constantly(new Object()), objectMonoid); + return new EquatableW<>(traced, t -> tuple(t.extract(), t.runTrace(new Object()))); + } + + @Test + public void tracedStringEmpty() { + Traced, String> tracer = traced(Id.id(), stringMonoid); + assertEquals(tracer.extract(), ""); + } + + @Test + public void tracedStringAppends() { + Traced, String> tracer = traced(Id.id(), stringMonoid); + Cokleisli, ?>> cokleisli = cokleisli(t2 -> t2.extract() + " world"); + Cokleisli, ?>> cokleisli1 = cokleisli(t1 -> t1.extract() + "hello"); + String applied = cokleisli1.andThen(cokleisli).apply(tracer); + assertEquals(applied, "hello world"); + } + + @Test + public void tracedProductStartsFromOne() { + Traced, Integer> tracer = traced(i -> i + 2, productMonoid); + Integer extract = tracer.extract(); + Integer i = 3; + assertEquals(i, extract); + } + + @Test + public void tracedProductAppendingStartsFromOne() { + Traced, Integer> tracer = traced(i -> i + 1, productMonoid); + Cokleisli, ?>> cokleisli = cokleisli((Fn1, Integer>, Integer>) t1 -> t1.runTrace(2) * 2); + Cokleisli, ?>> cokleisli1 = cokleisli((Fn1, Integer>, Integer>) t2 -> t2.runTrace(3) + 3); + Integer applied = (cokleisli).andThen(cokleisli1).apply(tracer); + Integer i = 17; + assertEquals(i, applied); + } +} diff --git a/src/test/java/testsupport/EquatableW.java b/src/test/java/testsupport/EquatableW.java new file mode 100644 index 000000000..1f3663a55 --- /dev/null +++ b/src/test/java/testsupport/EquatableW.java @@ -0,0 +1,49 @@ +package testsupport; + +import com.jnape.palatable.lambda.comonad.Comonad; +import com.jnape.palatable.lambda.functions.Fn1; + +import java.util.Objects; + +import static com.jnape.palatable.lambda.functions.Fn1.fn1; + +public final class EquatableW, A> implements Comonad> { + + private final Comonad wa; + private final Fn1 equatable; + + public EquatableW(Comonad wa, Fn1 equatable) { + this.wa = wa; + this.equatable = equatable; + } + + @Override + public A extract() { + return wa.extract(); + } + + @Override + public Comonad> extendImpl(Fn1>, ? extends B> f) { + return new EquatableW<>(wa.extendImpl(f.contraMap(fn1(eq -> new EquatableW<>(eq, equatable)))), equatable); + } + + @Override + public EquatableW fmap(Fn1 fn) { + return new EquatableW<>(wa.fmap(fn), equatable); + } + + @Override + @SuppressWarnings("unchecked") + public boolean equals(Object other) { + if (other instanceof EquatableW) { + EquatableW that = (EquatableW) other; + return Objects.equals(equatable.apply((W) wa), that.equatable.apply((W) that.wa)); + } + return false; + } + + @Override + public int hashCode() { + return super.hashCode(); + } +} diff --git a/src/test/java/testsupport/traits/ComonadLaws.java b/src/test/java/testsupport/traits/ComonadLaws.java new file mode 100644 index 000000000..06fde6e1f --- /dev/null +++ b/src/test/java/testsupport/traits/ComonadLaws.java @@ -0,0 +1,60 @@ +package testsupport.traits; + +import com.jnape.palatable.lambda.adt.Maybe; +import com.jnape.palatable.lambda.comonad.Comonad; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.io.IO; +import com.jnape.palatable.lambda.monoid.builtin.Present; +import com.jnape.palatable.traitor.traits.Trait; + +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.adt.Maybe.nothing; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Upcast.upcast; +import static java.util.Arrays.asList; + +public class ComonadLaws> implements Trait> { + @Override + public void test(Comonad w) { + Present.present((x, y) -> x + "\n\t - " + y) + ., Maybe>>foldMap(f -> f.apply(w), asList( + this::testLeftIdentity, + this::testRightIdentity, + this::testAssociativity, + this::testDuplicate) + ) + .traverse(s -> IO.throwing(new AssertionError("The following Comonad laws did not hold for instance of " + + w.getClass() + ": \n\t - " + s)), IO::io) + .unsafePerformIO(); + } + + private Maybe testLeftIdentity(Comonad w) { + Fn1, ?> fn = Comonad::extract; + return w.extend(fn).equals(w) + ? nothing() + : just("left identity w.extend(wa -> wa.extract()).equals(w)"); + } + + private Maybe testRightIdentity(Comonad w) { + Fn1, Object> fn = constantly(new Object()); + return w.extend(fn).extract().equals(fn.apply(w)) + ? nothing() + : just("right identity: w.extend(f).extract().equals(f.apply(w))"); + } + + private Maybe testAssociativity(Comonad w) { + Fn1, Object> f = constantly(new Object()); + Fn1, Object> g = constantly(new Object()); + return w.extend(f).extend(g).equals(w.extend((Comonad wa) -> g.apply(wa.extend(f).fmap(upcast())))) + ? nothing() + : just("associativity: w.extend(f).extend(g).equals(w.extend(wa -> g(wa.extend(f))))"); + } + + private Maybe testDuplicate(Comonad w) { + Comonad, W> wwa = Comonad.duplicate(w).fmap(wa -> wa.fmap(upcast())); + boolean equals = wwa.extract().equals(w); + return equals + ? nothing() + : just("duplicate: (Comonad.duplicate(w).extract()).equals(w)"); + } +} From f0caec4be5855eb2d4e6749cb8c50ab12b81a516 Mon Sep 17 00:00:00 2001 From: jnape Date: Tue, 10 Dec 2019 14:19:11 -0600 Subject: [PATCH 03/39] Adding MergeHMaps, a Monoid merging HMaps by using key-specific semigroups --- CHANGELOG.md | 3 + .../lambda/monoid/builtin/MergeHMaps.java | 81 +++++++++++++++++++ .../lambda/monoid/builtin/MergeHMapsTest.java | 62 ++++++++++++++ 3 files changed, 146 insertions(+) create mode 100644 src/main/java/com/jnape/palatable/lambda/monoid/builtin/MergeHMaps.java create mode 100644 src/test/java/com/jnape/palatable/lambda/monoid/builtin/MergeHMapsTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 0d82489f9..488f8b414 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,9 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/). ### Changed - `HList#cons` static factory method auto-promotes to specialized `HList` if there is one +### Added +- `MergeHMaps`, a `Monoid` that merges `HMap`s by merging the values via key-specified `Semigroup`s + ## [5.1.0] - 2019-10-13 ### Changed - All monad transformers that can support composable parallelism do support it diff --git a/src/main/java/com/jnape/palatable/lambda/monoid/builtin/MergeHMaps.java b/src/main/java/com/jnape/palatable/lambda/monoid/builtin/MergeHMaps.java new file mode 100644 index 000000000..fc2f28c95 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/monoid/builtin/MergeHMaps.java @@ -0,0 +1,81 @@ +package com.jnape.palatable.lambda.monoid.builtin; + +import com.jnape.palatable.lambda.adt.Maybe; +import com.jnape.palatable.lambda.adt.hmap.HMap; +import com.jnape.palatable.lambda.adt.hmap.TypeSafeKey; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.Fn2; +import com.jnape.palatable.lambda.functions.builtin.fn3.FoldLeft; +import com.jnape.palatable.lambda.monoid.Monoid; +import com.jnape.palatable.lambda.semigroup.Semigroup; + +import java.util.Map; + +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.adt.Maybe.maybe; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Map.map; +import static com.jnape.palatable.lambda.monoid.builtin.Last.last; +import static com.jnape.palatable.lambda.monoid.builtin.Present.present; +import static com.jnape.palatable.lambda.optics.functions.Set.set; +import static com.jnape.palatable.lambda.optics.lenses.MapLens.valueAt; +import static java.util.Arrays.asList; +import static java.util.Collections.emptyMap; + +/** + * A {@link Monoid} instance formed by merging {@link HMap HMaps} using the chosen + * {@link TypeSafeKey} -> {@link Semigroup} + * {@link MergeHMaps#key(TypeSafeKey, Semigroup) mappings}, defaulting to {@link Last} in case no + * {@link Semigroup} has been chosen for a given {@link TypeSafeKey}. + */ +public final class MergeHMaps implements Monoid { + + private final Map, Fn2> bindings; + private final Φ> defaultBinding; + + private MergeHMaps(Map, Fn2> bindings, + Φ> defaultBinding) { + this.bindings = bindings; + this.defaultBinding = defaultBinding; + } + + public MergeHMaps key(TypeSafeKey key, Semigroup semigroup) { + return new MergeHMaps(set(valueAt(key), just(merge(key, present(semigroup))), bindings), defaultBinding); + } + + @Override + public HMap identity() { + return HMap.emptyHMap(); + } + + @Override + public HMap checkedApply(HMap x, HMap y) throws Throwable { + return reduceLeft(asList(x, y)); + } + + @Override + public HMap foldMap(Fn1 fn, Iterable bs) { + return FoldLeft.foldLeft((acc, m) -> FoldLeft.foldLeft((result, k) -> maybe(bindings.get(k)) + .orElseGet(() -> defaultBinding.eliminate(k)) + .apply(result, m), acc, m.keys()), identity(), map(fn, bs)); + } + + public static MergeHMaps mergeHMaps() { + return new MergeHMaps(emptyMap(), new Φ>() { + @Override + public Fn2 eliminate(TypeSafeKey key) { + return merge(key, last()); + } + }); + } + + private static Fn2 merge(TypeSafeKey key, Semigroup> semigroup) { + return (x, y) -> semigroup.apply(x.get(key), y.get(key)) + .fmap(a -> x.put(key, a)) + .orElse(x); + } + + @SuppressWarnings({"NonAsciiCharacters"}) + private interface Φ { + R eliminate(TypeSafeKey key); + } +} diff --git a/src/test/java/com/jnape/palatable/lambda/monoid/builtin/MergeHMapsTest.java b/src/test/java/com/jnape/palatable/lambda/monoid/builtin/MergeHMapsTest.java new file mode 100644 index 000000000..0bf2d978b --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/monoid/builtin/MergeHMapsTest.java @@ -0,0 +1,62 @@ +package com.jnape.palatable.lambda.monoid.builtin; + +import com.jnape.palatable.lambda.adt.hmap.TypeSafeKey; +import org.junit.Test; + +import static com.jnape.palatable.lambda.adt.hmap.HMap.emptyHMap; +import static com.jnape.palatable.lambda.adt.hmap.HMap.hMap; +import static com.jnape.palatable.lambda.adt.hmap.HMap.singletonHMap; +import static com.jnape.palatable.lambda.adt.hmap.TypeSafeKey.typeSafeKey; +import static com.jnape.palatable.lambda.monoid.builtin.Join.join; +import static com.jnape.palatable.lambda.monoid.builtin.MergeHMaps.mergeHMaps; +import static java.util.Arrays.asList; +import static org.junit.Assert.assertEquals; + +public class MergeHMapsTest { + + @Test + public void allKeysAccountedFor() { + TypeSafeKey.Simple stringKey = typeSafeKey(); + MergeHMaps mergeHMaps = mergeHMaps().key(stringKey, join()); + + assertEquals(emptyHMap(), mergeHMaps.apply(emptyHMap(), emptyHMap())); + assertEquals(singletonHMap(stringKey, "foo"), + mergeHMaps.apply(singletonHMap(stringKey, "foo"), emptyHMap())); + assertEquals(singletonHMap(stringKey, "foobar"), + mergeHMaps.apply(singletonHMap(stringKey, "foo"), + singletonHMap(stringKey, "bar"))); + } + + @Test + public void unaccountedForKeyUsesLastByDefault() { + TypeSafeKey.Simple stringKey = typeSafeKey(); + + assertEquals(singletonHMap(stringKey, "foo"), + mergeHMaps().apply(singletonHMap(stringKey, "foo"), emptyHMap())); + assertEquals(singletonHMap(stringKey, "bar"), + mergeHMaps().apply(emptyHMap(), singletonHMap(stringKey, "bar"))); + assertEquals(singletonHMap(stringKey, "bar"), + mergeHMaps().apply(singletonHMap(stringKey, "foo"), singletonHMap(stringKey, "bar"))); + } + + @Test + public void sparseKeysAcrossMaps() { + TypeSafeKey.Simple stringKey = typeSafeKey(); + TypeSafeKey.Simple intKey = typeSafeKey(); + TypeSafeKey.Simple boolKey = typeSafeKey(); + + MergeHMaps mergeHMaps = mergeHMaps() + .key(stringKey, join()) + .key(intKey, Integer::sum); + + assertEquals(hMap(stringKey, "foobar", + intKey, 3, + boolKey, false), + mergeHMaps.reduceLeft(asList(singletonHMap(stringKey, "foo"), + singletonHMap(intKey, 1), + singletonHMap(boolKey, true), + hMap(stringKey, "bar", + intKey, 2, + boolKey, false)))); + } +} \ No newline at end of file From 2cf5cf46000e668c63212bc90969db563692d363 Mon Sep 17 00:00:00 2001 From: John Napier Date: Wed, 1 Jan 2020 18:16:08 -0600 Subject: [PATCH 04/39] Shameless self-promotion button --- .github/FUNDING.yml | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 .github/FUNDING.yml diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 000000000..66b54c05c --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,12 @@ +# These are supported funding model platforms + +github: [jnape] +patreon: # Replace with a single Patreon username +open_collective: # Replace with a single Open Collective username +ko_fi: # Replace with a single Ko-fi username +tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel +community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry +liberapay: # Replace with a single Liberapay username +issuehunt: # Replace with a single IssueHunt username +otechie: # Replace with a single Otechie username +custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] From 2633e790296b2b1ce0951193b02466bcca9fa81c Mon Sep 17 00:00:00 2001 From: jnape Date: Sun, 26 Jan 2020 15:30:05 -0600 Subject: [PATCH 05/39] Javadoc --- src/main/java/com/jnape/palatable/lambda/functions/Fn1.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/jnape/palatable/lambda/functions/Fn1.java b/src/main/java/com/jnape/palatable/lambda/functions/Fn1.java index 3f2be1e92..f21e70e23 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/Fn1.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/Fn1.java @@ -123,7 +123,7 @@ default Fn1 flatMap(Fn1>> f) { } /** - * Also left-to-right composition (sadly). + * Left-to-right composition. * * @param the return type of the next function to invoke * @param f the function to invoke with this function's return value From b38a83a7fda2427218dc327f1239a5b4f2d0fa38 Mon Sep 17 00:00:00 2001 From: jnape Date: Sat, 8 Feb 2020 16:14:50 -0600 Subject: [PATCH 06/39] Mockito is a test dependency; missed this in a previous release --- pom.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/pom.xml b/pom.xml index c94448fa2..5f84c2dd2 100644 --- a/pom.xml +++ b/pom.xml @@ -74,6 +74,7 @@ org.mockito mockito-core 2.28.2 + test com.jnape.palatable From 0ef288ee936778f4cf22837791a14248f229cc6c Mon Sep 17 00:00:00 2001 From: Alexander Bandukwala <7h3kk1d@gmail.com> Date: Thu, 24 Oct 2019 11:57:03 -0500 Subject: [PATCH 07/39] Add utility functions for interoperability with java.util.Comparator - Add ComparisonRelation to encapsulate Comparator/Comparable#compare semantics - Add Compare to abstract over Comparator#compare using ComparisonRelation - Add comparator compatible functions (CmpEqWith, GTEWith, GTWith, LTEWith, LTWith) --- .../lambda/functions/builtin/fn3/CmpEqBy.java | 5 +- .../functions/builtin/fn3/CmpEqWith.java | 62 +++++++++++++ .../lambda/functions/builtin/fn3/Compare.java | 56 ++++++++++++ .../lambda/functions/builtin/fn3/GTBy.java | 5 +- .../lambda/functions/builtin/fn3/GTEBy.java | 6 +- .../lambda/functions/builtin/fn3/GTEWith.java | 62 +++++++++++++ .../lambda/functions/builtin/fn3/GTWith.java | 62 +++++++++++++ .../lambda/functions/builtin/fn3/LTBy.java | 4 +- .../lambda/functions/builtin/fn3/LTEBy.java | 5 +- .../lambda/functions/builtin/fn3/LTEWith.java | 62 +++++++++++++ .../lambda/functions/builtin/fn3/LTWith.java | 62 +++++++++++++ .../ordering/ComparisonRelation.java | 89 +++++++++++++++++++ .../lambda/semigroup/builtin/MaxBy.java | 1 + .../lambda/semigroup/builtin/MaxWith.java | 52 +++++++++++ .../lambda/semigroup/builtin/MinWith.java | 52 +++++++++++ .../functions/builtin/fn3/CmpEqWithTest.java | 19 ++++ .../functions/builtin/fn3/CompareTest.java | 19 ++++ .../functions/builtin/fn3/GTEWithTest.java | 21 +++++ .../functions/builtin/fn3/GTWithTest.java | 20 +++++ .../functions/builtin/fn3/LTEWithTest.java | 21 +++++ .../functions/builtin/fn3/LTWithTest.java | 20 +++++ .../ordering/ComparisonRelationTest.java | 23 +++++ .../lambda/semigroup/builtin/MaxWithTest.java | 21 +++++ .../lambda/semigroup/builtin/MinWithTest.java | 21 +++++ 24 files changed, 763 insertions(+), 7 deletions(-) create mode 100644 src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/CmpEqWith.java create mode 100644 src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/Compare.java create mode 100644 src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/GTEWith.java create mode 100644 src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/GTWith.java create mode 100644 src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/LTEWith.java create mode 100644 src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/LTWith.java create mode 100644 src/main/java/com/jnape/palatable/lambda/functions/ordering/ComparisonRelation.java create mode 100644 src/main/java/com/jnape/palatable/lambda/semigroup/builtin/MaxWith.java create mode 100644 src/main/java/com/jnape/palatable/lambda/semigroup/builtin/MinWith.java create mode 100644 src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/CmpEqWithTest.java create mode 100644 src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/CompareTest.java create mode 100644 src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/GTEWithTest.java create mode 100644 src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/GTWithTest.java create mode 100644 src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/LTEWithTest.java create mode 100644 src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/LTWithTest.java create mode 100644 src/test/java/com/jnape/palatable/lambda/functions/ordering/ComparisonRelationTest.java create mode 100644 src/test/java/com/jnape/palatable/lambda/semigroup/builtin/MaxWithTest.java create mode 100644 src/test/java/com/jnape/palatable/lambda/semigroup/builtin/MinWithTest.java diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/CmpEqBy.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/CmpEqBy.java index 3b483aeaf..a6e04baa9 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/CmpEqBy.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/CmpEqBy.java @@ -6,7 +6,9 @@ import com.jnape.palatable.lambda.functions.specialized.BiPredicate; import com.jnape.palatable.lambda.functions.specialized.Predicate; +import static com.jnape.palatable.lambda.functions.builtin.fn3.CmpEqWith.cmpEqWith; import static com.jnape.palatable.lambda.functions.specialized.Predicate.predicate; +import static java.util.Comparator.comparing; /** * Given a mapping function from some type A to some {@link Comparable} type B and two values @@ -16,6 +18,7 @@ * @param the value type * @param the mapped comparison type * @see CmpEq + * @see CmpEqWith * @see LTBy * @see GTBy */ @@ -28,7 +31,7 @@ private CmpEqBy() { @Override public Boolean checkedApply(Fn1 compareFn, A x, A y) { - return compareFn.apply(x).compareTo(compareFn.apply(y)) == 0; + return cmpEqWith(comparing(compareFn.toFunction()), x, y); } @Override diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/CmpEqWith.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/CmpEqWith.java new file mode 100644 index 000000000..0a0c1f1ce --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/CmpEqWith.java @@ -0,0 +1,62 @@ +package com.jnape.palatable.lambda.functions.builtin.fn3; + +import com.jnape.palatable.lambda.functions.Fn3; +import com.jnape.palatable.lambda.functions.specialized.BiPredicate; +import com.jnape.palatable.lambda.functions.specialized.Predicate; + +import java.util.Comparator; + +import static com.jnape.palatable.lambda.functions.builtin.fn3.Compare.compare; +import static com.jnape.palatable.lambda.functions.ordering.ComparisonRelation.equal; +import static com.jnape.palatable.lambda.functions.specialized.Predicate.predicate; + +/** + * Given a {@link Comparator} from some type A and two values of type A, return true + * if the first value is strictly equal to the second value (according to {@link Comparator#compare(A, A)} + * otherwise, return false. + * + * @param the value type + * @see CmpEqBy + * @see LTBy + * @see GTBy + * @see Compare + */ +public final class CmpEqWith implements Fn3, A, A, Boolean> { + + private static final CmpEqWith INSTANCE = new CmpEqWith<>(); + + private CmpEqWith() { + } + + @Override + public BiPredicate apply(Comparator compareFn) { + return Fn3.super.apply(compareFn)::apply; + } + + @Override + public Predicate apply(Comparator compareFn, A x) { + return predicate(Fn3.super.apply(compareFn, x)); + } + + @SuppressWarnings("unchecked") + public static CmpEqWith cmpEqWith() { + return (CmpEqWith) INSTANCE; + } + + public static BiPredicate cmpEqWith(Comparator comparator) { + return CmpEqWith.cmpEqWith().apply(comparator); + } + + public static Predicate cmpEqWith(Comparator comparator, A x) { + return CmpEqWith.cmpEqWith(comparator).apply(x); + } + + public static Boolean cmpEqWith(Comparator comparator, A x, A y) { + return cmpEqWith(comparator, x).apply(y); + } + + @Override + public Boolean checkedApply(Comparator comparator, A a, A a2) { + return compare(comparator, a, a2).equals(equal()); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/Compare.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/Compare.java new file mode 100644 index 000000000..4f0bea533 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/Compare.java @@ -0,0 +1,56 @@ +package com.jnape.palatable.lambda.functions.builtin.fn3; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.Fn2; +import com.jnape.palatable.lambda.functions.Fn3; +import com.jnape.palatable.lambda.functions.ordering.ComparisonRelation; + +import java.util.Comparator; + +/** + * Given a {@link Comparator} from some type A and two values of type A, return a + * {@link ComparisonRelation} of the first value with reference to the second value (according to {@link Comparator#compare(A, A)}. + * The order of parameters is flipped with respect to {@link Comparator#compare(A, A)} for more idiomatic partial application. + * + *

+ * Example: + *

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

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

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


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

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

* Example: *

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

* * @param
the value type * @see Comparator @@ -30,7 +29,8 @@ public final class Compare implements Fn3, A, A, ComparisonRelation> { private static final Compare INSTANCE = new Compare<>(); - private Compare() { } + private Compare() { + } @Override public ComparisonRelation checkedApply(Comparator aComparator, A a, A a2) throws Throwable { diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/GTEWith.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/GTEWith.java index 7a3c8cc6d..29a5dfe82 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/GTEWith.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/GTEWith.java @@ -13,7 +13,7 @@ /** * Given a {@link Comparator} from some type A and two values of type A, * return true if the second value is greater than or equal to the first value in - * terms of their mapped B results according to {@link Comparator#compare(A, A)}; + * terms of their mapped B results according to {@link Comparator#compare(Object, Object)}; * otherwise, return false. * * @param the value type @@ -38,6 +38,11 @@ public Predicate apply(Comparator compareFn, A x) { return predicate(Fn3.super.apply(compareFn, x)); } + @Override + public Boolean checkedApply(Comparator comparator, A a, A a2) { + return !ltWith(comparator, a, a2); + } + @SuppressWarnings("unchecked") public static GTEWith gteWith() { return (GTEWith) INSTANCE; @@ -54,9 +59,4 @@ public static Predicate gteWith(Comparator comparator, A y) { public static Boolean gteWith(Comparator comparator, A y, A x) { return gteWith(comparator, y).apply(x); } - - @Override - public Boolean checkedApply(Comparator comparator, A a, A a2) { - return !ltWith(comparator, a, a2); - } } diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/LTEWith.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/LTEWith.java index 06e621d3f..403f5c15f 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/LTEWith.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/LTEWith.java @@ -13,7 +13,7 @@ /** * Given a {@link Comparator} from some type A and two values of type A, * return true if the second value is less than or equal to the first value in - * terms of their mapped B results according to {@link Comparator#compare(A, A)}; + * terms of their mapped B results according to {@link Comparator#compare(Object, Object)}; * otherwise, return false. * * @param the value type @@ -38,6 +38,11 @@ public Predicate apply(Comparator compareFn, A x) { return predicate(Fn3.super.apply(compareFn, x)); } + @Override + public Boolean checkedApply(Comparator comparator, A a, A a2) { + return !gtWith(comparator, a, a2); + } + @SuppressWarnings("unchecked") public static LTEWith lteWith() { return (LTEWith) INSTANCE; @@ -54,9 +59,4 @@ public static Predicate lteWith(Comparator comparator, A y) { public static Boolean lteWith(Comparator comparator, A y, A x) { return lteWith(comparator, y).apply(x); } - - @Override - public Boolean checkedApply(Comparator comparator, A a, A a2) { - return !gtWith(comparator, a, a2); - } } diff --git a/src/main/java/com/jnape/palatable/lambda/functor/builtin/Writer.java b/src/main/java/com/jnape/palatable/lambda/functor/builtin/Writer.java index bb00cb402..c4c6d752d 100644 --- a/src/main/java/com/jnape/palatable/lambda/functor/builtin/Writer.java +++ b/src/main/java/com/jnape/palatable/lambda/functor/builtin/Writer.java @@ -136,6 +136,7 @@ public Writer discardR(Applicative> appB) { /** * Construct a {@link Writer} from an accumulation. * + * @param w the accumulation * @param the accumulation type * @return the {@link Writer} */ @@ -146,6 +147,7 @@ public static Writer tell(W w) { /** * Construct a {@link Writer} from a value. * + * @param a the output value * @param the accumulation type * @param the value type * @return the {@link Writer} @@ -157,6 +159,7 @@ public static Writer listen(A a) { /** * Construct a {@link Writer} from an accumulation and a value. * + * @param aw the output value and accumulation * @param the accumulation type * @param the value type * @return the {@link WriterT} diff --git a/src/main/java/com/jnape/palatable/lambda/monad/transformer/builtin/IterateT.java b/src/main/java/com/jnape/palatable/lambda/monad/transformer/builtin/IterateT.java index 1b85133e1..606e7d894 100644 --- a/src/main/java/com/jnape/palatable/lambda/monad/transformer/builtin/IterateT.java +++ b/src/main/java/com/jnape/palatable/lambda/monad/transformer/builtin/IterateT.java @@ -230,6 +230,7 @@ public IterateT pure(B b) { * as such, calling this on an infinite {@link IterateT} will result in either heap exhaustion (e.g. in the case of * {@link List lists}) or non-termination (e.g. in the case of {@link Set sets}). * + * @param cFn0 the {@link Collection} construction function * @param the {@link Collection} type * @param the witnessed target type * @return the {@link List} inside of the effect From 4bc1b8068ba3fc61d94a93e8cefef73f6a2ae301 Mon Sep 17 00:00:00 2001 From: jnape Date: Sat, 8 Feb 2020 17:58:06 -0600 Subject: [PATCH 25/39] [maven-release-plugin] prepare release lambda-5.2.0 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 5f84c2dd2..f89d14be0 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ lambda - 5.1.1-SNAPSHOT + 5.2.0 jar Lambda From 19b797df16171be7882524a28eff66e981660940 Mon Sep 17 00:00:00 2001 From: jnape Date: Sat, 8 Feb 2020 17:58:13 -0600 Subject: [PATCH 26/39] [maven-release-plugin] prepare for next development iteration --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index f89d14be0..bda3d7caf 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ lambda - 5.2.0 + 5.2.1-SNAPSHOT jar Lambda From b4212694c42bef82353bc2459a19f327293dfda5 Mon Sep 17 00:00:00 2001 From: jnape Date: Wed, 12 Feb 2020 18:17:05 -0600 Subject: [PATCH 27/39] Updating README and CHANGELOG --- CHANGELOG.md | 7 ++++++- README.md | 4 ++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8561f1f8f..1ce4e47aa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,10 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/). ## [Unreleased] +There are currently no unreleased changes + +## [5.2.0] - 2020-02-12 + ### Changed - `HList#cons` static factory method auto-promotes to specialized `HList` if there is one - `EitherT` gains a `MonadError` instance @@ -557,7 +561,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/). - `Monadic/Dyadic/TriadicFunction`, `Predicate`, `Tuple2`, `Tuple3` - `Functor`, `BiFunctor`, `ProFunctor` -[Unreleased]: https://github.com/palatable/lambda/compare/lambda-5.1.0...HEAD +[Unreleased]: https://github.com/palatable/lambda/compare/lambda-5.2.0...HEAD +[5.2.0]: https://github.com/palatable/lambda/compare/lambda-5.1.0...lambda-5.2.0 [5.1.0]: https://github.com/palatable/lambda/compare/lambda-5.0.0...lambda-5.1.0 [5.0.0]: https://github.com/palatable/lambda/compare/lambda-4.0.0...lambda-5.0.0 [4.0.0]: https://github.com/palatable/lambda/compare/lambda-3.3.0...lambda-4.0.0 diff --git a/README.md b/README.md index edaf4dde9..312d220fb 100644 --- a/README.md +++ b/README.md @@ -61,14 +61,14 @@ Add the following dependency to your: com.jnape.palatable lambda - 5.1.0 + 5.2.0 ``` `build.gradle` ([Gradle](https://docs.gradle.org/current/userguide/dependency_management.html)): ```gradle -compile group: 'com.jnape.palatable', name: 'lambda', version: '5.1.0' +compile group: 'com.jnape.palatable', name: 'lambda', version: '5.2.0' ``` Examples From c3f5652ba3fd81f1528bfb9c409d0902925869c5 Mon Sep 17 00:00:00 2001 From: jnape Date: Fri, 21 Feb 2020 15:44:30 -0600 Subject: [PATCH 28/39] Adding static Pure and Lift instances for IterateT --- .../monad/transformer/builtin/IterateT.java | 26 ++++++++++++++ .../transformer/builtin/IterateTTest.java | 35 +++++++++++-------- 2 files changed, 46 insertions(+), 15 deletions(-) diff --git a/src/main/java/com/jnape/palatable/lambda/monad/transformer/builtin/IterateT.java b/src/main/java/com/jnape/palatable/lambda/monad/transformer/builtin/IterateT.java index 606e7d894..e4197b98d 100644 --- a/src/main/java/com/jnape/palatable/lambda/monad/transformer/builtin/IterateT.java +++ b/src/main/java/com/jnape/palatable/lambda/monad/transformer/builtin/IterateT.java @@ -8,6 +8,7 @@ import com.jnape.palatable.lambda.functions.Fn1; import com.jnape.palatable.lambda.functions.Fn2; import com.jnape.palatable.lambda.functions.recursion.RecursiveResult; +import com.jnape.palatable.lambda.functions.specialized.Lift; import com.jnape.palatable.lambda.functions.specialized.Pure; import com.jnape.palatable.lambda.functor.Applicative; import com.jnape.palatable.lambda.functor.builtin.Lazy; @@ -451,4 +452,29 @@ public static IterateT, A> fromIterator(Iterator as) { return nothing(); }), io(() -> as)); } + + /** + * The canonical {@link Pure} instance for {@link IterateT}. + * + * @param pureM the argument {@link Monad} {@link Pure} + * @param the argument {@link Monad} witness + * @return the {@link Pure} instance + */ + public static > Pure> pureIterateT(Pure pureM) { + return new Pure>() { + @Override + public IterateT checkedApply(A a) { + return liftIterateT().apply(pureM.>apply(a)); + } + }; + } + + /** + * {@link Lift} for {@link IterateT}. + * + * @return the {@link Monad} lifted into {@link IterateT} + */ + public static Lift> liftIterateT() { + return IterateT::singleton; + } } diff --git a/src/test/java/com/jnape/palatable/lambda/monad/transformer/builtin/IterateTTest.java b/src/test/java/com/jnape/palatable/lambda/monad/transformer/builtin/IterateTTest.java index 00768b0c3..b74e93835 100644 --- a/src/test/java/com/jnape/palatable/lambda/monad/transformer/builtin/IterateTTest.java +++ b/src/test/java/com/jnape/palatable/lambda/monad/transformer/builtin/IterateTTest.java @@ -13,11 +13,7 @@ import com.jnape.palatable.traitor.runners.Traits; import org.junit.Test; import org.junit.runner.RunWith; -import testsupport.traits.ApplicativeLaws; -import testsupport.traits.Equivalence; -import testsupport.traits.FunctorLaws; -import testsupport.traits.MonadLaws; -import testsupport.traits.MonadRecLaws; +import testsupport.traits.*; import java.util.ArrayList; import java.util.Collection; @@ -34,14 +30,9 @@ import static com.jnape.palatable.lambda.functions.recursion.RecursiveResult.terminate; import static com.jnape.palatable.lambda.functor.builtin.Identity.pureIdentity; import static com.jnape.palatable.lambda.functor.builtin.Lazy.lazy; -import static com.jnape.palatable.lambda.functor.builtin.Writer.listen; -import static com.jnape.palatable.lambda.functor.builtin.Writer.pureWriter; -import static com.jnape.palatable.lambda.functor.builtin.Writer.tell; -import static com.jnape.palatable.lambda.functor.builtin.Writer.writer; +import static com.jnape.palatable.lambda.functor.builtin.Writer.*; import static com.jnape.palatable.lambda.io.IO.io; -import static com.jnape.palatable.lambda.monad.transformer.builtin.IterateT.empty; -import static com.jnape.palatable.lambda.monad.transformer.builtin.IterateT.singleton; -import static com.jnape.palatable.lambda.monad.transformer.builtin.IterateT.unfold; +import static com.jnape.palatable.lambda.monad.transformer.builtin.IterateT.*; import static com.jnape.palatable.lambda.monoid.builtin.AddAll.addAll; import static com.jnape.palatable.lambda.monoid.builtin.Join.join; import static com.jnape.palatable.traitor.framework.Subjects.subjects; @@ -53,9 +44,7 @@ import static org.junit.Assert.assertThat; import static testsupport.Constants.STACK_EXPLODING_NUMBER; import static testsupport.matchers.IOMatcher.yieldsValue; -import static testsupport.matchers.IterateTMatcher.isEmpty; -import static testsupport.matchers.IterateTMatcher.iterates; -import static testsupport.matchers.IterateTMatcher.iteratesAll; +import static testsupport.matchers.IterateTMatcher.*; import static testsupport.traits.Equivalence.equivalence; @RunWith(Traits.class) @@ -242,4 +231,20 @@ public void concatIsStackSafe() { assertEquals(new Identity<>(10_000), bigIterateT.fold((x, y) -> new Identity<>(x + y), new Identity<>(0))); } + + @Test + public void staticPure() { + assertEquals(new Identity<>(singletonList(1)), + pureIterateT(pureIdentity()) + ., Integer>>apply(1) + ., Identity>>toCollection(ArrayList::new)); + } + + @Test + public void staticLift() { + assertEquals(new Identity<>(singletonList(1)), + liftIterateT() + ., IterateT, Integer>>apply(new Identity<>(1)) + ., Identity>>toCollection(ArrayList::new)); + } } \ No newline at end of file From 6748e7b70c01e0ae9b97189ed400020e8923e2c9 Mon Sep 17 00:00:00 2001 From: John Napier Date: Mon, 23 Mar 2020 15:52:38 -0500 Subject: [PATCH 29/39] Attempting to add JDK14 build to github actions --- .github/workflows/maven.yml | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml index 2f7615ccb..873d5ac83 100644 --- a/.github/workflows/maven.yml +++ b/.github/workflows/maven.yml @@ -28,3 +28,16 @@ jobs: java-version: 11 - name: Build with Maven run: mvn clean verify + + build-java-14: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v1 + - name: Set up JDK 14 + uses: actions/setup-java@v1 + with: + java-version: 14 + - name: Build with Maven + run: mvn clean verify From 8ac26203b600577d0bccb43c389bd078a8f05f18 Mon Sep 17 00:00:00 2001 From: jnape Date: Wed, 1 Apr 2020 14:34:23 -0500 Subject: [PATCH 30/39] Adding $, function application represented as a higher-order Fn2 --- CHANGELOG.md | 3 +- .../lambda/functions/builtin/fn2/$.java | 43 +++++++++++++++++++ .../lambda/functions/builtin/fn2/$Test.java | 21 +++++++++ 3 files changed, 66 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/$.java create mode 100644 src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/$Test.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 1ce4e47aa..885221573 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/). ## [Unreleased] -There are currently no unreleased changes +### Added +- `$`, function application represented as a higher-order `Fn2` ## [5.2.0] - 2020-02-12 diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/$.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/$.java new file mode 100644 index 000000000..63c60c051 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/$.java @@ -0,0 +1,43 @@ +package com.jnape.palatable.lambda.functions.builtin.fn2; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.Fn2; + +/** + * Function application, represented as a higher-order {@link Fn2} that receives an {@link Fn1} and its argument, and + * applies it. Useful for treating application as a combinator, e.g.: + *
+ * {@code
+ * List> fns     = asList(x -> x + 1, x -> x, x -> x - 1);
+ * List               args    = asList(0, 1, 2);
+ * Iterable           results = zipWith($(), fns, args); // [1, 1, 1]
+ * }
+ * 
+ * + * @param
the applied {@link Fn1 Fn1's} input type + * @param the applied {@link Fn1 Fn1's} output type + */ +public final class $ implements Fn2, A, B> { + private static final $ INSTANCE = new $<>(); + + private $() { + } + + @Override + public B checkedApply(Fn1 fn, A a) { + return fn.apply(a); + } + + @SuppressWarnings("unchecked") + public static $ $() { + return ($) INSTANCE; + } + + public static Fn1 $(Fn1 fn) { + return $.$().apply(fn); + } + + public static B $(Fn1 fn, A a) { + return $.$(fn).apply(a); + } +} diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/$Test.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/$Test.java new file mode 100644 index 000000000..166e99f29 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/$Test.java @@ -0,0 +1,21 @@ +package com.jnape.palatable.lambda.functions.builtin.fn2; + +import org.junit.Test; + +import static com.jnape.palatable.lambda.functions.Fn2.fn2; +import static com.jnape.palatable.lambda.functions.builtin.fn2.$.$; +import static org.junit.Assert.assertEquals; + +public class $Test { + + @Test + public void application() { + assertEquals((Integer) 1, $(x -> x + 1, 0)); + assertEquals((Integer) 1, $.$(x -> x + 1).apply(0)); + } + + @Test + public void curryingInference() { + assertEquals((Integer) 1, $($(fn2(Integer::sum), 0), 1)); + } +} \ No newline at end of file From c9e86a8782dad57b7841f7e2d2af62dcbc6be547 Mon Sep 17 00:00:00 2001 From: Joshua Potts <8704475+iamjpotts@users.noreply.github.com> Date: Sun, 12 Apr 2020 23:36:51 -0500 Subject: [PATCH 31/39] Adjust documentation for Fn7 and Fn8 --- src/main/java/com/jnape/palatable/lambda/functions/Fn7.java | 2 +- src/main/java/com/jnape/palatable/lambda/functions/Fn8.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/jnape/palatable/lambda/functions/Fn7.java b/src/main/java/com/jnape/palatable/lambda/functions/Fn7.java index 2d27e6be2..88044dff1 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/Fn7.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/Fn7.java @@ -8,7 +8,7 @@ import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; /** - * A function taking six arguments. Defined in terms of {@link Fn6}, so similarly auto-curried. + * A function taking seven arguments. Defined in terms of {@link Fn6}, so similarly auto-curried. * * @param The first argument type * @param The second argument type diff --git a/src/main/java/com/jnape/palatable/lambda/functions/Fn8.java b/src/main/java/com/jnape/palatable/lambda/functions/Fn8.java index a38e74eb1..c2b8fab52 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/Fn8.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/Fn8.java @@ -5,7 +5,7 @@ import com.jnape.palatable.lambda.internal.Runtime; /** - * A function taking six arguments. Defined in terms of {@link Fn7}, so similarly auto-curried. + * A function taking eight arguments. Defined in terms of {@link Fn7}, so similarly auto-curried. * * @param The first argument type * @param The second argument type From fec66aad6542b7921af0ae2abdc40277e21400fe Mon Sep 17 00:00:00 2001 From: Joshua Potts <8704475+iamjpotts@users.noreply.github.com> Date: Tue, 14 Apr 2020 22:17:54 -0500 Subject: [PATCH 32/39] Adjust documentation for Fn7 and Fn8 --- src/main/java/com/jnape/palatable/lambda/functions/Fn7.java | 4 ++-- src/main/java/com/jnape/palatable/lambda/functions/Fn8.java | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/jnape/palatable/lambda/functions/Fn7.java b/src/main/java/com/jnape/palatable/lambda/functions/Fn7.java index 88044dff1..15f7a3be9 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/Fn7.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/Fn7.java @@ -258,7 +258,7 @@ static Fn7 fn7(Fn4 the first input argument type * @param the second input argument type * @param the third input argument type @@ -276,7 +276,7 @@ static Fn7 fn7(Fn5 the first input argument type * @param the second input argument type * @param the third input argument type diff --git a/src/main/java/com/jnape/palatable/lambda/functions/Fn8.java b/src/main/java/com/jnape/palatable/lambda/functions/Fn8.java index c2b8fab52..60f3a4a3c 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/Fn8.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/Fn8.java @@ -274,7 +274,7 @@ static Fn8 fn8( /** * Static factory method for wrapping a curried {@link Fn5} in an {@link Fn8}. * - * @param curriedFn5 the curried fn4 to adapt + * @param curriedFn5 the curried fn5 to adapt * @param the first input argument type * @param the second input argument type * @param the third input argument type @@ -294,7 +294,7 @@ static Fn8 fn8( /** * Static factory method for wrapping a curried {@link Fn6} in an {@link Fn8}. * - * @param curriedFn6 the curried fn4 to adapt + * @param curriedFn6 the curried fn6 to adapt * @param the first input argument type * @param the second input argument type * @param the third input argument type @@ -314,7 +314,7 @@ static Fn8 fn8( /** * Static factory method for wrapping a curried {@link Fn7} in an {@link Fn8}. * - * @param curriedFn7 the curried fn4 to adapt + * @param curriedFn7 the curried fn7 to adapt * @param the first input argument type * @param the second input argument type * @param the third input argument type From 0643bc4a01358b0afd8ee0711db385bedeaf6722 Mon Sep 17 00:00:00 2001 From: jnape Date: Wed, 22 Apr 2020 17:26:51 -0500 Subject: [PATCH 33/39] Fn1#withSelf for writing self-referencing Fn1s --- CHANGELOG.md | 1 + .../jnape/palatable/lambda/functions/Fn1.java | 17 +++++++++++++++++ .../palatable/lambda/functions/Fn1Test.java | 7 ++++++- 3 files changed, 24 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 885221573..c0b802288 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/). ### Added - `$`, function application represented as a higher-order `Fn2` +- `Fn1#withSelf`, a static method for constructing a self-referencing `Fn1` ## [5.2.0] - 2020-02-12 diff --git a/src/main/java/com/jnape/palatable/lambda/functions/Fn1.java b/src/main/java/com/jnape/palatable/lambda/functions/Fn1.java index f21e70e23..cb4904d35 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/Fn1.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/Fn1.java @@ -302,6 +302,10 @@ default Fn2 andThen(Fn2 after return (a, c) -> after.apply(apply(a), c); } + default Fn1 self() { + return this; + } + /** * Static factory method for avoid explicit casting when using method references as {@link Fn1}s. * @@ -335,4 +339,17 @@ static Fn1 fromFunction(Function function) static Pure> pureFn1() { return Constantly::constantly; } + + /** + * Construct an {@link Fn1} that has a reference to itself in scope at the time it is executed (presumably for + * recursive invocations). + * + * @param fn the body of the function, with access to itself + * @param the input type + * @param the output type + * @return the {@link Fn1} + */ + static Fn1 withSelf(Fn2, ? super A, ? extends B> fn) { + return a -> fn.apply(withSelf(fn), a); + } } diff --git a/src/test/java/com/jnape/palatable/lambda/functions/Fn1Test.java b/src/test/java/com/jnape/palatable/lambda/functions/Fn1Test.java index 39400c6b0..047aea1db 100644 --- a/src/test/java/com/jnape/palatable/lambda/functions/Fn1Test.java +++ b/src/test/java/com/jnape/palatable/lambda/functions/Fn1Test.java @@ -8,8 +8,8 @@ import testsupport.traits.Equivalence; import testsupport.traits.FunctorLaws; import testsupport.traits.MonadLaws; -import testsupport.traits.MonadRecLaws; import testsupport.traits.MonadReaderLaws; +import testsupport.traits.MonadRecLaws; import testsupport.traits.MonadWriterLaws; import java.util.function.Function; @@ -105,4 +105,9 @@ public void staticPure() { Fn1 fn1 = Fn1.pureFn1().apply(1); assertEquals((Integer) 1, fn1.apply("anything")); } + + @Test + public void withSelf() { + assertEquals((Integer) 15, Fn1.withSelf((f, x) -> x > 1 ? x + f.apply(x - 1) : x).apply(5)); + } } From 8814a68035f6829918bec4b9115c6ad7d12dc172 Mon Sep 17 00:00:00 2001 From: jnape Date: Wed, 22 Apr 2020 17:27:38 -0500 Subject: [PATCH 34/39] IterateT#unfold now only constructs one Pure instance at invocation time --- .../monad/transformer/builtin/IterateT.java | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/jnape/palatable/lambda/monad/transformer/builtin/IterateT.java b/src/main/java/com/jnape/palatable/lambda/monad/transformer/builtin/IterateT.java index e4197b98d..07ea19982 100644 --- a/src/main/java/com/jnape/palatable/lambda/monad/transformer/builtin/IterateT.java +++ b/src/main/java/com/jnape/palatable/lambda/monad/transformer/builtin/IterateT.java @@ -29,6 +29,8 @@ import static com.jnape.palatable.lambda.adt.choice.Choice2.a; import static com.jnape.palatable.lambda.adt.choice.Choice2.b; import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; +import static com.jnape.palatable.lambda.functions.Fn1.withSelf; +import static com.jnape.palatable.lambda.functions.builtin.fn2.$.$; import static com.jnape.palatable.lambda.functions.builtin.fn2.Into.into; import static com.jnape.palatable.lambda.functions.builtin.fn2.Tupler2.tupler; import static com.jnape.palatable.lambda.functions.builtin.fn3.FoldLeft.foldLeft; @@ -74,10 +76,10 @@ private IterateT(Pure pureM, ImmutableQueue> conses, ImmutableQueue>>, M>>, IterateT>> middles, ImmutableQueue> snocs) { - this.pureM = pureM; - this.conses = conses; + this.pureM = pureM; + this.conses = conses; this.middles = middles; - this.snocs = snocs; + this.snocs = snocs; } /** @@ -220,7 +222,7 @@ public IterateT fmap(Fn1 fn) { */ @Override public IterateT pure(B b) { - return singleton(runIterateT().pure(b)); + return singleton(pureM.>apply(b)); } /** @@ -413,9 +415,10 @@ public static , A> IterateT of( */ public static , A, B> IterateT unfold( Fn1>, M>> fn, MonadRec mb) { - return suspended(() -> maybeT(mb.flatMap(fn)) - .fmap(ab -> ab.fmap(b -> unfold(fn, mb.pure(b)))) - .runMaybeT(), Pure.of(mb)); + Pure pureM = Pure.of(mb); + return $(withSelf((self, mmb) -> suspended(() -> maybeT(mmb.flatMap(fn)) + .fmap(ab -> ab.>fmap(b -> self.apply(pureM.apply(b)))) + .runMaybeT(), pureM)), mb); } /** From 721d044e3fb166ed740bd028ad77febb6627e528 Mon Sep 17 00:00:00 2001 From: jnape Date: Wed, 22 Apr 2020 18:12:14 -0500 Subject: [PATCH 35/39] Updating CHANGELOG --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c0b802288..281562d22 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,9 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/). ## [Unreleased] +### Changed +- `IterateT#unfold` now only computes a single `Pure` for the given input + ### Added - `$`, function application represented as a higher-order `Fn2` - `Fn1#withSelf`, a static method for constructing a self-referencing `Fn1` From 736dcb068dde0f2b5cf448f8609319f5585790a7 Mon Sep 17 00:00:00 2001 From: Joshua Potts <8704475+iamjpotts@users.noreply.github.com> Date: Tue, 14 Apr 2020 20:32:53 -0500 Subject: [PATCH 36/39] Add snoc to HNil, SingletonHList, and Tuple classes --- CHANGELOG.md | 1 + .../com/jnape/palatable/lambda/adt/hlist/HList.java | 11 +++++++++++ .../palatable/lambda/adt/hlist/SingletonHList.java | 11 +++++++++++ .../com/jnape/palatable/lambda/adt/hlist/Tuple2.java | 10 ++++++++++ .../com/jnape/palatable/lambda/adt/hlist/Tuple3.java | 10 ++++++++++ .../com/jnape/palatable/lambda/adt/hlist/Tuple4.java | 10 ++++++++++ .../com/jnape/palatable/lambda/adt/hlist/Tuple5.java | 10 ++++++++++ .../com/jnape/palatable/lambda/adt/hlist/Tuple6.java | 10 ++++++++++ .../com/jnape/palatable/lambda/adt/hlist/Tuple7.java | 10 ++++++++++ .../com/jnape/palatable/lambda/adt/hlist/Tuple8.java | 10 ++++++++++ .../jnape/palatable/lambda/adt/hlist/HListTest.java | 6 ++++++ .../lambda/adt/hlist/SingletonHListTest.java | 7 +++++++ .../jnape/palatable/lambda/adt/hlist/Tuple2Test.java | 6 ++++++ .../jnape/palatable/lambda/adt/hlist/Tuple3Test.java | 9 +++++++++ .../jnape/palatable/lambda/adt/hlist/Tuple4Test.java | 6 ++++++ .../jnape/palatable/lambda/adt/hlist/Tuple5Test.java | 6 ++++++ .../jnape/palatable/lambda/adt/hlist/Tuple6Test.java | 6 ++++++ .../jnape/palatable/lambda/adt/hlist/Tuple7Test.java | 6 ++++++ .../jnape/palatable/lambda/adt/hlist/Tuple8Test.java | 9 +++++++++ 19 files changed, 154 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 281562d22..e96c8697d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/). ### Added - `$`, function application represented as a higher-order `Fn2` - `Fn1#withSelf`, a static method for constructing a self-referencing `Fn1` +- `HNil/SingletonHList/TupleX#snoc`, a method to add a new last element (append to a tuple) ## [5.2.0] - 2020-02-12 diff --git a/src/main/java/com/jnape/palatable/lambda/adt/hlist/HList.java b/src/main/java/com/jnape/palatable/lambda/adt/hlist/HList.java index 421855a10..897f19c05 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/hlist/HList.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/hlist/HList.java @@ -297,5 +297,16 @@ private HNil() { public SingletonHList cons(Head head) { return new SingletonHList<>(head); } + + /** + * Snoc an element onto the back of this HList. + * + * @param last the new last element + * @param the new last element type + * @return the updated HList + */ + public SingletonHList snoc(Last last) { + return new SingletonHList<>(last); + } } } diff --git a/src/main/java/com/jnape/palatable/lambda/adt/hlist/SingletonHList.java b/src/main/java/com/jnape/palatable/lambda/adt/hlist/SingletonHList.java index 927f27ae3..79b3fbc87 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/hlist/SingletonHList.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/hlist/SingletonHList.java @@ -39,6 +39,17 @@ public <_0> Tuple2<_0, _1> cons(_0 _0) { return new Tuple2<>(_0, this); } + + /** + * Snoc an element onto the back of this HList. + * + * @param _2 the new last element + * @return the updated HList + */ + public <_2> Tuple2<_1, _2> snoc(_2 _2) { + return tuple(head(), _2); + } + /** * {@inheritDoc} */ diff --git a/src/main/java/com/jnape/palatable/lambda/adt/hlist/Tuple2.java b/src/main/java/com/jnape/palatable/lambda/adt/hlist/Tuple2.java index 726335f6d..b08d096f2 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 @@ -75,6 +75,16 @@ public <_0> Tuple3<_0, _1, _2> cons(_0 _0) { return new Tuple3<>(_0, this); } + /** + * Snoc an element onto the back of this HList. + * + * @param _3 the new last element + * @return the updated HList + */ + public <_3> Tuple3<_1, _2, _3> snoc(_3 _3) { + return tuple(_1, _2, _3); + } + /** * {@inheritDoc} */ diff --git a/src/main/java/com/jnape/palatable/lambda/adt/hlist/Tuple3.java b/src/main/java/com/jnape/palatable/lambda/adt/hlist/Tuple3.java index e32eac589..48fd309a4 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 @@ -56,6 +56,16 @@ public <_0> Tuple4<_0, _1, _2, _3> cons(_0 _0) { return new Tuple4<>(_0, this); } + /** + * Snoc an element onto the back of this HList. + * + * @param _4 the new last element + * @return the updated HList + */ + public <_4> Tuple4<_1, _2, _3, _4> snoc(_4 _4) { + return tuple(_1, _2, _3, _4); + } + /** * {@inheritDoc} */ diff --git a/src/main/java/com/jnape/palatable/lambda/adt/hlist/Tuple4.java b/src/main/java/com/jnape/palatable/lambda/adt/hlist/Tuple4.java index 3e74da859..f8b630aa5 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 @@ -59,6 +59,16 @@ public <_0> Tuple5<_0, _1, _2, _3, _4> cons(_0 _0) { return new Tuple5<>(_0, this); } + /** + * Snoc an element onto the back of this HList. + * + * @param _5 the new last element + * @return the updated HList + */ + public <_5> Tuple5<_1, _2, _3, _4, _5> snoc(_5 _5) { + return tuple(_1, _2, _3, _4, _5); + } + /** * {@inheritDoc} */ diff --git a/src/main/java/com/jnape/palatable/lambda/adt/hlist/Tuple5.java b/src/main/java/com/jnape/palatable/lambda/adt/hlist/Tuple5.java index c87ee564b..e7a2c7877 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 @@ -62,6 +62,16 @@ public <_0> Tuple6<_0, _1, _2, _3, _4, _5> cons(_0 _0) { return new Tuple6<>(_0, this); } + /** + * Snoc an element onto the back of this HList. + * + * @param _6 the new last element + * @return the updated HList + */ + public <_6> Tuple6<_1, _2, _3, _4, _5, _6> snoc(_6 _6) { + return tuple(_1, _2, _3, _4, _5, _6); + } + /** * {@inheritDoc} */ diff --git a/src/main/java/com/jnape/palatable/lambda/adt/hlist/Tuple6.java b/src/main/java/com/jnape/palatable/lambda/adt/hlist/Tuple6.java index 6d07b503b..ca65c7793 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 @@ -66,6 +66,16 @@ public <_0> Tuple7<_0, _1, _2, _3, _4, _5, _6> cons(_0 _0) { return new Tuple7<>(_0, this); } + /** + * Snoc an element onto the back of this HList. + * + * @param _7 the new last element + * @return the updated HList + */ + public <_7> Tuple7<_1, _2, _3, _4, _5, _6, _7> snoc(_7 _7) { + return tuple(_1, _2, _3, _4, _5, _6, _7); + } + /** * {@inheritDoc} */ diff --git a/src/main/java/com/jnape/palatable/lambda/adt/hlist/Tuple7.java b/src/main/java/com/jnape/palatable/lambda/adt/hlist/Tuple7.java index a3e162f73..61f4edf63 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 @@ -70,6 +70,16 @@ public <_0> Tuple8<_0, _1, _2, _3, _4, _5, _6, _7> cons(_0 _0) { return new Tuple8<>(_0, this); } + /** + * Snoc an element onto the back of this HList. + * + * @param _8 the new last element + * @return the updated HList + */ + public <_8> Tuple8<_1, _2, _3, _4, _5, _6, _7, _8> snoc(_8 _8) { + return tuple(_1, _2, _3, _4, _5, _6, _7, _8); + } + /** * {@inheritDoc} */ diff --git a/src/main/java/com/jnape/palatable/lambda/adt/hlist/Tuple8.java b/src/main/java/com/jnape/palatable/lambda/adt/hlist/Tuple8.java index 8a8001e24..fd6f3d8d0 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 @@ -74,6 +74,16 @@ public <_0> HCons<_0, Tuple8<_1, _2, _3, _4, _5, _6, _7, _8>> cons(_0 _0) { return new HCons<>(_0, this); } + /** + * Snoc an element onto the back of this HList. + * + * @param _9 the new last element + * @return the updated HList + */ + public <_9> HCons<_1, Tuple8<_2, _3, _4, _5, _6, _7, _8, _9>> snoc(_9 _9) { + return singletonHList(_9).cons(_8).cons(_7).cons(_6).cons(_5).cons(_4).cons(_3).cons(_2).cons(_1); + } + /** * {@inheritDoc} */ diff --git a/src/test/java/com/jnape/palatable/lambda/adt/hlist/HListTest.java b/src/test/java/com/jnape/palatable/lambda/adt/hlist/HListTest.java index 1f75ce450..09e9e44d0 100644 --- a/src/test/java/com/jnape/palatable/lambda/adt/hlist/HListTest.java +++ b/src/test/java/com/jnape/palatable/lambda/adt/hlist/HListTest.java @@ -68,4 +68,10 @@ public void hashCodeUsesDecentDistribution() { assertNotEquals(nil().cons(1).hashCode(), nil().cons(2).hashCode()); assertNotEquals(nil().cons(1).cons(2).hashCode(), nil().cons(1).cons(3).hashCode()); } + + @Test + public void snoc() { + SingletonHList tuple = nil().snoc((float) 4.0); + assertEquals(4.0, tuple.head(), 0.01); + } } \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/adt/hlist/SingletonHListTest.java b/src/test/java/com/jnape/palatable/lambda/adt/hlist/SingletonHListTest.java index 57a9f85b7..6d7d8c0b2 100644 --- a/src/test/java/com/jnape/palatable/lambda/adt/hlist/SingletonHListTest.java +++ b/src/test/java/com/jnape/palatable/lambda/adt/hlist/SingletonHListTest.java @@ -13,6 +13,7 @@ import static com.jnape.palatable.lambda.adt.hlist.HList.nil; import static com.jnape.palatable.lambda.adt.hlist.HList.singletonHList; +import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; import static com.jnape.palatable.lambda.adt.hlist.SingletonHList.pureSingletonHList; import static org.junit.Assert.assertEquals; @@ -56,4 +57,10 @@ public void staticPure() { SingletonHList singletonHList = pureSingletonHList().apply(1); assertEquals(singletonHList(1), singletonHList); } + + @Test + public void snoc() { + Tuple2 tuple = singletonHList((byte) 127).snoc('x'); + assertEquals(tuple((byte) 127, 'x'), tuple); + } } \ 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 32483a168..ce28b964b 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 @@ -143,4 +143,10 @@ public void staticPure() { Tuple2 tuple = pureTuple(1).apply("two"); assertEquals(tuple(1, "two"), tuple); } + + @Test + public void snoc() { + Tuple3 tuple = tuple(Long.MAX_VALUE, 123).snoc("hi"); + assertEquals(tuple(Long.MAX_VALUE, 123, "hi"), tuple); + } } \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple3Test.java b/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple3Test.java index 9ac57fcfb..aa25c3e59 100644 --- a/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple3Test.java +++ b/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple3Test.java @@ -13,11 +13,14 @@ import testsupport.traits.MonadRecLaws; import testsupport.traits.TraversableLaws; +import java.time.Duration; + 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.adt.hlist.Tuple3.pureTuple; import static com.jnape.palatable.lambda.functions.builtin.fn1.Repeat.repeat; +import static java.time.Duration.ofSeconds; import static java.util.Collections.emptyList; import static java.util.Collections.singletonList; import static org.junit.Assert.assertEquals; @@ -119,4 +122,10 @@ public void staticPure() { Tuple3 tuple = pureTuple(1, "2").apply('3'); assertEquals(tuple(1, "2", '3'), tuple); } + + @Test + public void snoc() { + Tuple4 tuple = tuple("qux", Long.MIN_VALUE, 7).snoc(ofSeconds(13)); + assertEquals(tuple("qux", Long.MIN_VALUE, 7, ofSeconds(13)), tuple); + } } diff --git a/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple4Test.java b/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple4Test.java index b2a1d6319..a0b27f05e 100644 --- a/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple4Test.java +++ b/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple4Test.java @@ -122,4 +122,10 @@ public void staticPure() { Tuple4 tuple = pureTuple(1, "2", '3').apply(true); assertEquals(tuple(1, "2", '3', true), tuple); } + + @Test + public void snoc() { + Tuple5 tuple = tuple("qux", 7, "foo", 13L).snoc(17); + assertEquals(tuple("qux", 7, "foo", 13L, 17), tuple); + } } \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple5Test.java b/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple5Test.java index 83b300b97..ee0d6b7be 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 @@ -128,4 +128,10 @@ public void staticPure() { Tuple5 tuple = pureTuple(1, "2", '3', true).apply(5f); assertEquals(tuple(1, "2", '3', true, 5f), tuple); } + + @Test + public void snoc() { + Tuple6 tuple = tuple("a", 5, "b", 7, "c").snoc(11); + assertEquals(tuple("a", 5, "b", 7, "c", 11), tuple); + } } \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple6Test.java b/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple6Test.java index 902b4b254..dd57fde9c 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 @@ -132,4 +132,10 @@ public void staticPure() { Tuple6 tuple = pureTuple(1, "2", '3', true, 5f).apply((byte) 6); assertEquals(tuple(1, "2", '3', true, 5f, (byte) 6), tuple); } + + @Test + public void snoc() { + Tuple7 tuple = tuple(5L, "a", 7, "b", 11, "c").snoc(13); + assertEquals(tuple(5L, "a", 7, "b", 11, "c", 13), tuple); + } } \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple7Test.java b/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple7Test.java index 0e8a68aec..14ccb30a6 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 @@ -136,4 +136,10 @@ public void staticPure() { pureTuple((byte) 1, (short) 2, 3, 4L, 5F, 6D).apply(true); assertEquals(tuple((byte) 1, (short) 2, 3, 4L, 5F, 6D, true), tuple); } + + @Test + public void snoc() { + Tuple8 tuple = tuple("b", 7L, "c", 11, "d", 13, "e").snoc('f'); + assertEquals(tuple("b", 7L, "c", 11, "d", 13, "e", 'f'), tuple); + } } \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple8Test.java b/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple8Test.java index 4d2a8cb76..33921079e 100644 --- a/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple8Test.java +++ b/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple8Test.java @@ -14,6 +14,8 @@ import testsupport.traits.MonadRecLaws; import testsupport.traits.TraversableLaws; +import java.time.LocalDate; + import static com.jnape.palatable.lambda.adt.Maybe.just; import static com.jnape.palatable.lambda.adt.Maybe.nothing; import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; @@ -147,4 +149,11 @@ public void staticPure() { pureTuple((byte) 1, (short) 2, 3, 4L, 5F, 6D, true).apply('8'); assertEquals(tuple((byte) 1, (short) 2, 3, 4L, 5F, 6D, true, '8'), tuple); } + + @Test + public void snoc() { + HCons> actual = tuple("b", 7L, "c", 11, "d", 13, "e", 15L).snoc(LocalDate.of(2020, 4, 14)); + assertEquals("b", actual.head()); + assertEquals(actual.tail(), tuple(7L, "c", 11, "d", 13, "e", 15L, LocalDate.of(2020, 4, 14))); + } } \ No newline at end of file From ed6a066df40e95bd9bacb3ebe088aef73a25df0b Mon Sep 17 00:00:00 2001 From: jnape Date: Thu, 18 Jun 2020 11:23:09 -0500 Subject: [PATCH 37/39] Updating javadoc, reorganizing code, dropping HNil#snoc --- .../palatable/lambda/adt/hlist/HList.java | 20 +------------ .../lambda/adt/hlist/SingletonHList.java | 5 ++-- .../palatable/lambda/adt/hlist/Tuple2.java | 7 +++-- .../palatable/lambda/adt/hlist/Tuple3.java | 9 +++--- .../palatable/lambda/adt/hlist/Tuple4.java | 11 +++---- .../palatable/lambda/adt/hlist/Tuple5.java | 13 +++++---- .../palatable/lambda/adt/hlist/Tuple6.java | 15 +++++----- .../palatable/lambda/adt/hlist/Tuple7.java | 17 ++++++----- .../palatable/lambda/adt/hlist/Tuple8.java | 19 ++++++------ .../palatable/lambda/adt/hlist/HListTest.java | 29 +++++++++---------- .../lambda/adt/hlist/SingletonHListTest.java | 11 ++++--- .../lambda/adt/hlist/Tuple2Test.java | 11 ++++--- .../lambda/adt/hlist/Tuple3Test.java | 14 ++++----- .../lambda/adt/hlist/Tuple4Test.java | 11 ++++--- .../lambda/adt/hlist/Tuple5Test.java | 11 ++++--- .../lambda/adt/hlist/Tuple6Test.java | 11 ++++--- .../lambda/adt/hlist/Tuple7Test.java | 11 ++++--- .../lambda/adt/hlist/Tuple8Test.java | 16 +++++----- 18 files changed, 112 insertions(+), 129 deletions(-) diff --git a/src/main/java/com/jnape/palatable/lambda/adt/hlist/HList.java b/src/main/java/com/jnape/palatable/lambda/adt/hlist/HList.java index 897f19c05..705e43bad 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/hlist/HList.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/hlist/HList.java @@ -89,7 +89,6 @@ public static SingletonHList singletonHList(Head head) { * @return the 2-element HList * @see Tuple2 */ - @SuppressWarnings("JavaDoc") public static <_1, _2> Tuple2<_1, _2> tuple(_1 _1, _2 _2) { return singletonHList(_2).cons(_1); } @@ -106,7 +105,6 @@ public static <_1, _2> Tuple2<_1, _2> tuple(_1 _1, _2 _2) { * @return the 3-element HList * @see Tuple3 */ - @SuppressWarnings("JavaDoc") public static <_1, _2, _3> Tuple3<_1, _2, _3> tuple(_1 _1, _2 _2, _3 _3) { return tuple(_2, _3).cons(_1); } @@ -125,7 +123,6 @@ public static <_1, _2, _3> Tuple3<_1, _2, _3> tuple(_1 _1, _2 _2, _3 _3) { * @return the 4-element HList * @see Tuple4 */ - @SuppressWarnings("JavaDoc") public static <_1, _2, _3, _4> Tuple4<_1, _2, _3, _4> tuple(_1 _1, _2 _2, _3 _3, _4 _4) { return tuple(_2, _3, _4).cons(_1); } @@ -146,7 +143,6 @@ public static <_1, _2, _3, _4> Tuple4<_1, _2, _3, _4> tuple(_1 _1, _2 _2, _3 _3, * @return the 5-element HList * @see Tuple5 */ - @SuppressWarnings("JavaDoc") public static <_1, _2, _3, _4, _5> Tuple5<_1, _2, _3, _4, _5> tuple(_1 _1, _2 _2, _3 _3, _4 _4, _5 _5) { return tuple(_2, _3, _4, _5).cons(_1); } @@ -169,7 +165,6 @@ public static <_1, _2, _3, _4, _5> Tuple5<_1, _2, _3, _4, _5> tuple(_1 _1, _2 _2 * @return the 6-element HList * @see Tuple6 */ - @SuppressWarnings("JavaDoc") public static <_1, _2, _3, _4, _5, _6> Tuple6<_1, _2, _3, _4, _5, _6> tuple(_1 _1, _2 _2, _3 _3, _4 _4, _5 _5, _6 _6) { return tuple(_2, _3, _4, _5, _6).cons(_1); @@ -195,7 +190,6 @@ public static <_1, _2, _3, _4, _5, _6> Tuple6<_1, _2, _3, _4, _5, _6> tuple(_1 _ * @return the 7-element HList * @see Tuple7 */ - @SuppressWarnings("JavaDoc") public static <_1, _2, _3, _4, _5, _6, _7> Tuple7<_1, _2, _3, _4, _5, _6, _7> tuple(_1 _1, _2 _2, _3 _3, _4 _4, _5 _5, _6 _6, _7 _7) { return tuple(_2, _3, _4, _5, _6, _7).cons(_1); @@ -223,7 +217,6 @@ public static <_1, _2, _3, _4, _5, _6, _7> Tuple7<_1, _2, _3, _4, _5, _6, _7> tu * @return the 8-element HList * @see Tuple8 */ - @SuppressWarnings("JavaDoc") public static <_1, _2, _3, _4, _5, _6, _7, _8> Tuple8<_1, _2, _3, _4, _5, _6, _7, _8> tuple(_1 _1, _2 _2, _3 _3, _4 _4, _5 _5, _6 _6, _7 _7, _8 _8) { @@ -273,7 +266,7 @@ public final boolean equals(Object other) { if (other instanceof HCons) { HCons that = (HCons) other; return this.head.equals(that.head) - && this.tail.equals(that.tail); + && this.tail.equals(that.tail); } return false; } @@ -297,16 +290,5 @@ private HNil() { public SingletonHList cons(Head head) { return new SingletonHList<>(head); } - - /** - * Snoc an element onto the back of this HList. - * - * @param last the new last element - * @param the new last element type - * @return the updated HList - */ - public SingletonHList snoc(Last last) { - return new SingletonHList<>(last); - } } } 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 79b3fbc87..8e8516a47 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/hlist/SingletonHList.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/hlist/SingletonHList.java @@ -41,10 +41,11 @@ public <_0> Tuple2<_0, _1> cons(_0 _0) { /** - * Snoc an element onto the back of this HList. + * Snoc an element onto the back of this {@link SingletonHList}. * * @param _2 the new last element - * @return the updated HList + * @param <_2> the new last element type + * @return the new {@link Tuple2} */ public <_2> Tuple2<_1, _2> snoc(_2 _2) { return tuple(head(), _2); 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 b08d096f2..cdf79be12 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/hlist/Tuple2.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/hlist/Tuple2.java @@ -48,7 +48,7 @@ public class Tuple2<_1, _2> extends HCons<_1, SingletonHList<_2>> implements Tuple2(_1 _1, SingletonHList<_2> tail) { super(_1, tail); this._1 = _1; - _2 = tail.head(); + _2 = tail.head(); } /** @@ -76,10 +76,11 @@ public <_0> Tuple3<_0, _1, _2> cons(_0 _0) { } /** - * Snoc an element onto the back of this HList. + * Snoc an element onto the back of this {@link Tuple2}. * * @param _3 the new last element - * @return the updated HList + * @param <_3> the new last element type + * @return the new {@link Tuple3} */ public <_3> Tuple3<_1, _2, _3> snoc(_3 _3) { return tuple(_1, _2, _3); 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 48fd309a4..6fe7710b3 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/hlist/Tuple3.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/hlist/Tuple3.java @@ -44,8 +44,8 @@ public class Tuple3<_1, _2, _3> extends HCons<_1, Tuple2<_2, _3>> implements Tuple3(_1 _1, Tuple2<_2, _3> tail) { super(_1, tail); this._1 = _1; - _2 = tail._1(); - _3 = tail._2(); + _2 = tail._1(); + _3 = tail._2(); } /** @@ -57,10 +57,11 @@ public <_0> Tuple4<_0, _1, _2, _3> cons(_0 _0) { } /** - * Snoc an element onto the back of this HList. + * Snoc an element onto the back of this {@link Tuple3}. * * @param _4 the new last element - * @return the updated HList + * @param <_4> the new last element type + * @return the new {@link Tuple4} */ public <_4> Tuple4<_1, _2, _3, _4> snoc(_4 _4) { return tuple(_1, _2, _3, _4); 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 f8b630aa5..ebd946548 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/hlist/Tuple4.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/hlist/Tuple4.java @@ -46,9 +46,9 @@ public class Tuple4<_1, _2, _3, _4> extends HCons<_1, Tuple3<_2, _3, _4>> implem Tuple4(_1 _1, Tuple3<_2, _3, _4> tail) { super(_1, tail); this._1 = _1; - _2 = tail._1(); - _3 = tail._2(); - _4 = tail._3(); + _2 = tail._1(); + _3 = tail._2(); + _4 = tail._3(); } /** @@ -60,10 +60,11 @@ public <_0> Tuple5<_0, _1, _2, _3, _4> cons(_0 _0) { } /** - * Snoc an element onto the back of this HList. + * Snoc an element onto the back of this {@link Tuple4}. * * @param _5 the new last element - * @return the updated HList + * @param <_5> the new last element type + * @return the new {@link Tuple5} */ public <_5> Tuple5<_1, _2, _3, _4, _5> snoc(_5 _5) { return tuple(_1, _2, _3, _4, _5); 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 e7a2c7877..869a41f97 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/hlist/Tuple5.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/hlist/Tuple5.java @@ -48,10 +48,10 @@ public class Tuple5<_1, _2, _3, _4, _5> extends HCons<_1, Tuple4<_2, _3, _4, _5> Tuple5(_1 _1, Tuple4<_2, _3, _4, _5> tail) { super(_1, tail); this._1 = _1; - _2 = tail._1(); - _3 = tail._2(); - _4 = tail._3(); - _5 = tail._4(); + _2 = tail._1(); + _3 = tail._2(); + _4 = tail._3(); + _5 = tail._4(); } /** @@ -63,10 +63,11 @@ public <_0> Tuple6<_0, _1, _2, _3, _4, _5> cons(_0 _0) { } /** - * Snoc an element onto the back of this HList. + * Snoc an element onto the back of this {@link Tuple5}. * * @param _6 the new last element - * @return the updated HList + * @param <_6> the new last element type + * @return the new {@link Tuple6} */ public <_6> Tuple6<_1, _2, _3, _4, _5, _6> snoc(_6 _6) { return tuple(_1, _2, _3, _4, _5, _6); 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 ca65c7793..72bbac9ba 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/hlist/Tuple6.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/hlist/Tuple6.java @@ -51,11 +51,11 @@ public class Tuple6<_1, _2, _3, _4, _5, _6> extends HCons<_1, Tuple5<_2, _3, _4, Tuple6(_1 _1, Tuple5<_2, _3, _4, _5, _6> tail) { super(_1, tail); this._1 = _1; - _2 = tail._1(); - _3 = tail._2(); - _4 = tail._3(); - _5 = tail._4(); - _6 = tail._5(); + _2 = tail._1(); + _3 = tail._2(); + _4 = tail._3(); + _5 = tail._4(); + _6 = tail._5(); } /** @@ -67,10 +67,11 @@ public <_0> Tuple7<_0, _1, _2, _3, _4, _5, _6> cons(_0 _0) { } /** - * Snoc an element onto the back of this HList. + * Snoc an element onto the back of this {@link Tuple6}. * * @param _7 the new last element - * @return the updated HList + * @param <_7> the new last element type + * @return the new {@link Tuple7} */ public <_7> Tuple7<_1, _2, _3, _4, _5, _6, _7> snoc(_7 _7) { return tuple(_1, _2, _3, _4, _5, _6, _7); 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 61f4edf63..3a2f027b7 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/hlist/Tuple7.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/hlist/Tuple7.java @@ -54,12 +54,12 @@ public class Tuple7<_1, _2, _3, _4, _5, _6, _7> extends HCons<_1, Tuple6<_2, _3, Tuple7(_1 _1, Tuple6<_2, _3, _4, _5, _6, _7> tail) { super(_1, tail); this._1 = _1; - _2 = tail._1(); - _3 = tail._2(); - _4 = tail._3(); - _5 = tail._4(); - _6 = tail._5(); - _7 = tail._6(); + _2 = tail._1(); + _3 = tail._2(); + _4 = tail._3(); + _5 = tail._4(); + _6 = tail._5(); + _7 = tail._6(); } /** @@ -71,10 +71,11 @@ public <_0> Tuple8<_0, _1, _2, _3, _4, _5, _6, _7> cons(_0 _0) { } /** - * Snoc an element onto the back of this HList. + * Snoc an element onto the back of this {@link Tuple7}. * * @param _8 the new last element - * @return the updated HList + * @param <_8> the new last element type + * @return the new {@link Tuple8} */ public <_8> Tuple8<_1, _2, _3, _4, _5, _6, _7, _8> snoc(_8 _8) { return tuple(_1, _2, _3, _4, _5, _6, _7, _8); 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 fd6f3d8d0..c97cea46d 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/hlist/Tuple8.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/hlist/Tuple8.java @@ -57,13 +57,13 @@ public class Tuple8<_1, _2, _3, _4, _5, _6, _7, _8> extends HCons<_1, Tuple7<_2, Tuple8(_1 _1, Tuple7<_2, _3, _4, _5, _6, _7, _8> tail) { super(_1, tail); this._1 = _1; - _2 = tail._1(); - _3 = tail._2(); - _4 = tail._3(); - _5 = tail._4(); - _6 = tail._5(); - _7 = tail._6(); - _8 = tail._7(); + _2 = tail._1(); + _3 = tail._2(); + _4 = tail._3(); + _5 = tail._4(); + _6 = tail._5(); + _7 = tail._6(); + _8 = tail._7(); } /** @@ -75,10 +75,11 @@ public <_0> HCons<_0, Tuple8<_1, _2, _3, _4, _5, _6, _7, _8>> cons(_0 _0) { } /** - * Snoc an element onto the back of this HList. + * Snoc an element onto the back of this {@link Tuple8}. * * @param _9 the new last element - * @return the updated HList + * @param <_9> the new last element type + * @return the new {@link HCons consed} {@link Tuple8} */ public <_9> HCons<_1, Tuple8<_2, _3, _4, _5, _6, _7, _8, _9>> snoc(_9 _9) { return singletonHList(_9).cons(_8).cons(_7).cons(_6).cons(_5).cons(_4).cons(_3).cons(_2).cons(_1); diff --git a/src/test/java/com/jnape/palatable/lambda/adt/hlist/HListTest.java b/src/test/java/com/jnape/palatable/lambda/adt/hlist/HListTest.java index 09e9e44d0..3e3513a3a 100644 --- a/src/test/java/com/jnape/palatable/lambda/adt/hlist/HListTest.java +++ b/src/test/java/com/jnape/palatable/lambda/adt/hlist/HListTest.java @@ -2,9 +2,15 @@ import org.junit.Test; -import static com.jnape.palatable.lambda.adt.hlist.HList.*; +import static com.jnape.palatable.lambda.adt.hlist.HList.cons; +import static com.jnape.palatable.lambda.adt.hlist.HList.nil; +import static com.jnape.palatable.lambda.adt.hlist.HList.singletonHList; +import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; import static org.hamcrest.CoreMatchers.instanceOf; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertThat; public class HListTest { @@ -48,16 +54,15 @@ public void nilReusesInstance() { } @Test - @SuppressWarnings({"EqualsWithItself", "EqualsBetweenInconvertibleTypes"}) public void equality() { - assertTrue(nil().equals(nil())); - assertTrue(cons(1, nil()).equals(cons(1, nil()))); + assertEquals(nil(), nil()); + assertEquals(cons(1, nil()), cons(1, nil())); - assertFalse(cons(1, nil()).equals(nil())); - assertFalse(nil().equals(cons(1, nil()))); + assertNotEquals(cons(1, nil()), nil()); + assertNotEquals(nil(), cons(1, nil())); - assertFalse(cons(1, cons(2, nil())).equals(cons(1, nil()))); - assertFalse(cons(1, nil()).equals(cons(1, cons(2, nil())))); + assertNotEquals(cons(1, cons(2, nil())), cons(1, nil())); + assertNotEquals(cons(1, nil()), cons(1, cons(2, nil()))); } @Test @@ -68,10 +73,4 @@ public void hashCodeUsesDecentDistribution() { assertNotEquals(nil().cons(1).hashCode(), nil().cons(2).hashCode()); assertNotEquals(nil().cons(1).cons(2).hashCode(), nil().cons(1).cons(3).hashCode()); } - - @Test - public void snoc() { - SingletonHList tuple = nil().snoc((float) 4.0); - assertEquals(4.0, tuple.head(), 0.01); - } } \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/adt/hlist/SingletonHListTest.java b/src/test/java/com/jnape/palatable/lambda/adt/hlist/SingletonHListTest.java index 6d7d8c0b2..7a577f2ce 100644 --- a/src/test/java/com/jnape/palatable/lambda/adt/hlist/SingletonHListTest.java +++ b/src/test/java/com/jnape/palatable/lambda/adt/hlist/SingletonHListTest.java @@ -47,6 +47,11 @@ public void cons() { assertEquals(new Tuple2<>("0", singletonHList), singletonHList.cons("0")); } + @Test + public void snoc() { + assertEquals(tuple((byte) 127, 'x'), singletonHList((byte) 127).snoc('x')); + } + @Test public void intoAppliesHeadToFn() { assertEquals("FOO", singletonHList("foo").into(String::toUpperCase)); @@ -57,10 +62,4 @@ public void staticPure() { SingletonHList singletonHList = pureSingletonHList().apply(1); assertEquals(singletonHList(1), singletonHList); } - - @Test - public void snoc() { - Tuple2 tuple = singletonHList((byte) 127).snoc('x'); - assertEquals(tuple((byte) 127, 'x'), tuple); - } } \ 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 ce28b964b..31dc2f285 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 @@ -68,6 +68,11 @@ public void cons() { assertEquals(new Tuple3<>(0, tuple2), tuple2.cons(0)); } + @Test + public void snoc() { + assertEquals(tuple(Long.MAX_VALUE, 123, "hi"), tuple(Long.MAX_VALUE, 123).snoc("hi")); + } + @Test public void accessors() { assertEquals((Integer) 1, tuple2._1()); @@ -143,10 +148,4 @@ public void staticPure() { Tuple2 tuple = pureTuple(1).apply("two"); assertEquals(tuple(1, "two"), tuple); } - - @Test - public void snoc() { - Tuple3 tuple = tuple(Long.MAX_VALUE, 123).snoc("hi"); - assertEquals(tuple(Long.MAX_VALUE, 123, "hi"), tuple); - } } \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple3Test.java b/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple3Test.java index aa25c3e59..55842ee8b 100644 --- a/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple3Test.java +++ b/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple3Test.java @@ -13,8 +13,6 @@ import testsupport.traits.MonadRecLaws; import testsupport.traits.TraversableLaws; -import java.time.Duration; - 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; @@ -65,6 +63,12 @@ public void cons() { assertEquals(new Tuple4<>(0, tuple3), tuple3.cons(0)); } + @Test + public void snoc() { + assertEquals(tuple("qux", Long.MIN_VALUE, 7, ofSeconds(13)), + tuple("qux", Long.MIN_VALUE, 7).snoc(ofSeconds(13))); + } + @Test public void accessors() { assertEquals((Integer) 1, tuple3._1()); @@ -122,10 +126,4 @@ public void staticPure() { Tuple3 tuple = pureTuple(1, "2").apply('3'); assertEquals(tuple(1, "2", '3'), tuple); } - - @Test - public void snoc() { - Tuple4 tuple = tuple("qux", Long.MIN_VALUE, 7).snoc(ofSeconds(13)); - assertEquals(tuple("qux", Long.MIN_VALUE, 7, ofSeconds(13)), tuple); - } } diff --git a/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple4Test.java b/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple4Test.java index a0b27f05e..bb5380891 100644 --- a/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple4Test.java +++ b/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple4Test.java @@ -62,6 +62,11 @@ public void cons() { assertEquals(new Tuple5<>(0, tuple4), tuple4.cons(0)); } + @Test + public void snoc() { + assertEquals(tuple("qux", 7, "foo", 13L, 17), tuple("qux", 7, "foo", 13L).snoc(17)); + } + @Test public void accessors() { assertEquals((Integer) 1, tuple4._1()); @@ -122,10 +127,4 @@ public void staticPure() { Tuple4 tuple = pureTuple(1, "2", '3').apply(true); assertEquals(tuple(1, "2", '3', true), tuple); } - - @Test - public void snoc() { - Tuple5 tuple = tuple("qux", 7, "foo", 13L).snoc(17); - assertEquals(tuple("qux", 7, "foo", 13L, 17), tuple); - } } \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple5Test.java b/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple5Test.java index ee0d6b7be..f156f9072 100644 --- a/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple5Test.java +++ b/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple5Test.java @@ -63,6 +63,11 @@ public void cons() { assertEquals(new HCons<>(0, tuple5), tuple5.cons(0)); } + @Test + public void snoc() { + assertEquals(tuple("a", 5, "b", 7, "c", 11), tuple("a", 5, "b", 7, "c").snoc(11)); + } + @Test public void accessors() { assertEquals((Integer) 1, tuple5._1()); @@ -128,10 +133,4 @@ public void staticPure() { Tuple5 tuple = pureTuple(1, "2", '3', true).apply(5f); assertEquals(tuple(1, "2", '3', true, 5f), tuple); } - - @Test - public void snoc() { - Tuple6 tuple = tuple("a", 5, "b", 7, "c").snoc(11); - assertEquals(tuple("a", 5, "b", 7, "c", 11), tuple); - } } \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple6Test.java b/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple6Test.java index dd57fde9c..a148ae972 100644 --- a/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple6Test.java +++ b/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple6Test.java @@ -64,6 +64,11 @@ public void cons() { assertEquals(new HCons<>(0, tuple6), tuple6.cons(0)); } + @Test + public void snoc() { + assertEquals(tuple(5L, "a", 7, "b", 11, "c", 13), tuple(5L, "a", 7, "b", 11, "c").snoc(13)); + } + @Test public void accessors() { assertEquals((Float) 2.0f, tuple6._1()); @@ -132,10 +137,4 @@ public void staticPure() { Tuple6 tuple = pureTuple(1, "2", '3', true, 5f).apply((byte) 6); assertEquals(tuple(1, "2", '3', true, 5f, (byte) 6), tuple); } - - @Test - public void snoc() { - Tuple7 tuple = tuple(5L, "a", 7, "b", 11, "c").snoc(13); - assertEquals(tuple(5L, "a", 7, "b", 11, "c", 13), tuple); - } } \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple7Test.java b/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple7Test.java index 14ccb30a6..f1cbb7f3a 100644 --- a/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple7Test.java +++ b/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple7Test.java @@ -64,6 +64,11 @@ public void cons() { assertEquals(new HCons<>(0, tuple7), tuple7.cons(0)); } + @Test + public void snoc() { + assertEquals(tuple("b", 7L, "c", 11, "d", 13, "e", 'f'), tuple("b", 7L, "c", 11, "d", 13, "e").snoc('f')); + } + @Test public void accessors() { assertEquals((Byte) (byte) 127, tuple7._1()); @@ -136,10 +141,4 @@ public void staticPure() { pureTuple((byte) 1, (short) 2, 3, 4L, 5F, 6D).apply(true); assertEquals(tuple((byte) 1, (short) 2, 3, 4L, 5F, 6D, true), tuple); } - - @Test - public void snoc() { - Tuple8 tuple = tuple("b", 7L, "c", 11, "d", 13, "e").snoc('f'); - assertEquals(tuple("b", 7L, "c", 11, "d", 13, "e", 'f'), tuple); - } } \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple8Test.java b/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple8Test.java index 33921079e..dacab1869 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 @@ -69,6 +69,15 @@ public void cons() { assertEquals(new HCons<>(0, tuple8), tuple8.cons(0)); } + @Test + public void snoc() { + LocalDate last = LocalDate.of(2020, 4, 14); + HCons> actual = + tuple("b", 7L, "c", 11, "d", 13, "e", 15L).snoc(last); + assertEquals("b", actual.head()); + assertEquals(actual.tail(), tuple(7L, "c", 11, "d", 13, "e", 15L, last)); + } + @Test public void accessors() { assertEquals((Short) (short) 65535, tuple8._1()); @@ -149,11 +158,4 @@ public void staticPure() { pureTuple((byte) 1, (short) 2, 3, 4L, 5F, 6D, true).apply('8'); assertEquals(tuple((byte) 1, (short) 2, 3, 4L, 5F, 6D, true, '8'), tuple); } - - @Test - public void snoc() { - HCons> actual = tuple("b", 7L, "c", 11, "d", 13, "e", 15L).snoc(LocalDate.of(2020, 4, 14)); - assertEquals("b", actual.head()); - assertEquals(actual.tail(), tuple(7L, "c", 11, "d", 13, "e", 15L, LocalDate.of(2020, 4, 14))); - } } \ No newline at end of file From 27879e8264c08176d9581077420458ca7a1d432b Mon Sep 17 00:00:00 2001 From: Michael Anderson Date: Fri, 24 Jul 2020 09:13:57 -0500 Subject: [PATCH 38/39] Add BimonadRec for instances of both MonadRec and Comonad - Prevent type inference explosions with too many extensions on Tuples --- .../lambda/adt/hlist/SingletonHList.java | 30 +++++++++++++++---- .../palatable/lambda/adt/hlist/Tuple2.java | 30 +++++++++++++++---- .../palatable/lambda/adt/hlist/Tuple3.java | 28 +++++++++++++---- .../palatable/lambda/adt/hlist/Tuple4.java | 28 +++++++++++++---- .../palatable/lambda/adt/hlist/Tuple5.java | 30 +++++++++++++++---- .../palatable/lambda/adt/hlist/Tuple6.java | 30 +++++++++++++++---- .../palatable/lambda/adt/hlist/Tuple7.java | 30 +++++++++++++++---- .../palatable/lambda/adt/hlist/Tuple8.java | 30 +++++++++++++++---- .../palatable/lambda/bimonad/BimonadRec.java | 15 ++++++++++ .../palatable/lambda/functor/builtin/Env.java | 20 +++++-------- .../lambda/functor/builtin/Identity.java | 11 +++---- .../lambda/functor/builtin/Store.java | 4 +-- .../lambda/functor/builtin/Traced.java | 16 +++++----- .../lambda/adt/hlist/SingletonHListTest.java | 12 ++------ .../lambda/adt/hlist/Tuple2Test.java | 16 +++------- .../lambda/adt/hlist/Tuple3Test.java | 10 ++----- .../lambda/adt/hlist/Tuple4Test.java | 10 ++----- .../lambda/adt/hlist/Tuple5Test.java | 15 +++------- .../lambda/adt/hlist/Tuple6Test.java | 15 +++------- .../lambda/adt/hlist/Tuple7Test.java | 15 +++------- .../lambda/adt/hlist/Tuple8Test.java | 15 +++------- 21 files changed, 258 insertions(+), 152 deletions(-) create mode 100644 src/main/java/com/jnape/palatable/lambda/bimonad/BimonadRec.java 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 8e8516a47..49c1a7329 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 @@ -2,6 +2,8 @@ import com.jnape.palatable.lambda.adt.hlist.HList.HCons; import com.jnape.palatable.lambda.adt.hlist.HList.HNil; +import com.jnape.palatable.lambda.bimonad.BimonadRec; +import com.jnape.palatable.lambda.comonad.Comonad; import com.jnape.palatable.lambda.functions.Fn1; import com.jnape.palatable.lambda.functions.recursion.RecursiveResult; import com.jnape.palatable.lambda.functions.specialized.Pure; @@ -24,7 +26,7 @@ * @see Tuple5 */ public class SingletonHList<_1> extends HCons<_1, HNil> implements - MonadRec<_1, SingletonHList>, + BimonadRec<_1, SingletonHList>, Traversable<_1, SingletonHList> { SingletonHList(_1 _1) { @@ -51,12 +53,28 @@ public <_2> Tuple2<_1, _2> snoc(_2 _2) { return tuple(head(), _2); } + /** + * {@inheritDoc} + */ + @Override + public _1 extract() { + return head(); + } + + /** + * {@inheritDoc} + */ + @Override + public SingletonHList extendImpl(Fn1>, ? extends B> f) { + return singletonHList(f.apply(this)); + } + /** * {@inheritDoc} */ @Override public <_1Prime> SingletonHList<_1Prime> fmap(Fn1 fn) { - return MonadRec.super.<_1Prime>fmap(fn).coerce(); + return BimonadRec.super.<_1Prime>fmap(fn).coerce(); } /** @@ -73,7 +91,7 @@ public <_1Prime> SingletonHList<_1Prime> pure(_1Prime _1Prime) { @Override public <_1Prime> SingletonHList<_1Prime> zip( Applicative, SingletonHList> appFn) { - return MonadRec.super.zip(appFn).coerce(); + return BimonadRec.super.zip(appFn).coerce(); } /** @@ -82,7 +100,7 @@ public <_1Prime> SingletonHList<_1Prime> zip( @Override public <_1Prime> Lazy> lazyZip( Lazy, SingletonHList>> lazyAppFn) { - return MonadRec.super.lazyZip(lazyAppFn).fmap(Monad<_1Prime, SingletonHList>::coerce); + return BimonadRec.super.lazyZip(lazyAppFn).fmap(Monad<_1Prime, SingletonHList>::coerce); } /** @@ -90,7 +108,7 @@ public <_1Prime> Lazy> lazyZip( */ @Override public <_1Prime> SingletonHList<_1Prime> discardL(Applicative<_1Prime, SingletonHList> appB) { - return MonadRec.super.discardL(appB).coerce(); + return BimonadRec.super.discardL(appB).coerce(); } /** @@ -98,7 +116,7 @@ public <_1Prime> SingletonHList<_1Prime> discardL(Applicative<_1Prime, Singleton */ @Override public <_1Prime> SingletonHList<_1> discardR(Applicative<_1Prime, SingletonHList> appB) { - return MonadRec.super.discardR(appB).coerce(); + return BimonadRec.super.discardR(appB).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 cdf79be12..1a5803023 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 @@ -3,6 +3,8 @@ import com.jnape.palatable.lambda.adt.Maybe; import com.jnape.palatable.lambda.adt.hlist.HList.HCons; import com.jnape.palatable.lambda.adt.product.Product2; +import com.jnape.palatable.lambda.bimonad.BimonadRec; +import com.jnape.palatable.lambda.comonad.Comonad; import com.jnape.palatable.lambda.functions.Fn1; import com.jnape.palatable.lambda.functions.builtin.fn1.Head; import com.jnape.palatable.lambda.functions.recursion.RecursiveResult; @@ -37,7 +39,7 @@ */ public class Tuple2<_1, _2> extends HCons<_1, SingletonHList<_2>> implements Product2<_1, _2>, - MonadRec<_2, Tuple2<_1, ?>>, + BimonadRec<_2, Tuple2<_1, ?>>, MonadWriter<_1, _2, Tuple2<_1, ?>>, Bifunctor<_1, _2, Tuple2>, Traversable<_2, Tuple2<_1, ?>> { @@ -134,12 +136,28 @@ public Tuple2<_2, _1> invert() { return tuple(_2, _1); } + /** + * {@inheritDoc} + */ + @Override + public _2 extract() { + return _2; + } + + /** + * {@inheritDoc} + */ + @Override + public Tuple2<_1, B> extendImpl(Fn1>, ? extends B> f) { + return tuple(_1, f.apply(this)); + } + /** * {@inheritDoc} */ @Override public <_2Prime> Tuple2<_1, _2Prime> fmap(Fn1 fn) { - return MonadRec.super.<_2Prime>fmap(fn).coerce(); + return BimonadRec.super.<_2Prime>fmap(fn).coerce(); } /** @@ -181,7 +199,7 @@ public <_2Prime> Tuple2<_1, _2Prime> pure(_2Prime _2Prime) { @Override public <_2Prime> Tuple2<_1, _2Prime> zip( Applicative, Tuple2<_1, ?>> appFn) { - return MonadRec.super.zip(appFn).coerce(); + return BimonadRec.super.zip(appFn).coerce(); } /** @@ -190,7 +208,7 @@ public <_2Prime> Tuple2<_1, _2Prime> zip( @Override public <_2Prime> Lazy> lazyZip( Lazy, Tuple2<_1, ?>>> lazyAppFn) { - return MonadRec.super.lazyZip(lazyAppFn).fmap(Monad<_2Prime, Tuple2<_1, ?>>::coerce); + return BimonadRec.super.lazyZip(lazyAppFn).fmap(Monad<_2Prime, Tuple2<_1, ?>>::coerce); } /** @@ -198,7 +216,7 @@ public <_2Prime> Lazy> lazyZip( */ @Override public <_2Prime> Tuple2<_1, _2Prime> discardL(Applicative<_2Prime, Tuple2<_1, ?>> appB) { - return MonadRec.super.discardL(appB).coerce(); + return BimonadRec.super.discardL(appB).coerce(); } /** @@ -206,7 +224,7 @@ public <_2Prime> Tuple2<_1, _2Prime> discardL(Applicative<_2Prime, Tuple2<_1, ?> */ @Override public <_2Prime> Tuple2<_1, _2> discardR(Applicative<_2Prime, Tuple2<_1, ?>> appB) { - return MonadRec.super.discardR(appB).coerce(); + return BimonadRec.super.discardR(appB).coerce(); } /** diff --git a/src/main/java/com/jnape/palatable/lambda/adt/hlist/Tuple3.java b/src/main/java/com/jnape/palatable/lambda/adt/hlist/Tuple3.java index 6fe7710b3..6696cb383 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 @@ -3,6 +3,8 @@ import com.jnape.palatable.lambda.adt.Maybe; import com.jnape.palatable.lambda.adt.hlist.HList.HCons; import com.jnape.palatable.lambda.adt.product.Product3; +import com.jnape.palatable.lambda.bimonad.BimonadRec; +import com.jnape.palatable.lambda.comonad.Comonad; import com.jnape.palatable.lambda.functions.Fn1; import com.jnape.palatable.lambda.functions.builtin.fn2.Into; import com.jnape.palatable.lambda.functions.recursion.RecursiveResult; @@ -33,7 +35,7 @@ */ public class Tuple3<_1, _2, _3> extends HCons<_1, Tuple2<_2, _3>> implements Product3<_1, _2, _3>, - MonadRec<_3, Tuple3<_1, _2, ?>>, + BimonadRec<_3, Tuple3<_1, _2, ?>>, Bifunctor<_2, _3, Tuple3<_1, ?, ?>>, Traversable<_3, Tuple3<_1, _2, ?>> { @@ -115,12 +117,28 @@ public Tuple3<_2, _1, _3> invert() { return tuple(_2, _1, _3); } + /** + * {@inheritDoc} + */ + @Override + public _3 extract() { + return _3; + } + + /** + * {@inheritDoc} + */ + @Override + public Tuple3<_1, _2, B> extendImpl(Fn1>, ? extends B> f) { + return tuple(_1, _2, f.apply(this)); + } + /** * {@inheritDoc} */ @Override public <_3Prime> Tuple3<_1, _2, _3Prime> fmap(Fn1 fn) { - return (Tuple3<_1, _2, _3Prime>) MonadRec.super.<_3Prime>fmap(fn); + return (Tuple3<_1, _2, _3Prime>) BimonadRec.super.<_3Prime>fmap(fn); } /** @@ -171,7 +189,7 @@ public <_3Prime> Tuple3<_1, _2, _3Prime> zip( @Override public <_3Prime> Lazy> lazyZip( Lazy, Tuple3<_1, _2, ?>>> lazyAppFn) { - return MonadRec.super.lazyZip(lazyAppFn).fmap(Monad<_3Prime, Tuple3<_1, _2, ?>>::coerce); + return BimonadRec.super.lazyZip(lazyAppFn).fmap(Monad<_3Prime, Tuple3<_1, _2, ?>>::coerce); } /** @@ -179,7 +197,7 @@ public <_3Prime> Lazy> lazyZip( */ @Override public <_3Prime> Tuple3<_1, _2, _3Prime> discardL(Applicative<_3Prime, Tuple3<_1, _2, ?>> appB) { - return MonadRec.super.discardL(appB).coerce(); + return BimonadRec.super.discardL(appB).coerce(); } /** @@ -187,7 +205,7 @@ public <_3Prime> Tuple3<_1, _2, _3Prime> discardL(Applicative<_3Prime, Tuple3<_1 */ @Override public <_3Prime> Tuple3<_1, _2, _3> discardR(Applicative<_3Prime, Tuple3<_1, _2, ?>> appB) { - return MonadRec.super.discardR(appB).coerce(); + return BimonadRec.super.discardR(appB).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 ebd946548..04bfdce5c 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 @@ -3,6 +3,8 @@ import com.jnape.palatable.lambda.adt.Maybe; import com.jnape.palatable.lambda.adt.hlist.HList.HCons; import com.jnape.palatable.lambda.adt.product.Product4; +import com.jnape.palatable.lambda.bimonad.BimonadRec; +import com.jnape.palatable.lambda.comonad.Comonad; import com.jnape.palatable.lambda.functions.Fn1; import com.jnape.palatable.lambda.functions.builtin.fn2.Into; import com.jnape.palatable.lambda.functions.recursion.RecursiveResult; @@ -34,7 +36,7 @@ */ public class Tuple4<_1, _2, _3, _4> extends HCons<_1, Tuple3<_2, _3, _4>> implements Product4<_1, _2, _3, _4>, - MonadRec<_4, Tuple4<_1, _2, _3, ?>>, + BimonadRec<_4, Tuple4<_1, _2, _3, ?>>, Bifunctor<_3, _4, Tuple4<_1, _2, ?, ?>>, Traversable<_4, Tuple4<_1, _2, _3, ?>> { @@ -142,12 +144,28 @@ public Tuple4<_2, _1, _3, _4> invert() { return tuple(_2, _1, _3, _4); } + /** + * {@inheritDoc} + */ + @Override + public _4 extract() { + return _4; + } + + /** + * {@inheritDoc} + */ + @Override + public Tuple4<_1, _2, _3, B> extendImpl(Fn1>, ? extends B> f) { + return tuple(_1, _2, _3, f.apply(this)); + } + /** * {@inheritDoc} */ @Override public <_4Prime> Tuple4<_1, _2, _3, _4Prime> fmap(Fn1 fn) { - return (Tuple4<_1, _2, _3, _4Prime>) MonadRec.super.<_4Prime>fmap(fn); + return (Tuple4<_1, _2, _3, _4Prime>) BimonadRec.super.<_4Prime>fmap(fn); } /** @@ -198,7 +216,7 @@ public <_4Prime> Tuple4<_1, _2, _3, _4Prime> zip( @Override public <_4Prime> Lazy> lazyZip( Lazy, Tuple4<_1, _2, _3, ?>>> lazyAppFn) { - return MonadRec.super.lazyZip(lazyAppFn).fmap(Monad<_4Prime, Tuple4<_1, _2, _3, ?>>::coerce); + return BimonadRec.super.lazyZip(lazyAppFn).fmap(Monad<_4Prime, Tuple4<_1, _2, _3, ?>>::coerce); } /** @@ -206,7 +224,7 @@ public <_4Prime> Lazy> lazyZip( */ @Override public <_4Prime> Tuple4<_1, _2, _3, _4Prime> discardL(Applicative<_4Prime, Tuple4<_1, _2, _3, ?>> appB) { - return MonadRec.super.discardL(appB).coerce(); + return BimonadRec.super.discardL(appB).coerce(); } /** @@ -214,7 +232,7 @@ public <_4Prime> Tuple4<_1, _2, _3, _4Prime> discardL(Applicative<_4Prime, Tuple */ @Override public <_4Prime> Tuple4<_1, _2, _3, _4> discardR(Applicative<_4Prime, Tuple4<_1, _2, _3, ?>> appB) { - return MonadRec.super.discardR(appB).coerce(); + return BimonadRec.super.discardR(appB).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 869a41f97..141690d0c 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 @@ -3,6 +3,8 @@ import com.jnape.palatable.lambda.adt.Maybe; import com.jnape.palatable.lambda.adt.hlist.HList.HCons; import com.jnape.palatable.lambda.adt.product.Product5; +import com.jnape.palatable.lambda.bimonad.BimonadRec; +import com.jnape.palatable.lambda.comonad.Comonad; import com.jnape.palatable.lambda.functions.Fn1; import com.jnape.palatable.lambda.functions.builtin.fn2.Into; import com.jnape.palatable.lambda.functions.recursion.RecursiveResult; @@ -35,7 +37,7 @@ */ public class Tuple5<_1, _2, _3, _4, _5> extends HCons<_1, Tuple4<_2, _3, _4, _5>> implements Product5<_1, _2, _3, _4, _5>, - MonadRec<_5, Tuple5<_1, _2, _3, _4, ?>>, + BimonadRec<_5, Tuple5<_1, _2, _3, _4, ?>>, Bifunctor<_4, _5, Tuple5<_1, _2, _3, ?, ?>>, Traversable<_5, Tuple5<_1, _2, _3, _4, ?>> { @@ -169,12 +171,28 @@ public Tuple5<_2, _1, _3, _4, _5> invert() { return tuple(_2, _1, _3, _4, _5); } + /** + * {@inheritDoc} + */ + @Override + public _5 extract() { + return _5; + } + + /** + * {@inheritDoc} + */ + @Override + public Tuple5<_1, _2, _3, _4, B> extendImpl(Fn1>, ? extends B> f) { + return tuple(_1, _2, _3, _4, f.apply(this)); + } + /** * {@inheritDoc} */ @Override public <_5Prime> Tuple5<_1, _2, _3, _4, _5Prime> fmap(Fn1 fn) { - return MonadRec.super.<_5Prime>fmap(fn).coerce(); + return BimonadRec.super.<_5Prime>fmap(fn).coerce(); } /** @@ -216,7 +234,7 @@ public <_5Prime> Tuple5<_1, _2, _3, _4, _5Prime> pure(_5Prime _5Prime) { @Override public <_5Prime> Tuple5<_1, _2, _3, _4, _5Prime> zip( Applicative, Tuple5<_1, _2, _3, _4, ?>> appFn) { - return MonadRec.super.zip(appFn).coerce(); + return BimonadRec.super.zip(appFn).coerce(); } /** @@ -225,7 +243,7 @@ public <_5Prime> Tuple5<_1, _2, _3, _4, _5Prime> zip( @Override public <_5Prime> Lazy> lazyZip( Lazy, Tuple5<_1, _2, _3, _4, ?>>> lazyAppFn) { - return MonadRec.super.lazyZip(lazyAppFn).fmap(Monad<_5Prime, Tuple5<_1, _2, _3, _4, ?>>::coerce); + return BimonadRec.super.lazyZip(lazyAppFn).fmap(Monad<_5Prime, Tuple5<_1, _2, _3, _4, ?>>::coerce); } /** @@ -233,7 +251,7 @@ public <_5Prime> Lazy> lazyZip( */ @Override public <_5Prime> Tuple5<_1, _2, _3, _4, _5Prime> discardL(Applicative<_5Prime, Tuple5<_1, _2, _3, _4, ?>> appB) { - return MonadRec.super.discardL(appB).coerce(); + return BimonadRec.super.discardL(appB).coerce(); } /** @@ -241,7 +259,7 @@ public <_5Prime> Tuple5<_1, _2, _3, _4, _5Prime> discardL(Applicative<_5Prime, T */ @Override public <_5Prime> Tuple5<_1, _2, _3, _4, _5> discardR(Applicative<_5Prime, Tuple5<_1, _2, _3, _4, ?>> appB) { - return MonadRec.super.discardR(appB).coerce(); + return BimonadRec.super.discardR(appB).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 72bbac9ba..d9eada308 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 @@ -3,6 +3,8 @@ import com.jnape.palatable.lambda.adt.Maybe; import com.jnape.palatable.lambda.adt.hlist.HList.HCons; import com.jnape.palatable.lambda.adt.product.Product6; +import com.jnape.palatable.lambda.bimonad.BimonadRec; +import com.jnape.palatable.lambda.comonad.Comonad; import com.jnape.palatable.lambda.functions.Fn1; import com.jnape.palatable.lambda.functions.builtin.fn2.Into; import com.jnape.palatable.lambda.functions.recursion.RecursiveResult; @@ -37,7 +39,7 @@ */ public class Tuple6<_1, _2, _3, _4, _5, _6> extends HCons<_1, Tuple5<_2, _3, _4, _5, _6>> implements Product6<_1, _2, _3, _4, _5, _6>, - MonadRec<_6, Tuple6<_1, _2, _3, _4, _5, ?>>, + BimonadRec<_6, Tuple6<_1, _2, _3, _4, _5, ?>>, Bifunctor<_5, _6, Tuple6<_1, _2, _3, _4, ?, ?>>, Traversable<_6, Tuple6<_1, _2, _3, _4, _5, ?>> { @@ -197,12 +199,28 @@ public Tuple6<_2, _1, _3, _4, _5, _6> invert() { return tuple(_2, _1, _3, _4, _5, _6); } + /** + * {@inheritDoc} + */ + @Override + public _6 extract() { + return _6; + } + + /** + * {@inheritDoc} + */ + @Override + public Tuple6<_1, _2, _3, _4, _5, B> extendImpl(Fn1>, ? extends B> f) { + return tuple(_1, _2, _3, _4, _5, f.apply(this)); + } + /** * {@inheritDoc} */ @Override public <_6Prime> Tuple6<_1, _2, _3, _4, _5, _6Prime> fmap(Fn1 fn) { - return MonadRec.super.<_6Prime>fmap(fn).coerce(); + return BimonadRec.super.<_6Prime>fmap(fn).coerce(); } /** @@ -245,7 +263,7 @@ public <_6Prime> Tuple6<_1, _2, _3, _4, _5, _6Prime> pure(_6Prime _6Prime) { @Override public <_6Prime> Tuple6<_1, _2, _3, _4, _5, _6Prime> zip( Applicative, Tuple6<_1, _2, _3, _4, _5, ?>> appFn) { - return MonadRec.super.zip(appFn).coerce(); + return BimonadRec.super.zip(appFn).coerce(); } /** @@ -254,7 +272,7 @@ public <_6Prime> Tuple6<_1, _2, _3, _4, _5, _6Prime> zip( @Override public <_6Prime> Lazy> lazyZip( Lazy, Tuple6<_1, _2, _3, _4, _5, ?>>> lazyAppFn) { - return MonadRec.super.lazyZip(lazyAppFn).fmap(Monad<_6Prime, Tuple6<_1, _2, _3, _4, _5, ?>>::coerce); + return BimonadRec.super.lazyZip(lazyAppFn).fmap(Monad<_6Prime, Tuple6<_1, _2, _3, _4, _5, ?>>::coerce); } /** @@ -263,7 +281,7 @@ public <_6Prime> Lazy> lazyZip( @Override public <_6Prime> Tuple6<_1, _2, _3, _4, _5, _6Prime> discardL( Applicative<_6Prime, Tuple6<_1, _2, _3, _4, _5, ?>> appB) { - return MonadRec.super.discardL(appB).coerce(); + return BimonadRec.super.discardL(appB).coerce(); } /** @@ -271,7 +289,7 @@ public <_6Prime> Tuple6<_1, _2, _3, _4, _5, _6Prime> discardL( */ @Override public <_6Prime> Tuple6<_1, _2, _3, _4, _5, _6> discardR(Applicative<_6Prime, Tuple6<_1, _2, _3, _4, _5, ?>> appB) { - return MonadRec.super.discardR(appB).coerce(); + return BimonadRec.super.discardR(appB).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 3a2f027b7..61e260d99 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 @@ -3,6 +3,8 @@ import com.jnape.palatable.lambda.adt.Maybe; import com.jnape.palatable.lambda.adt.hlist.HList.HCons; import com.jnape.palatable.lambda.adt.product.Product7; +import com.jnape.palatable.lambda.bimonad.BimonadRec; +import com.jnape.palatable.lambda.comonad.Comonad; import com.jnape.palatable.lambda.functions.Fn1; import com.jnape.palatable.lambda.functions.builtin.fn2.Into; import com.jnape.palatable.lambda.functions.recursion.RecursiveResult; @@ -39,7 +41,7 @@ */ public class Tuple7<_1, _2, _3, _4, _5, _6, _7> extends HCons<_1, Tuple6<_2, _3, _4, _5, _6, _7>> implements Product7<_1, _2, _3, _4, _5, _6, _7>, - MonadRec<_7, Tuple7<_1, _2, _3, _4, _5, _6, ?>>, + BimonadRec<_7, Tuple7<_1, _2, _3, _4, _5, _6, ?>>, Bifunctor<_6, _7, Tuple7<_1, _2, _3, _4, _5, ?, ?>>, Traversable<_7, Tuple7<_1, _2, _3, _4, _5, _6, ?>> { @@ -225,12 +227,28 @@ public Tuple7<_2, _1, _3, _4, _5, _6, _7> invert() { return tuple(_2, _1, _3, _4, _5, _6, _7); } + /** + * {@inheritDoc} + */ + @Override + public _7 extract() { + return _7; + } + + /** + * {@inheritDoc} + */ + @Override + public Tuple7<_1, _2, _3, _4, _5, _6, B> extendImpl(Fn1>, ? extends B> f) { + return tuple(_1, _2, _3, _4, _5, _6, f.apply(this)); + } + /** * {@inheritDoc} */ @Override public <_7Prime> Tuple7<_1, _2, _3, _4, _5, _6, _7Prime> fmap(Fn1 fn) { - return MonadRec.super.<_7Prime>fmap(fn).coerce(); + return BimonadRec.super.<_7Prime>fmap(fn).coerce(); } /** @@ -273,7 +291,7 @@ public <_7Prime> Tuple7<_1, _2, _3, _4, _5, _6, _7Prime> pure(_7Prime _7Prime) { @Override public <_7Prime> Tuple7<_1, _2, _3, _4, _5, _6, _7Prime> zip( Applicative, Tuple7<_1, _2, _3, _4, _5, _6, ?>> appFn) { - return MonadRec.super.zip(appFn).coerce(); + return BimonadRec.super.zip(appFn).coerce(); } /** @@ -282,7 +300,7 @@ public <_7Prime> Tuple7<_1, _2, _3, _4, _5, _6, _7Prime> zip( @Override public <_7Prime> Lazy> lazyZip( Lazy, Tuple7<_1, _2, _3, _4, _5, _6, ?>>> lazyAppFn) { - return MonadRec.super.lazyZip(lazyAppFn).fmap(Monad<_7Prime, Tuple7<_1, _2, _3, _4, _5, _6, ?>>::coerce); + return BimonadRec.super.lazyZip(lazyAppFn).fmap(Monad<_7Prime, Tuple7<_1, _2, _3, _4, _5, _6, ?>>::coerce); } /** @@ -291,7 +309,7 @@ public <_7Prime> Lazy> lazyZip( @Override public <_7Prime> Tuple7<_1, _2, _3, _4, _5, _6, _7Prime> discardL( Applicative<_7Prime, Tuple7<_1, _2, _3, _4, _5, _6, ?>> appB) { - return MonadRec.super.discardL(appB).coerce(); + return BimonadRec.super.discardL(appB).coerce(); } /** @@ -300,7 +318,7 @@ public <_7Prime> Tuple7<_1, _2, _3, _4, _5, _6, _7Prime> discardL( @Override public <_7Prime> Tuple7<_1, _2, _3, _4, _5, _6, _7> discardR( Applicative<_7Prime, Tuple7<_1, _2, _3, _4, _5, _6, ?>> appB) { - return MonadRec.super.discardR(appB).coerce(); + return BimonadRec.super.discardR(appB).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 c97cea46d..5d8a9c041 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 @@ -3,6 +3,8 @@ import com.jnape.palatable.lambda.adt.Maybe; import com.jnape.palatable.lambda.adt.hlist.HList.HCons; import com.jnape.palatable.lambda.adt.product.Product8; +import com.jnape.palatable.lambda.bimonad.BimonadRec; +import com.jnape.palatable.lambda.comonad.Comonad; import com.jnape.palatable.lambda.functions.Fn1; import com.jnape.palatable.lambda.functions.builtin.fn2.Into; import com.jnape.palatable.lambda.functions.recursion.RecursiveResult; @@ -41,7 +43,7 @@ */ public class Tuple8<_1, _2, _3, _4, _5, _6, _7, _8> extends HCons<_1, Tuple7<_2, _3, _4, _5, _6, _7, _8>> implements Product8<_1, _2, _3, _4, _5, _6, _7, _8>, - MonadRec<_8, Tuple8<_1, _2, _3, _4, _5, _6, _7, ?>>, + BimonadRec<_8, Tuple8<_1, _2, _3, _4, _5, _6, _7, ?>>, Bifunctor<_7, _8, Tuple8<_1, _2, _3, _4, _5, _6, ?, ?>>, Traversable<_8, Tuple8<_1, _2, _3, _4, _5, _6, _7, ?>> { @@ -253,12 +255,28 @@ public Tuple8<_2, _1, _3, _4, _5, _6, _7, _8> invert() { return tuple(_2, _1, _3, _4, _5, _6, _7, _8); } + /** + * {@inheritDoc} + */ + @Override + public _8 extract() { + return _8; + } + + /** + * {@inheritDoc} + */ + @Override + public Tuple8<_1, _2, _3, _4, _5, _6, _7, B> extendImpl(Fn1>, ? extends B> f) { + return tuple(_1, _2, _3, _4, _5, _6, _7, f.apply(this)); + } + /** * {@inheritDoc} */ @Override public <_8Prime> Tuple8<_1, _2, _3, _4, _5, _6, _7, _8Prime> fmap(Fn1 fn) { - return MonadRec.super.<_8Prime>fmap(fn).coerce(); + return BimonadRec.super.<_8Prime>fmap(fn).coerce(); } /** @@ -301,7 +319,7 @@ public <_8Prime> Tuple8<_1, _2, _3, _4, _5, _6, _7, _8Prime> pure(_8Prime _8Prim @Override public <_8Prime> Tuple8<_1, _2, _3, _4, _5, _6, _7, _8Prime> zip( Applicative, Tuple8<_1, _2, _3, _4, _5, _6, _7, ?>> appFn) { - return MonadRec.super.zip(appFn).coerce(); + return BimonadRec.super.zip(appFn).coerce(); } /** @@ -311,7 +329,7 @@ public <_8Prime> Tuple8<_1, _2, _3, _4, _5, _6, _7, _8Prime> zip( public <_8Prime> Lazy> lazyZip( Lazy, Tuple8<_1, _2, _3, _4, _5, _6, _7, ?>>> lazyAppFn) { - return MonadRec.super.lazyZip(lazyAppFn).fmap(Monad<_8Prime, Tuple8<_1, _2, _3, _4, _5, _6, _7, ?>>::coerce); + return BimonadRec.super.lazyZip(lazyAppFn).fmap(Monad<_8Prime, Tuple8<_1, _2, _3, _4, _5, _6, _7, ?>>::coerce); } /** @@ -320,7 +338,7 @@ public <_8Prime> Lazy> lazyZip( @Override public <_8Prime> Tuple8<_1, _2, _3, _4, _5, _6, _7, _8Prime> discardL( Applicative<_8Prime, Tuple8<_1, _2, _3, _4, _5, _6, _7, ?>> appB) { - return MonadRec.super.discardL(appB).coerce(); + return BimonadRec.super.discardL(appB).coerce(); } /** @@ -329,7 +347,7 @@ public <_8Prime> Tuple8<_1, _2, _3, _4, _5, _6, _7, _8Prime> discardL( @Override public <_8Prime> Tuple8<_1, _2, _3, _4, _5, _6, _7, _8> discardR( Applicative<_8Prime, Tuple8<_1, _2, _3, _4, _5, _6, _7, ?>> appB) { - return MonadRec.super.discardR(appB).coerce(); + return BimonadRec.super.discardR(appB).coerce(); } /** diff --git a/src/main/java/com/jnape/palatable/lambda/bimonad/BimonadRec.java b/src/main/java/com/jnape/palatable/lambda/bimonad/BimonadRec.java new file mode 100644 index 000000000..86199cb17 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/bimonad/BimonadRec.java @@ -0,0 +1,15 @@ +package com.jnape.palatable.lambda.bimonad; + +import com.jnape.palatable.lambda.comonad.Comonad; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.monad.MonadRec; + +public interface BimonadRec> extends MonadRec, Comonad { + /** + * {@inheritDoc} + */ + @Override + default BimonadRec fmap(Fn1 fn) { + return MonadRec.super.fmap(fn).coerce(); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functor/builtin/Env.java b/src/main/java/com/jnape/palatable/lambda/functor/builtin/Env.java index 2dabb2912..a30308132 100644 --- a/src/main/java/com/jnape/palatable/lambda/functor/builtin/Env.java +++ b/src/main/java/com/jnape/palatable/lambda/functor/builtin/Env.java @@ -1,17 +1,13 @@ package com.jnape.palatable.lambda.functor.builtin; -import com.jnape.palatable.lambda.adt.product.Product2; import com.jnape.palatable.lambda.comonad.Comonad; import com.jnape.palatable.lambda.comonad.builtin.ComonadEnv; import com.jnape.palatable.lambda.functions.Fn1; -import com.jnape.palatable.lambda.functions.builtin.fn1.Id; import com.jnape.palatable.lambda.functor.Bifunctor; -import com.jnape.palatable.lambda.functor.Functor; import java.util.Objects; import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; -import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; /** * A concrete implementation of the {@link ComonadEnv} interface. @@ -20,8 +16,8 @@ * @param the value type */ public final class Env implements Comonad>, ComonadEnv>, Bifunctor> { - private E env; - private A value; + private final E env; + private final A value; private Env(E e, A a) { this.env = e; @@ -32,11 +28,11 @@ private Env(E e, A a) { /** * Constructor function for an Env. * - * @param env the environment provided as context to value - * @param value the primary value to be extracted - * @param the type of the environment - * @param the type of the value - * @return a new instance of Env<E, A> + * @param env the environment provided as context to value + * @param value the primary value to be extracted + * @param the type of the environment + * @param the type of the value + * @return a new instance of Env<E, A> */ public static Env env(E env, A value) { return new Env<>(env, value); @@ -48,7 +44,7 @@ public static Env env(E env, A value) { @Override public final E ask() { return this.env; - }; + } /** * {@inheritDoc} 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 ba2945c7b..76e8fac63 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 @@ -1,5 +1,6 @@ package com.jnape.palatable.lambda.functor.builtin; +import com.jnape.palatable.lambda.bimonad.BimonadRec; import com.jnape.palatable.lambda.comonad.Comonad; import com.jnape.palatable.lambda.functions.Fn1; import com.jnape.palatable.lambda.functions.recursion.RecursiveResult; @@ -19,7 +20,7 @@ * * @param the value type */ -public final class Identity implements MonadRec>, Traversable>, Comonad> { +public final class Identity implements BimonadRec>, Traversable> { private final A a; @@ -65,7 +66,7 @@ public Comonad> extendImpl(Fn1 */ @Override public Identity fmap(Fn1 fn) { - return MonadRec.super.fmap(fn).coerce(); + return BimonadRec.super.fmap(fn).coerce(); } /** @@ -90,7 +91,7 @@ public Identity zip(Applicative, Identity> @Override public Lazy> lazyZip( Lazy, Identity>> lazyAppFn) { - return MonadRec.super.lazyZip(lazyAppFn).fmap(Monad>::coerce); + return BimonadRec.super.lazyZip(lazyAppFn).fmap(Monad>::coerce); } /** @@ -98,7 +99,7 @@ public Lazy> lazyZip( */ @Override public Identity discardL(Applicative> appB) { - return MonadRec.super.discardL(appB).coerce(); + return BimonadRec.super.discardL(appB).coerce(); } /** @@ -106,7 +107,7 @@ public Identity discardL(Applicative> appB) { */ @Override public Identity discardR(Applicative> appB) { - return MonadRec.super.discardR(appB).coerce(); + return BimonadRec.super.discardR(appB).coerce(); } /** diff --git a/src/main/java/com/jnape/palatable/lambda/functor/builtin/Store.java b/src/main/java/com/jnape/palatable/lambda/functor/builtin/Store.java index 230eef0c4..fe099d7d9 100644 --- a/src/main/java/com/jnape/palatable/lambda/functor/builtin/Store.java +++ b/src/main/java/com/jnape/palatable/lambda/functor/builtin/Store.java @@ -14,8 +14,8 @@ * @param the retrieved value type */ public final class Store implements Comonad>, ComonadStore> { - private Fn1 storage; - private S cursor; + private final Fn1 storage; + private final S cursor; private Store(Fn1 f, S s) { this.storage = f; diff --git a/src/main/java/com/jnape/palatable/lambda/functor/builtin/Traced.java b/src/main/java/com/jnape/palatable/lambda/functor/builtin/Traced.java index 01d9ce542..510865aa0 100644 --- a/src/main/java/com/jnape/palatable/lambda/functor/builtin/Traced.java +++ b/src/main/java/com/jnape/palatable/lambda/functor/builtin/Traced.java @@ -13,8 +13,8 @@ * @param the type of the output to the {@link Traced#trace} function */ public final class Traced, B> implements Comonad>, ComonadTraced> { - private Fn1 trace; - private Monoid aMonoid; + private final Fn1 trace; + private final Monoid aMonoid; private Traced(Fn1 t, Monoid m) { this.trace = t; @@ -24,12 +24,12 @@ private Traced(Fn1 t, Monoid m) { /** * Constructor function for Traced. * - * @param t the trace function - * @param m the {@link Monoid} instance for the input to t - * @param the input type of t - * @param the {@link Monoid} type of m - * @param the output type of t - * @return a new instance of Traced<A, M, B> + * @param t the trace function + * @param m the {@link Monoid} instance for the input to t + * @param the input type of t + * @param the {@link Monoid} type of m + * @param the output type of t + * @return a new instance of Traced<A, M, B> */ public static , B> Traced traced(Fn1 t, Monoid m) { return new Traced<>(t, m); diff --git a/src/test/java/com/jnape/palatable/lambda/adt/hlist/SingletonHListTest.java b/src/test/java/com/jnape/palatable/lambda/adt/hlist/SingletonHListTest.java index 7a577f2ce..e50a0a237 100644 --- a/src/test/java/com/jnape/palatable/lambda/adt/hlist/SingletonHListTest.java +++ b/src/test/java/com/jnape/palatable/lambda/adt/hlist/SingletonHListTest.java @@ -5,15 +5,9 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import testsupport.traits.ApplicativeLaws; -import testsupport.traits.FunctorLaws; -import testsupport.traits.MonadLaws; -import testsupport.traits.MonadRecLaws; -import testsupport.traits.TraversableLaws; +import testsupport.traits.*; -import static com.jnape.palatable.lambda.adt.hlist.HList.nil; -import static com.jnape.palatable.lambda.adt.hlist.HList.singletonHList; -import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; +import static com.jnape.palatable.lambda.adt.hlist.HList.*; import static com.jnape.palatable.lambda.adt.hlist.SingletonHList.pureSingletonHList; import static org.junit.Assert.assertEquals; @@ -27,7 +21,7 @@ public void setUp() { singletonHList = new SingletonHList<>(1); } - @TestTraits({FunctorLaws.class, ApplicativeLaws.class, MonadLaws.class, TraversableLaws.class, MonadRecLaws.class}) + @TestTraits({FunctorLaws.class, ApplicativeLaws.class, MonadLaws.class, TraversableLaws.class, MonadRecLaws.class, ComonadLaws.class}) public SingletonHList testSubject() { return singletonHList("one"); } 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 31dc2f285..d22fa834d 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 @@ -6,13 +6,7 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import testsupport.traits.ApplicativeLaws; -import testsupport.traits.BifunctorLaws; -import testsupport.traits.FunctorLaws; -import testsupport.traits.MonadLaws; -import testsupport.traits.MonadRecLaws; -import testsupport.traits.MonadWriterLaws; -import testsupport.traits.TraversableLaws; +import testsupport.traits.*; import java.util.HashMap; import java.util.Map; @@ -26,10 +20,7 @@ import static java.util.Collections.emptyList; import static java.util.Collections.singletonList; import static org.junit.Assert.assertEquals; -import static org.mockito.Mockito.only; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.*; @RunWith(Traits.class) public class Tuple2Test { @@ -48,7 +39,8 @@ public void setUp() { MonadRecLaws.class, MonadWriterLaws.class, BifunctorLaws.class, - TraversableLaws.class}) + TraversableLaws.class, + ComonadLaws.class}) public Tuple2 testSubject() { return tuple("one", 2); } diff --git a/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple3Test.java b/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple3Test.java index 55842ee8b..c60a10d9b 100644 --- a/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple3Test.java +++ b/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple3Test.java @@ -6,12 +6,7 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import testsupport.traits.ApplicativeLaws; -import testsupport.traits.BifunctorLaws; -import testsupport.traits.FunctorLaws; -import testsupport.traits.MonadLaws; -import testsupport.traits.MonadRecLaws; -import testsupport.traits.TraversableLaws; +import testsupport.traits.*; import static com.jnape.palatable.lambda.adt.Maybe.just; import static com.jnape.palatable.lambda.adt.Maybe.nothing; @@ -43,7 +38,8 @@ public void setUp() { MonadLaws.class, MonadRecLaws.class, BifunctorLaws.class, - TraversableLaws.class}) + TraversableLaws.class, + ComonadLaws.class}) public Tuple3 testSubject() { return tuple("one", 2, 3d); } diff --git a/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple4Test.java b/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple4Test.java index bb5380891..cbc9b6898 100644 --- a/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple4Test.java +++ b/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple4Test.java @@ -6,12 +6,7 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import testsupport.traits.ApplicativeLaws; -import testsupport.traits.BifunctorLaws; -import testsupport.traits.FunctorLaws; -import testsupport.traits.MonadLaws; -import testsupport.traits.MonadRecLaws; -import testsupport.traits.TraversableLaws; +import testsupport.traits.*; import static com.jnape.palatable.lambda.adt.Maybe.just; import static com.jnape.palatable.lambda.adt.Maybe.nothing; @@ -42,7 +37,8 @@ public void setUp() { MonadLaws.class, MonadRecLaws.class, BifunctorLaws.class, - TraversableLaws.class}) + TraversableLaws.class, + ComonadLaws.class}) public Tuple4 testSubject() { return tuple("one", 2, 3d, 4f); } 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 f156f9072..2bd4a5865 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 @@ -7,12 +7,7 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import testsupport.traits.ApplicativeLaws; -import testsupport.traits.BifunctorLaws; -import testsupport.traits.FunctorLaws; -import testsupport.traits.MonadLaws; -import testsupport.traits.MonadRecLaws; -import testsupport.traits.TraversableLaws; +import testsupport.traits.*; import static com.jnape.palatable.lambda.adt.Maybe.just; import static com.jnape.palatable.lambda.adt.Maybe.nothing; @@ -22,10 +17,7 @@ import static java.util.Collections.emptyList; import static java.util.Collections.singletonList; import static org.junit.Assert.assertEquals; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.*; @RunWith(Traits.class) public class Tuple5Test { @@ -43,7 +35,8 @@ public void setUp() { MonadLaws.class, MonadRecLaws.class, BifunctorLaws.class, - TraversableLaws.class}) + TraversableLaws.class, + ComonadLaws.class}) public Tuple5 testSubject() { return tuple("one", 2, 3d, 4f, '5'); } 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 a148ae972..10e236f21 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 @@ -7,12 +7,7 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import testsupport.traits.ApplicativeLaws; -import testsupport.traits.BifunctorLaws; -import testsupport.traits.FunctorLaws; -import testsupport.traits.MonadLaws; -import testsupport.traits.MonadRecLaws; -import testsupport.traits.TraversableLaws; +import testsupport.traits.*; import static com.jnape.palatable.lambda.adt.Maybe.just; import static com.jnape.palatable.lambda.adt.Maybe.nothing; @@ -22,10 +17,7 @@ import static java.util.Collections.emptyList; import static java.util.Collections.singletonList; import static org.junit.Assert.assertEquals; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.*; @RunWith(Traits.class) public class Tuple6Test { @@ -43,7 +35,8 @@ public void setUp() { MonadLaws.class, MonadRecLaws.class, BifunctorLaws.class, - TraversableLaws.class}) + TraversableLaws.class, + ComonadLaws.class}) public Tuple6 testSubject() { return tuple("one", 2, 3d, 4f, '5', (byte) 6); } 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 f1cbb7f3a..f768fbe37 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 @@ -7,12 +7,7 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import testsupport.traits.ApplicativeLaws; -import testsupport.traits.BifunctorLaws; -import testsupport.traits.FunctorLaws; -import testsupport.traits.MonadLaws; -import testsupport.traits.MonadRecLaws; -import testsupport.traits.TraversableLaws; +import testsupport.traits.*; import static com.jnape.palatable.lambda.adt.Maybe.just; import static com.jnape.palatable.lambda.adt.Maybe.nothing; @@ -22,10 +17,7 @@ import static java.util.Collections.emptyList; import static java.util.Collections.singletonList; import static org.junit.Assert.assertEquals; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.*; @RunWith(Traits.class) public class Tuple7Test { @@ -43,7 +35,8 @@ public void setUp() { MonadLaws.class, MonadRecLaws.class, BifunctorLaws.class, - TraversableLaws.class}) + TraversableLaws.class, + ComonadLaws.class}) public Tuple7 testSubject() { return tuple("one", 2, 3d, 4f, '5', (byte) 6, 7L); } 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 dacab1869..5ab7c7334 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 @@ -7,12 +7,7 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import testsupport.traits.ApplicativeLaws; -import testsupport.traits.BifunctorLaws; -import testsupport.traits.FunctorLaws; -import testsupport.traits.MonadLaws; -import testsupport.traits.MonadRecLaws; -import testsupport.traits.TraversableLaws; +import testsupport.traits.*; import java.time.LocalDate; @@ -24,10 +19,7 @@ import static java.util.Collections.emptyList; import static java.util.Collections.singletonList; import static org.junit.Assert.assertEquals; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.*; @RunWith(Traits.class) public class Tuple8Test { @@ -47,7 +39,8 @@ public void setUp() { MonadLaws.class, MonadRecLaws.class, BifunctorLaws.class, - TraversableLaws.class}) + TraversableLaws.class, + ComonadLaws.class}) public Tuple8 testSubject() { return tuple("one", 2, 3d, 4f, '5', (byte) 6, 7L, (short) 65535); } From 172250972370076569bcacd51fa680d975878ba3 Mon Sep 17 00:00:00 2001 From: Michael Anderson Date: Fri, 24 Jul 2020 09:17:03 -0500 Subject: [PATCH 39/39] Fix Traced doc formatting --- .../palatable/lambda/functor/builtin/Traced.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/jnape/palatable/lambda/functor/builtin/Traced.java b/src/main/java/com/jnape/palatable/lambda/functor/builtin/Traced.java index 510865aa0..a18d31803 100644 --- a/src/main/java/com/jnape/palatable/lambda/functor/builtin/Traced.java +++ b/src/main/java/com/jnape/palatable/lambda/functor/builtin/Traced.java @@ -24,12 +24,12 @@ private Traced(Fn1 t, Monoid m) { /** * Constructor function for Traced. * - * @param t the trace function - * @param m the {@link Monoid} instance for the input to t - * @param the input type of t - * @param the {@link Monoid} type of m - * @param the output type of t - * @return a new instance of Traced<A, M, B> + * @param t the trace function + * @param m the {@link Monoid} instance for the input to t + * @param the input type of t + * @param the {@link Monoid} type of m + * @param the output type of t + * @return a new instance of Traced<A, M, B> */ public static , B> Traced traced(Fn1 t, Monoid m) { return new Traced<>(t, m);