From 2046a612fd806671f3059a17f94e967f95daf796 Mon Sep 17 00:00:00 2001 From: jnape Date: Sat, 11 Feb 2017 15:32:59 -0600 Subject: [PATCH 01/24] [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 fb88cf903..6d651dc0c 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ lambda - 1.5.6 + 1.5.7-SNAPSHOT jar Lambda From 2980896724f790f1b21808efe5cf96765652cc30 Mon Sep 17 00:00:00 2001 From: jnape Date: Sat, 21 Jan 2017 14:29:54 -0600 Subject: [PATCH 02/24] adding unification parameter to Functor and friends --- .../jnape/palatable/lambda/adt/Either.java | 2 +- .../palatable/lambda/adt/choice/Choice2.java | 2 +- .../palatable/lambda/adt/choice/Choice3.java | 2 +- .../palatable/lambda/adt/choice/Choice4.java | 2 +- .../palatable/lambda/adt/choice/Choice5.java | 2 +- .../lambda/adt/hlist/SingletonHList.java | 2 +- .../palatable/lambda/adt/hlist/Tuple2.java | 2 +- .../palatable/lambda/adt/hlist/Tuple3.java | 2 +- .../palatable/lambda/adt/hlist/Tuple4.java | 2 +- .../palatable/lambda/adt/hlist/Tuple5.java | 2 +- .../jnape/palatable/lambda/functions/Fn1.java | 2 +- .../palatable/lambda/functor/Bifunctor.java | 13 ++++++------ .../palatable/lambda/functor/Functor.java | 5 +++-- .../palatable/lambda/functor/Profunctor.java | 15 ++++++------- .../lambda/functor/builtin/Const.java | 2 +- .../lambda/functor/builtin/Identity.java | 2 +- .../com/jnape/palatable/lambda/lens/Lens.java | 21 +++++++++++-------- .../palatable/lambda/lens/functions/Over.java | 2 +- .../palatable/lambda/lens/functions/View.java | 2 +- .../lambda/functor/BifunctorTest.java | 4 ++-- .../lambda/functor/ProfunctorTest.java | 4 ++-- .../jnape/palatable/lambda/lens/LensTest.java | 10 ++++----- .../InvocationRecordingBifunctor.java | 8 +++---- .../InvocationRecordingProfunctor.java | 6 +++--- 24 files changed, 61 insertions(+), 55 deletions(-) diff --git a/src/main/java/com/jnape/palatable/lambda/adt/Either.java b/src/main/java/com/jnape/palatable/lambda/adt/Either.java index 4d6c988c4..887ce8330 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/Either.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/Either.java @@ -25,7 +25,7 @@ * @param The left parameter type * @param The right parameter type */ -public abstract class Either implements CoProduct2, Functor, Bifunctor { +public abstract class Either implements CoProduct2, Functor>, Bifunctor { private Either() { } diff --git a/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice2.java b/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice2.java index 2fe7a03ed..e1579c565 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice2.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice2.java @@ -18,7 +18,7 @@ * @see Either * @see Choice3 */ -public abstract class Choice2 implements CoProduct2, Functor, Bifunctor { +public abstract class Choice2 implements CoProduct2, Functor>, Bifunctor { private Choice2() { } diff --git a/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice3.java b/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice3.java index 52119c199..7cb8b7b02 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice3.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice3.java @@ -17,7 +17,7 @@ * @see Choice2 * @see Choice4 */ -public abstract class Choice3 implements CoProduct3, Functor, Bifunctor { +public abstract class Choice3 implements CoProduct3, Functor>, Bifunctor> { private Choice3() { } diff --git a/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice4.java b/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice4.java index d9ef5efd6..eb3fa2467 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice4.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice4.java @@ -18,7 +18,7 @@ * @see Choice3 * @see Choice5 */ -public abstract class Choice4 implements CoProduct4, Functor, Bifunctor { +public abstract class Choice4 implements CoProduct4, Functor>, Bifunctor> { private Choice4() { } diff --git a/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice5.java b/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice5.java index 5603a4fe6..805657bc3 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice5.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice5.java @@ -18,7 +18,7 @@ * @param a type parameter representing the fifth possible type of this choice * @see Choice4 */ -public abstract class Choice5 implements CoProduct5, Functor, Bifunctor { +public abstract class Choice5 implements CoProduct5, Functor>, Bifunctor> { private Choice5() { } 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 6e62e1a66..c97b15f27 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 @@ -16,7 +16,7 @@ * @see Tuple4 * @see Tuple5 */ -public class SingletonHList<_1> extends HCons<_1, HNil> implements Functor<_1> { +public class SingletonHList<_1> extends HCons<_1, HNil> implements Functor<_1, SingletonHList> { SingletonHList(_1 _1) { super(_1, nil()); 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 95fc2b305..31478a292 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 @@ -19,7 +19,7 @@ * @see Tuple4 * @see Tuple5 */ -public class Tuple2<_1, _2> extends HCons<_1, SingletonHList<_2>> implements Map.Entry<_1, _2>, Functor<_2>, Bifunctor<_1, _2> { +public class Tuple2<_1, _2> extends HCons<_1, SingletonHList<_2>> implements Map.Entry<_1, _2>, Functor<_2, Tuple2<_1, ?>>, Bifunctor<_1, _2, Tuple2> { private final _1 _1; private final _2 _2; 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 aa07008ad..36685ebeb 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 @@ -19,7 +19,7 @@ * @see Tuple4 * @see Tuple5 */ -public class Tuple3<_1, _2, _3> extends HCons<_1, Tuple2<_2, _3>> implements Functor<_3>, Bifunctor<_2, _3> { +public class Tuple3<_1, _2, _3> extends HCons<_1, Tuple2<_2, _3>> implements Functor<_3, Tuple3<_1, _2, ?>>, Bifunctor<_2, _3, Tuple3<_1, ?, ?>> { private final _1 _1; private final _2 _2; private final _3 _3; 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 fc456c966..01a2c4a95 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 @@ -20,7 +20,7 @@ * @see Tuple3 * @see Tuple5 */ -public class Tuple4<_1, _2, _3, _4> extends HCons<_1, Tuple3<_2, _3, _4>> implements Functor<_4>, Bifunctor<_3, _4> { +public class Tuple4<_1, _2, _3, _4> extends HCons<_1, Tuple3<_2, _3, _4>> implements Functor<_4, Tuple4<_1, _2, _3, ?>>, Bifunctor<_3, _4, Tuple4<_1, _2, ?, ?>> { private final _1 _1; private final _2 _2; private final _3 _3; 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 101440fdb..2e31cd99a 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 @@ -20,7 +20,7 @@ * @see Tuple3 * @see Tuple4 */ -public class Tuple5<_1, _2, _3, _4, _5> extends HCons<_1, Tuple4<_2, _3, _4, _5>> implements Functor<_5>, Bifunctor<_4, _5> { +public class Tuple5<_1, _2, _3, _4, _5> extends HCons<_1, Tuple4<_2, _3, _4, _5>> implements Functor<_5, Tuple5<_1, _2, _3, _4, ?>>, Bifunctor<_4, _5, Tuple5<_1, _2, _3, ?, ?>> { private final _1 _1; private final _2 _2; private final _3 _3; 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 ccb0eafe3..b850daa42 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/Fn1.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/Fn1.java @@ -13,7 +13,7 @@ * @param The result type */ @FunctionalInterface -public interface Fn1 extends Functor, Profunctor, Function { +public interface Fn1 extends Functor>, Profunctor, Function { /** * Invoke this function with the given argument. diff --git a/src/main/java/com/jnape/palatable/lambda/functor/Bifunctor.java b/src/main/java/com/jnape/palatable/lambda/functor/Bifunctor.java index 6ab64dd96..b024bf37b 100644 --- a/src/main/java/com/jnape/palatable/lambda/functor/Bifunctor.java +++ b/src/main/java/com/jnape/palatable/lambda/functor/Bifunctor.java @@ -9,14 +9,15 @@ *

* For more information, read about Bifunctors. * - * @param The type of the left parameter - * @param The type of the right parameter + * @param The type of the left parameter + * @param The type of the right parameter + * @param The unification parameter * @see Functor * @see Profunctor * @see com.jnape.palatable.lambda.adt.hlist.Tuple2 */ @FunctionalInterface -public interface Bifunctor { +public interface Bifunctor { /** * Covariantly map over the left parameter. @@ -25,7 +26,7 @@ public interface Bifunctor { * @param fn the mapping function * @return a bifunctor over C (the new left parameter) and B (the same right parameter) */ - default Bifunctor biMapL(Function fn) { + default Bifunctor biMapL(Function fn) { return biMap(fn, id()); } @@ -37,7 +38,7 @@ default Bifunctor biMapL(Function fn) { * @param fn the mapping function * @return a bifunctor over A (the same left parameter) and C (the new right parameter) */ - default Bifunctor biMapR(Function fn) { + default Bifunctor biMapR(Function fn) { return biMap(id(), fn); } @@ -51,5 +52,5 @@ default Bifunctor biMapR(Function fn) { * @param rFn the right parameter mapping function * @return a bifunctor over C (the new left parameter type) and D (the new right parameter type) */ - Bifunctor biMap(Function lFn, Function rFn); + Bifunctor biMap(Function lFn, Function rFn); } diff --git a/src/main/java/com/jnape/palatable/lambda/functor/Functor.java b/src/main/java/com/jnape/palatable/lambda/functor/Functor.java index ef9b3b308..769aae816 100644 --- a/src/main/java/com/jnape/palatable/lambda/functor/Functor.java +++ b/src/main/java/com/jnape/palatable/lambda/functor/Functor.java @@ -12,6 +12,7 @@ * For more information, read about Functors. * * @param The type of the parameter + * @param The unification parameter * @see Bifunctor * @see Profunctor * @see Fn1 @@ -19,7 +20,7 @@ * @see com.jnape.palatable.lambda.adt.Either */ @FunctionalInterface -public interface Functor { +public interface Functor { /** * Covariantly transmute this functor's parameter using the given mapping function. Generally this method is @@ -29,5 +30,5 @@ public interface Functor { * @param fn the mapping function * @return a functor over B (the new parameter type) */ - Functor fmap(Function fn); + Functor fmap(Function fn); } diff --git a/src/main/java/com/jnape/palatable/lambda/functor/Profunctor.java b/src/main/java/com/jnape/palatable/lambda/functor/Profunctor.java index db8562172..968c16fd7 100644 --- a/src/main/java/com/jnape/palatable/lambda/functor/Profunctor.java +++ b/src/main/java/com/jnape/palatable/lambda/functor/Profunctor.java @@ -12,14 +12,15 @@ *

* For more information, read about Profunctors. * - * @param The type of the left parameter - * @param The type of the right parameter + * @param The type of the left parameter + * @param The type of the right parameter + * @param The unification parameter * @see Functor * @see Bifunctor * @see Fn1 */ @FunctionalInterface -public interface Profunctor { +public interface Profunctor { /** * Contravariantly map over the left parameter. @@ -28,7 +29,7 @@ public interface Profunctor { * @param fn the mapping function * @return a profunctor over Z (the new left parameter type) and C (the same right parameter type) */ - default Profunctor diMapL(Function fn) { + default Profunctor diMapL(Function fn) { return diMap(fn, id()); } @@ -40,7 +41,7 @@ default Profunctor diMapL(Function fn) { * @param fn the mapping function * @return a profunctor over A (the same left parameter type) and C (the new right parameter type) */ - default Profunctor diMapR(Function fn) { + default Profunctor diMapR(Function fn) { return diMap(id(), fn); } @@ -52,7 +53,7 @@ default Profunctor diMapR(Function fn) { * @param the new right parameter type * @param lFn the left parameter mapping function * @param rFn the right parameter mapping function - * @return a profunctor over Z (the new left parameter type) and C (the new right parameter tyep) + * @return a profunctor over Z (the new left parameter type) and C (the new right parameter type) */ - Profunctor diMap(Function lFn, Function rFn); + Profunctor diMap(Function lFn, Function rFn); } diff --git a/src/main/java/com/jnape/palatable/lambda/functor/builtin/Const.java b/src/main/java/com/jnape/palatable/lambda/functor/builtin/Const.java index e19261415..a4b997b58 100644 --- a/src/main/java/com/jnape/palatable/lambda/functor/builtin/Const.java +++ b/src/main/java/com/jnape/palatable/lambda/functor/builtin/Const.java @@ -14,7 +14,7 @@ * @param the left parameter type, and the type of the stored value * @param the right (phantom) parameter type */ -public final class Const implements Functor, Bifunctor { +public final class Const implements Functor>, Bifunctor { private final A a; diff --git a/src/main/java/com/jnape/palatable/lambda/functor/builtin/Identity.java b/src/main/java/com/jnape/palatable/lambda/functor/builtin/Identity.java index 04160b05f..c385df320 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 @@ -9,7 +9,7 @@ * * @param the value type */ -public final class Identity implements Functor { +public final class Identity implements Functor { private final A a; diff --git a/src/main/java/com/jnape/palatable/lambda/lens/Lens.java b/src/main/java/com/jnape/palatable/lambda/lens/Lens.java index 62b24ccfb..f3aed795f 100644 --- a/src/main/java/com/jnape/palatable/lambda/lens/Lens.java +++ b/src/main/java/com/jnape/palatable/lambda/lens/Lens.java @@ -132,9 +132,10 @@ * @param the type of the "smaller" update value */ @FunctionalInterface -public interface Lens extends Functor { +public interface Lens extends Functor> { - , FB extends Functor> FT apply(Function fn, S s); + , FB extends Functor> FT apply( + Function fn, S s); /** * Fix this lens against some functor, producing a non-polymorphic runnable lens as an {@link Fn2}. @@ -146,7 +147,7 @@ public interface Lens extends Functor { * @param the type of the lifted B * @return the lens, "fixed" to the functor */ - default , FB extends Functor> Fixed fix() { + default , FB extends Functor> Fixed fix() { return this::apply; } @@ -239,8 +240,9 @@ static Lens lens(Function gette return new Lens() { @Override @SuppressWarnings("unchecked") - public , FB extends Functor> FT apply(Function fn, - S s) { + public , FB extends Functor> FT apply( + Function fn, + S s) { return (FT) fn.apply(getter.apply(s)).fmap(b -> setter.apply(s, b)); } }; @@ -272,7 +274,7 @@ static Lens.Simple simpleLens(Function gett interface Simple extends Lens { @Override - default , FA extends Functor> Fixed fix() { + default , FA extends Functor> Fixed fix() { return this::apply; } @@ -294,8 +296,8 @@ default Lens.Simple andThen(Lens.Simple f) { * @param the type of the lifted A */ @FunctionalInterface - interface Fixed, FA extends Functor> - extends Lens.Fixed { + interface Fixed, FA extends Functor> + extends Lens.Fixed { } } @@ -307,11 +309,12 @@ interface Fixed, FA extends Functor> * @param the type of the "larger" value for putting * @param the type of the "smaller" value that is read * @param the type of the "smaller" update value + * @param the functor unification type between FT and FB * @param the type of the lifted T * @param the type of the lifted B */ @FunctionalInterface - interface Fixed, FB extends Functor> + interface Fixed, FB extends Functor> extends Fn2, S, FT> { } } diff --git a/src/main/java/com/jnape/palatable/lambda/lens/functions/Over.java b/src/main/java/com/jnape/palatable/lambda/lens/functions/Over.java index e9c4aadd6..131aa5046 100644 --- a/src/main/java/com/jnape/palatable/lambda/lens/functions/Over.java +++ b/src/main/java/com/jnape/palatable/lambda/lens/functions/Over.java @@ -32,7 +32,7 @@ private Over() { @Override public T apply(Lens lens, Function fn, S s) { - return lens., Identity>fix() + return lens., Identity>fix() .apply(fn.andThen((Function>) Identity::new), s) .runIdentity(); } diff --git a/src/main/java/com/jnape/palatable/lambda/lens/functions/View.java b/src/main/java/com/jnape/palatable/lambda/lens/functions/View.java index 8cab0c403..86e4990a0 100644 --- a/src/main/java/com/jnape/palatable/lambda/lens/functions/View.java +++ b/src/main/java/com/jnape/palatable/lambda/lens/functions/View.java @@ -27,7 +27,7 @@ private View() { @Override public A apply(Lens lens, S s) { - return lens., Const>fix().apply(Const::new, s).runConst(); + return lens., Const, Const>fix().apply(Const::new, s).runConst(); } @SuppressWarnings("unchecked") diff --git a/src/test/java/com/jnape/palatable/lambda/functor/BifunctorTest.java b/src/test/java/com/jnape/palatable/lambda/functor/BifunctorTest.java index afc3c0a8e..ce89ba26e 100644 --- a/src/test/java/com/jnape/palatable/lambda/functor/BifunctorTest.java +++ b/src/test/java/com/jnape/palatable/lambda/functor/BifunctorTest.java @@ -15,7 +15,7 @@ public class BifunctorTest { @Test public void biMapLUsesIdentityForRightBiMapFunction() { AtomicReference rightInvocation = new AtomicReference<>(); - Bifunctor bifunctor = new InvocationRecordingBifunctor<>(new AtomicReference<>(), rightInvocation); + Bifunctor bifunctor = new InvocationRecordingBifunctor<>(new AtomicReference<>(), rightInvocation); bifunctor.biMapL(String::toUpperCase); assertThat(rightInvocation.get(), is(id())); } @@ -23,7 +23,7 @@ public void biMapLUsesIdentityForRightBiMapFunction() { @Test public void biMapRUsesIdentityForLeftBiMapFunction() { AtomicReference leftInvocation = new AtomicReference<>(); - Bifunctor bifunctor = new InvocationRecordingBifunctor<>(leftInvocation, new AtomicReference<>()); + Bifunctor bifunctor = new InvocationRecordingBifunctor<>(leftInvocation, new AtomicReference<>()); bifunctor.biMapR(String::valueOf); assertThat(leftInvocation.get(), is(id())); } diff --git a/src/test/java/com/jnape/palatable/lambda/functor/ProfunctorTest.java b/src/test/java/com/jnape/palatable/lambda/functor/ProfunctorTest.java index 5e552f7e0..0f7358250 100644 --- a/src/test/java/com/jnape/palatable/lambda/functor/ProfunctorTest.java +++ b/src/test/java/com/jnape/palatable/lambda/functor/ProfunctorTest.java @@ -15,7 +15,7 @@ public class ProfunctorTest { @Test public void diMapLUsesIdentityForRightDiMapFunction() { AtomicReference rightInvocation = new AtomicReference<>(); - Profunctor profunctor = new InvocationRecordingProfunctor<>(new AtomicReference<>(), rightInvocation); + Profunctor profunctor = new InvocationRecordingProfunctor<>(new AtomicReference<>(), rightInvocation); profunctor.diMapL(Object::toString); assertThat(rightInvocation.get(), is(id())); } @@ -23,7 +23,7 @@ public void diMapLUsesIdentityForRightDiMapFunction() { @Test public void diMapRUsesIdentityForLeftDiMapFunction() { AtomicReference leftInvocation = new AtomicReference<>(); - Profunctor profunctor = new InvocationRecordingProfunctor<>(leftInvocation, new AtomicReference<>()); + Profunctor profunctor = new InvocationRecordingProfunctor<>(leftInvocation, new AtomicReference<>()); profunctor.diMapR(String::valueOf); assertThat(leftInvocation.get(), is(id())); } diff --git a/src/test/java/com/jnape/palatable/lambda/lens/LensTest.java b/src/test/java/com/jnape/palatable/lambda/lens/LensTest.java index c7e1419a9..f48e30cef 100644 --- a/src/test/java/com/jnape/palatable/lambda/lens/LensTest.java +++ b/src/test/java/com/jnape/palatable/lambda/lens/LensTest.java @@ -27,13 +27,13 @@ public class LensTest { @Test public void setsUnderIdentity() { - Set ints = LENS.>, Identity>apply(s -> new Identity<>(s.length()), asList("foo", "bar", "baz")).runIdentity(); + Set ints = LENS.>, Identity>apply(s -> new Identity<>(s.length()), asList("foo", "bar", "baz")).runIdentity(); assertEquals(singleton(3), ints); } @Test public void viewsUnderConst() { - Integer i = LENS.>, Const>apply(s -> new Const<>(s.length()), asList("foo", "bar", "baz")).runConst(); + Integer i = LENS., Const>, Const>apply(s -> new Const<>(s.length()), asList("foo", "bar", "baz")).runConst(); assertEquals((Integer) 3, i); } @@ -42,8 +42,8 @@ public void fix() { Fn1> fn = s -> new Const<>(s.length()); List s = singletonList("foo"); - Integer fixedLensResult = LENS.>, Const>fix().apply(fn, s).runConst(); - Integer unfixedLensResult = LENS.>, Const>apply(fn, s).runConst(); + Integer fixedLensResult = LENS., Const>, Const>fix().apply(fn, s).runConst(); + Integer unfixedLensResult = LENS., Const>, Const>apply(fn, s).runConst(); assertEquals(unfixedLensResult, fixedLensResult); } @@ -62,7 +62,7 @@ public void mapsIndividuallyOverParameters() { .mapA(Optional::ofNullable) .mapB((Optional optI) -> optI.orElse(-1)); - Lens.Fixed, Optional, Optional, Optional, Identity>, Identity>> fixed = theGambit.fix(); + Lens.Fixed, Optional, Optional, Optional, Identity, Identity>, Identity>> fixed = theGambit.fix(); assertEquals(Optional.of(true), fixed.apply(optC -> new Identity<>(optC.map(c -> parseInt(Character.toString(c)))), Optional.of("321")).runIdentity()); } diff --git a/src/test/java/testsupport/applicatives/InvocationRecordingBifunctor.java b/src/test/java/testsupport/applicatives/InvocationRecordingBifunctor.java index 2ef23f622..e1ab75465 100644 --- a/src/test/java/testsupport/applicatives/InvocationRecordingBifunctor.java +++ b/src/test/java/testsupport/applicatives/InvocationRecordingBifunctor.java @@ -5,7 +5,7 @@ import java.util.concurrent.atomic.AtomicReference; import java.util.function.Function; -public final class InvocationRecordingBifunctor implements Bifunctor { +public final class InvocationRecordingBifunctor implements Bifunctor { private final AtomicReference leftFn; private final AtomicReference rightFn; @@ -17,10 +17,10 @@ public InvocationRecordingBifunctor(AtomicReference leftFn, @Override @SuppressWarnings("unchecked") - public Bifunctor biMap(Function lFn, - Function rFn) { + public InvocationRecordingBifunctor biMap(Function lFn, + Function rFn) { leftFn.set(lFn); rightFn.set(rFn); - return (Bifunctor) this; + return (InvocationRecordingBifunctor) this; } } diff --git a/src/test/java/testsupport/applicatives/InvocationRecordingProfunctor.java b/src/test/java/testsupport/applicatives/InvocationRecordingProfunctor.java index dadcec960..1e7c690c4 100644 --- a/src/test/java/testsupport/applicatives/InvocationRecordingProfunctor.java +++ b/src/test/java/testsupport/applicatives/InvocationRecordingProfunctor.java @@ -5,7 +5,7 @@ import java.util.concurrent.atomic.AtomicReference; import java.util.function.Function; -public final class InvocationRecordingProfunctor implements Profunctor { +public final class InvocationRecordingProfunctor implements Profunctor { private final AtomicReference leftFn; private final AtomicReference rightFn; @@ -17,9 +17,9 @@ public InvocationRecordingProfunctor(AtomicReference leftFn, @Override @SuppressWarnings("unchecked") - public Profunctor diMap(Function lFn, Function rFn) { + public InvocationRecordingProfunctor diMap(Function lFn, Function rFn) { leftFn.set(lFn); rightFn.set(rFn); - return (Profunctor) this; + return (InvocationRecordingProfunctor) this; } } From 8910b621e17e33a0a3e1c7bec338e5764088112f Mon Sep 17 00:00:00 2001 From: jnape Date: Fri, 24 Mar 2017 11:32:17 -0500 Subject: [PATCH 03/24] adding FunctorLaws trait and replacing manual functor tests --- CHANGELOG.md | 3 ++ .../lambda/functor/builtin/Identity.java | 11 ++++++ .../palatable/lambda/adt/EitherTest.java | 24 +++++++----- .../lambda/adt/choice/Choice2Test.java | 21 +++++++--- .../lambda/adt/choice/Choice3Test.java | 28 +++++++++---- .../lambda/adt/choice/Choice4Test.java | 33 ++++++++++++---- .../lambda/adt/choice/Choice5Test.java | 39 ++++++++++++++----- .../lambda/adt/hlist/SingletonHListTest.java | 16 +++++--- .../lambda/adt/hlist/Tuple2Test.java | 15 ++++--- .../lambda/adt/hlist/Tuple3Test.java | 15 ++++--- .../lambda/adt/hlist/Tuple4Test.java | 15 ++++--- .../lambda/adt/hlist/Tuple5Test.java | 16 +++++--- .../palatable/lambda/functions/Fn1Test.java | 19 +++++---- .../functions/builtin/fn1/LastTest.java | 2 +- .../lambda/functor/builtin/ConstTest.java | 11 ++++-- .../lambda/functor/builtin/IdentityTest.java | 14 ++++--- .../iterators/DroppingIteratorTest.java | 3 +- .../PredicatedTakingIteratorTest.java | 5 ++- .../iterators/ReversingIteratorTest.java | 12 +++--- .../iterators/RewindableIteratorTest.java | 5 +-- .../jnape/palatable/lambda/lens/LensTest.java | 17 +++++--- .../java/testsupport/EqualityAwareFn1.java | 38 ++++++++++++++++++ .../java/testsupport/EqualityAwareLens.java | 37 ++++++++++++++++++ .../java/testsupport/traits/FunctorLaws.java | 39 +++++++++++++++++++ 24 files changed, 337 insertions(+), 101 deletions(-) create mode 100644 src/test/java/testsupport/EqualityAwareFn1.java create mode 100644 src/test/java/testsupport/EqualityAwareLens.java create mode 100644 src/test/java/testsupport/traits/FunctorLaws.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 8ebcf9408..1a8d11fce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,9 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/). ## [Unreleased] +### Changed +- `Functor`, `Bifunctor`, and `Profunctor` (as well as all instances) get a unification parameter +- `Identity` supports value equality ## [1.5.6] - 2017-02-11 ### Added 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 c385df320..bb98e41a5 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 @@ -2,6 +2,7 @@ import com.jnape.palatable.lambda.functor.Functor; +import java.util.Objects; import java.util.function.Function; /** @@ -37,4 +38,14 @@ public A runIdentity() { public Identity fmap(Function fn) { return new Identity<>(fn.apply(a)); } + + @Override + public boolean equals(Object other) { + return other instanceof Identity && Objects.equals(a, ((Identity) other).a); + } + + @Override + public int hashCode() { + return Objects.hash(a); + } } diff --git a/src/test/java/com/jnape/palatable/lambda/adt/EitherTest.java b/src/test/java/com/jnape/palatable/lambda/adt/EitherTest.java index 165d9b019..5afd36b03 100644 --- a/src/test/java/com/jnape/palatable/lambda/adt/EitherTest.java +++ b/src/test/java/com/jnape/palatable/lambda/adt/EitherTest.java @@ -1,8 +1,12 @@ package com.jnape.palatable.lambda.adt; +import com.jnape.palatable.traitor.annotations.TestTraits; +import com.jnape.palatable.traitor.runners.Traits; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; +import org.junit.runner.RunWith; +import testsupport.traits.FunctorLaws; import java.util.Optional; import java.util.concurrent.atomic.AtomicInteger; @@ -16,11 +20,22 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThat; +@RunWith(Traits.class) public class EitherTest { @Rule public ExpectedException thrown = ExpectedException.none(); + @TestTraits({FunctorLaws.class}) + public Either testRightTraits() { + return right(1); + } + + @TestTraits({FunctorLaws.class}) + public Either testLeftTraits() { + return left("foo"); + } + @Test public void recoverLiftsLeftAndFlattensRight() { Either left = left("foo"); @@ -150,15 +165,6 @@ public void fromOptionalDoesNotEvaluateLeftFnForRight() { assertThat(atomicInteger.get(), is(0)); } - @Test - public void functorProperties() { - Either left = left("foo"); - Either right = right(1); - - assertThat(left.fmap(r -> r + 1), is(left)); - assertThat(right.fmap(r -> r + 1), is(right(2))); - } - @Test public void bifunctorProperties() { Either left = left("foo"); diff --git a/src/test/java/com/jnape/palatable/lambda/adt/choice/Choice2Test.java b/src/test/java/com/jnape/palatable/lambda/adt/choice/Choice2Test.java index d7597d9ab..47ac423cd 100644 --- a/src/test/java/com/jnape/palatable/lambda/adt/choice/Choice2Test.java +++ b/src/test/java/com/jnape/palatable/lambda/adt/choice/Choice2Test.java @@ -1,12 +1,17 @@ package com.jnape.palatable.lambda.adt.choice; +import com.jnape.palatable.traitor.annotations.TestTraits; +import com.jnape.palatable.traitor.runners.Traits; import org.junit.Before; import org.junit.Test; +import org.junit.runner.RunWith; +import testsupport.traits.FunctorLaws; import static com.jnape.palatable.lambda.adt.choice.Choice2.a; import static com.jnape.palatable.lambda.adt.choice.Choice2.b; import static org.junit.Assert.assertEquals; +@RunWith(Traits.class) public class Choice2Test { private Choice2 a; @@ -18,18 +23,22 @@ public void setUp() { b = b(true); } + @TestTraits({FunctorLaws.class}) + public Choice2 testSubjectA() { + return a("foo"); + } + + @TestTraits({FunctorLaws.class}) + public Choice2 testSubjectB() { + return b(1); + } + @Test public void divergeStaysInChoice() { assertEquals(Choice3.a(1), a.diverge()); assertEquals(Choice3.b(true), b.diverge()); } - @Test - public void functorProperties() { - assertEquals(a, a.fmap(bool -> !bool)); - assertEquals(b(false), b.fmap(bool -> !bool)); - } - @Test public void bifunctorProperties() { assertEquals(a(-1), a.biMap(i -> i * -1, bool -> !bool)); diff --git a/src/test/java/com/jnape/palatable/lambda/adt/choice/Choice3Test.java b/src/test/java/com/jnape/palatable/lambda/adt/choice/Choice3Test.java index 0b4b0a9f8..f188f2139 100644 --- a/src/test/java/com/jnape/palatable/lambda/adt/choice/Choice3Test.java +++ b/src/test/java/com/jnape/palatable/lambda/adt/choice/Choice3Test.java @@ -1,12 +1,18 @@ package com.jnape.palatable.lambda.adt.choice; +import com.jnape.palatable.traitor.annotations.TestTraits; +import com.jnape.palatable.traitor.runners.Traits; import org.junit.Before; import org.junit.Test; +import org.junit.runner.RunWith; +import testsupport.traits.FunctorLaws; +import static com.jnape.palatable.lambda.adt.choice.Choice3.a; import static com.jnape.palatable.lambda.adt.choice.Choice3.b; import static com.jnape.palatable.lambda.adt.choice.Choice3.c; import static org.junit.Assert.assertEquals; +@RunWith(Traits.class) public class Choice3Test { private Choice3 a; @@ -20,6 +26,21 @@ public void setUp() { c = Choice3.c(true); } + @TestTraits({FunctorLaws.class}) + public Choice3 testSubjectA() { + return a("foo"); + } + + @TestTraits({FunctorLaws.class}) + public Choice3 testSubjectB() { + return b(1); + } + + @TestTraits({FunctorLaws.class}) + public Choice3 testSubjectC() { + return c(true); + } + @Test public void convergeStaysInChoice() { assertEquals(Choice2.a(1), a.converge(c -> Choice2.b(c.toString()))); @@ -34,13 +55,6 @@ public void divergeStaysInChoice() { assertEquals(Choice4.c(true), c.diverge()); } - @Test - public void functorProperties() { - assertEquals(a, a.fmap(bool -> !bool)); - assertEquals(b, b.fmap(bool -> !bool)); - assertEquals(c(false), c.fmap(bool -> !bool)); - } - @Test public void bifunctorProperties() { assertEquals(a, a.biMap(String::toUpperCase, bool -> !bool)); diff --git a/src/test/java/com/jnape/palatable/lambda/adt/choice/Choice4Test.java b/src/test/java/com/jnape/palatable/lambda/adt/choice/Choice4Test.java index 5be43ca7c..93cd29130 100644 --- a/src/test/java/com/jnape/palatable/lambda/adt/choice/Choice4Test.java +++ b/src/test/java/com/jnape/palatable/lambda/adt/choice/Choice4Test.java @@ -1,7 +1,11 @@ package com.jnape.palatable.lambda.adt.choice; +import com.jnape.palatable.traitor.annotations.TestTraits; +import com.jnape.palatable.traitor.runners.Traits; import org.junit.Before; import org.junit.Test; +import org.junit.runner.RunWith; +import testsupport.traits.FunctorLaws; import static com.jnape.palatable.lambda.adt.choice.Choice4.a; import static com.jnape.palatable.lambda.adt.choice.Choice4.b; @@ -9,6 +13,7 @@ import static com.jnape.palatable.lambda.adt.choice.Choice4.d; import static org.junit.Assert.assertEquals; +@RunWith(Traits.class) public class Choice4Test { private Choice4 a; @@ -24,6 +29,26 @@ public void setUp() { d = d(4D); } + @TestTraits({FunctorLaws.class}) + public Choice4 testSubjectA() { + return a("foo"); + } + + @TestTraits({FunctorLaws.class}) + public Choice4 testSubjectB() { + return b(1); + } + + @TestTraits({FunctorLaws.class}) + public Choice4 testSubjectC() { + return c(true); + } + + @TestTraits({FunctorLaws.class}) + public Choice4 testSubjectD() { + return d('a'); + } + @Test public void convergeStaysInChoice() { assertEquals(Choice3.a(1), a.converge(d -> Choice3.b(d.toString()))); @@ -40,14 +65,6 @@ public void divergeStaysInChoice() { assertEquals(Choice5.d(4D), d.diverge()); } - @Test - public void functorProperties() { - assertEquals(a, a.fmap(d -> -d)); - assertEquals(b, b.fmap(d -> -d)); - assertEquals(c, c.fmap(d -> -d)); - assertEquals(d(-4D), d.fmap(d -> -d)); - } - @Test public void bifunctorProperties() { assertEquals(a, a.biMap(c -> !c, d -> -d)); diff --git a/src/test/java/com/jnape/palatable/lambda/adt/choice/Choice5Test.java b/src/test/java/com/jnape/palatable/lambda/adt/choice/Choice5Test.java index 6792fa4d2..65d911f9b 100644 --- a/src/test/java/com/jnape/palatable/lambda/adt/choice/Choice5Test.java +++ b/src/test/java/com/jnape/palatable/lambda/adt/choice/Choice5Test.java @@ -1,7 +1,11 @@ package com.jnape.palatable.lambda.adt.choice; +import com.jnape.palatable.traitor.annotations.TestTraits; +import com.jnape.palatable.traitor.runners.Traits; import org.junit.Before; import org.junit.Test; +import org.junit.runner.RunWith; +import testsupport.traits.FunctorLaws; import static com.jnape.palatable.lambda.adt.choice.Choice5.a; import static com.jnape.palatable.lambda.adt.choice.Choice5.b; @@ -10,6 +14,7 @@ import static com.jnape.palatable.lambda.adt.choice.Choice5.e; import static org.junit.Assert.assertEquals; +@RunWith(Traits.class) public class Choice5Test { private Choice5 a; @@ -27,6 +32,31 @@ public void setUp() { e = e('z'); } + @TestTraits({FunctorLaws.class}) + public Choice5 testSubjectA() { + return Choice5.a("foo"); + } + + @TestTraits({FunctorLaws.class}) + public Choice5 testSubjectB() { + return Choice5.b(1); + } + + @TestTraits({FunctorLaws.class}) + public Choice5 testSubjectC() { + return Choice5.c(true); + } + + @TestTraits({FunctorLaws.class}) + public Choice5 testSubjectD() { + return Choice5.d('a'); + } + + @TestTraits({FunctorLaws.class}) + public Choice5 testSubjectE() { + return Choice5.e(2d); + } + @Test public void convergeStaysInChoice() { assertEquals(Choice4.a(1), a.converge(e -> Choice4.b(e.toString()))); @@ -36,15 +66,6 @@ public void convergeStaysInChoice() { assertEquals(Choice4.b("z"), e.converge(e -> Choice4.b(e.toString()))); } - @Test - public void functorProperties() { - assertEquals(a, a.fmap(Character::toUpperCase)); - assertEquals(b, b.fmap(Character::toUpperCase)); - assertEquals(c, c.fmap(Character::toUpperCase)); - assertEquals(d, d.fmap(Character::toUpperCase)); - assertEquals(e('Z'), e.fmap(Character::toUpperCase)); - } - @Test public void bifunctorProperties() { assertEquals(a, a.biMap(d -> -d, Character::toUpperCase)); 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 18bc68a05..de4118de3 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 @@ -1,11 +1,17 @@ package com.jnape.palatable.lambda.adt.hlist; +import com.jnape.palatable.traitor.annotations.TestTraits; +import com.jnape.palatable.traitor.runners.Traits; import org.junit.Before; import org.junit.Test; +import org.junit.runner.RunWith; +import testsupport.traits.FunctorLaws; import static com.jnape.palatable.lambda.adt.hlist.HList.nil; +import static com.jnape.palatable.lambda.adt.hlist.HList.singletonHList; import static org.junit.Assert.assertEquals; +@RunWith(Traits.class) public class SingletonHListTest { private SingletonHList singletonHList; @@ -15,6 +21,11 @@ public void setUp() { singletonHList = new SingletonHList<>(1); } + @TestTraits({FunctorLaws.class}) + public SingletonHList testSubject() { + return singletonHList("one"); + } + @Test public void head() { assertEquals((Integer) 1, singletonHList.head()); @@ -29,9 +40,4 @@ public void tail() { public void cons() { assertEquals(new Tuple2<>("0", singletonHList), singletonHList.cons("0")); } - - @Test - public void functorProperties() { - assertEquals(new SingletonHList<>("1"), singletonHList.fmap(Object::toString)); - } } \ 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 b1ff6a325..d9991f56f 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 @@ -1,7 +1,11 @@ package com.jnape.palatable.lambda.adt.hlist; +import com.jnape.palatable.traitor.annotations.TestTraits; +import com.jnape.palatable.traitor.runners.Traits; import org.junit.Before; import org.junit.Test; +import org.junit.runner.RunWith; +import testsupport.traits.FunctorLaws; import java.util.HashMap; import java.util.Map; @@ -14,6 +18,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; +@RunWith(Traits.class) public class Tuple2Test { private Tuple2 tuple2; @@ -23,6 +28,11 @@ public void setUp() throws Exception { tuple2 = new Tuple2<>(1, new SingletonHList<>(2)); } + @TestTraits({FunctorLaws.class}) + public Tuple2 testSubject() { + return tuple("one", 2); + } + @Test public void head() { assertEquals((Integer) 1, tuple2.head()); @@ -66,11 +76,6 @@ public void fill() { assertEquals(tuple("foo", "foo"), Tuple2.fill("foo")); } - @Test - public void functorProperties() { - assertEquals(new Tuple2<>(1, new SingletonHList<>("2")), tuple2.fmap(Object::toString)); - } - @Test public void bifunctorProperties() { assertEquals(new Tuple2<>("1", new SingletonHList<>("2")), tuple2.biMap(Object::toString, Object::toString)); 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 5f5dad667..18ae10153 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 @@ -1,7 +1,11 @@ package com.jnape.palatable.lambda.adt.hlist; +import com.jnape.palatable.traitor.annotations.TestTraits; +import com.jnape.palatable.traitor.runners.Traits; import org.junit.Before; import org.junit.Test; +import org.junit.runner.RunWith; +import testsupport.traits.FunctorLaws; import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; import static org.junit.Assert.assertEquals; @@ -10,6 +14,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; +@RunWith(Traits.class) public class Tuple3Test { private Tuple3 tuple3; @@ -19,6 +24,11 @@ public void setUp() { tuple3 = new Tuple3<>(1, new Tuple2<>("2", new SingletonHList<>('3'))); } + @TestTraits({FunctorLaws.class}) + public Tuple3 testSubject() { + return tuple("one", 2, 3d); + } + @Test public void head() { assertEquals((Integer) 1, tuple3.head()); @@ -65,11 +75,6 @@ public void fill() { assertEquals(tuple("foo", "foo", "foo"), Tuple3.fill("foo")); } - @Test - public void functorProperties() { - assertEquals(new Tuple3<>(1, new Tuple2<>("2", new SingletonHList<>("3"))), tuple3.fmap(Object::toString)); - } - @Test public void bifunctorProperties() { assertEquals(new Tuple3<>(1, new Tuple2<>(2, new SingletonHList<>("3"))), tuple3.biMap(Integer::parseInt, Object::toString)); 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 ebaca3c5a..bafbdfd07 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 @@ -1,7 +1,11 @@ package com.jnape.palatable.lambda.adt.hlist; +import com.jnape.palatable.traitor.annotations.TestTraits; +import com.jnape.palatable.traitor.runners.Traits; import org.junit.Before; import org.junit.Test; +import org.junit.runner.RunWith; +import testsupport.traits.FunctorLaws; import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; import static org.junit.Assert.assertEquals; @@ -10,6 +14,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; +@RunWith(Traits.class) public class Tuple4Test { private Tuple4 tuple4; @@ -19,6 +24,11 @@ public void setUp() { tuple4 = new Tuple4<>(1, new Tuple3<>("2", new Tuple2<>('3', new SingletonHList<>(false)))); } + @TestTraits({FunctorLaws.class}) + public Tuple4 testSubject() { + return tuple("one", 2, 3d, 4f); + } + @Test public void head() { assertEquals((Integer) 1, tuple4.head()); @@ -68,11 +78,6 @@ public void fill() { assertEquals(tuple("foo", "foo", "foo", "foo"), Tuple4.fill("foo")); } - @Test - public void functorProperties() { - assertEquals(new Tuple4<>(1, new Tuple3<>("2", new Tuple2<>('3', new SingletonHList<>(true)))), tuple4.fmap(x -> !x)); - } - @Test public void bifunctorProperties() { assertEquals(new Tuple4<>(1, new Tuple3<>("2", new Tuple2<>("3", new SingletonHList<>(true)))), 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 b6065098c..8d2b137d1 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 @@ -1,8 +1,12 @@ package com.jnape.palatable.lambda.adt.hlist; import com.jnape.palatable.lambda.adt.hlist.HList.HCons; +import com.jnape.palatable.traitor.annotations.TestTraits; +import com.jnape.palatable.traitor.runners.Traits; import org.junit.Before; import org.junit.Test; +import org.junit.runner.RunWith; +import testsupport.traits.FunctorLaws; import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; import static org.junit.Assert.assertEquals; @@ -11,6 +15,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; +@RunWith(Traits.class) public class Tuple5Test { private Tuple5 tuple5; @@ -20,6 +25,11 @@ public void setUp() { tuple5 = new Tuple5<>(1, new Tuple4<>("2", new Tuple3<>('3', new Tuple2<>(false, new SingletonHList<>(5L))))); } + @TestTraits({FunctorLaws.class}) + public Tuple5 testSubject() { + return tuple("one", 2, 3d, 4f, '5'); + } + @Test public void head() { assertEquals((Integer) 1, tuple5.head()); @@ -66,12 +76,6 @@ public void fill() { assertEquals(tuple("foo", "foo", "foo", "foo", "foo"), Tuple5.fill("foo")); } - @Test - public void functorProperties() { - assertEquals(new Tuple5<>(1, new Tuple4<>("2", new Tuple3<>('3', new Tuple2<>(false, new SingletonHList<>("5"))))), - tuple5.fmap(Object::toString)); - } - @Test public void bifunctorProperties() { assertEquals(new Tuple5<>(1, new Tuple4<>("2", new Tuple3<>('3', new Tuple2<>(true, new SingletonHList<>("5"))))), 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 26baebef4..5fedfd78c 100644 --- a/src/test/java/com/jnape/palatable/lambda/functions/Fn1Test.java +++ b/src/test/java/com/jnape/palatable/lambda/functions/Fn1Test.java @@ -1,21 +1,24 @@ package com.jnape.palatable.lambda.functions; -import org.hamcrest.MatcherAssert; +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.EqualityAwareFn1; +import testsupport.traits.FunctorLaws; import java.util.function.Function; +import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.core.Is.is; import static org.junit.Assert.assertEquals; +@RunWith(Traits.class) public class Fn1Test { - @Test - public void functorProperties() { - Fn1 add2 = integer -> integer + 2; - Fn1 toString = Object::toString; - - MatcherAssert.assertThat(add2.fmap(toString).apply(2), is(toString.apply(add2.apply(2)))); + @TestTraits({FunctorLaws.class}) + public Fn1 testSubject() { + return new EqualityAwareFn1<>("1", Integer::parseInt); } @Test @@ -32,7 +35,7 @@ public void thenIsJustAnAliasForFmap() { Fn1 add2 = integer -> integer + 2; Fn1 toString = Object::toString; - MatcherAssert.assertThat(add2.then(toString).apply(2), is(toString.apply(add2.apply(2)))); + assertThat(add2.then(toString).apply(2), is(toString.apply(add2.apply(2)))); } @Test diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/LastTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/LastTest.java index 142ba988f..bbfc550f9 100644 --- a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/LastTest.java +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/LastTest.java @@ -17,7 +17,7 @@ public void presentForNonEmptyIterable() { } @Test - public void emptyForEmpyIterables() { + public void emptyForEmptyIterables() { assertEquals(Optional.empty(), last(emptyList())); } } \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functor/builtin/ConstTest.java b/src/test/java/com/jnape/palatable/lambda/functor/builtin/ConstTest.java index 5d8a06abc..ee66aeefd 100644 --- a/src/test/java/com/jnape/palatable/lambda/functor/builtin/ConstTest.java +++ b/src/test/java/com/jnape/palatable/lambda/functor/builtin/ConstTest.java @@ -1,14 +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.Test; +import org.junit.runner.RunWith; +import testsupport.traits.FunctorLaws; import static org.junit.Assert.assertEquals; +@RunWith(Traits.class) public class ConstTest { - @Test - public void functorProperties() { - assertEquals("foo", new Const("foo").fmap(x -> x + 1).runConst()); + @TestTraits({FunctorLaws.class}) + public Const testSubject() { + return new Const<>(1); } @Test 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 792980e99..a4efc3e6a 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 @@ -1,13 +1,15 @@ package com.jnape.palatable.lambda.functor.builtin; -import org.junit.Test; - -import static org.junit.Assert.assertEquals; +import com.jnape.palatable.traitor.annotations.TestTraits; +import com.jnape.palatable.traitor.runners.Traits; +import org.junit.runner.RunWith; +import testsupport.traits.FunctorLaws; +@RunWith(Traits.class) public class IdentityTest { - @Test - public void functorProperties() { - assertEquals("FOO", new Identity<>("foo").fmap(String::toUpperCase).runIdentity()); + @TestTraits({FunctorLaws.class}) + public Identity testSubject() { + return new Identity<>(""); } } \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/iterators/DroppingIteratorTest.java b/src/test/java/com/jnape/palatable/lambda/iterators/DroppingIteratorTest.java index e1fbdde1a..e5a09fb77 100644 --- a/src/test/java/com/jnape/palatable/lambda/iterators/DroppingIteratorTest.java +++ b/src/test/java/com/jnape/palatable/lambda/iterators/DroppingIteratorTest.java @@ -1,6 +1,5 @@ package com.jnape.palatable.lambda.iterators; -import org.hamcrest.core.Is; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -40,6 +39,6 @@ public void hasNextIfIteratorHoldsMoreThanNElements() { @Test public void dropsElementsOnNextIfNotAlreadyDropped() { mockIteratorToHaveValues(iterator, 1, 2, 3, 4, 5, 6); - assertThat(droppingIterator.next(), Is.is(6)); + assertThat(droppingIterator.next(), is(6)); } } diff --git a/src/test/java/com/jnape/palatable/lambda/iterators/PredicatedTakingIteratorTest.java b/src/test/java/com/jnape/palatable/lambda/iterators/PredicatedTakingIteratorTest.java index 69f8d2f51..6e817057b 100644 --- a/src/test/java/com/jnape/palatable/lambda/iterators/PredicatedTakingIteratorTest.java +++ b/src/test/java/com/jnape/palatable/lambda/iterators/PredicatedTakingIteratorTest.java @@ -6,6 +6,7 @@ import java.util.NoSuchElementException; import static java.util.Arrays.asList; +import static java.util.Collections.singletonList; import static org.hamcrest.core.Is.is; import static org.junit.Assert.assertThat; @@ -47,14 +48,14 @@ public void doesNotHaveNextIfTakenAllElements() { @Test(expected = NoSuchElementException.class) public void throwsExceptionIfNextAfterFailedPredicate() { - Iterable words = asList("no"); + Iterable words = singletonList("no"); PredicatedTakingIterator predicatedTakingIterator = new PredicatedTakingIterator<>(HAS_FOUR_LETTERS, words.iterator()); predicatedTakingIterator.next(); } @Test public void takesEverythingIfPredicateNeverFails() { - Iterable words = asList("yeah"); + Iterable words = singletonList("yeah"); PredicatedTakingIterator predicatedTakingIterator = new PredicatedTakingIterator<>(HAS_FOUR_LETTERS, words.iterator()); assertThat(predicatedTakingIterator.next(), is("yeah")); assertThat(predicatedTakingIterator.hasNext(), is(false)); diff --git a/src/test/java/com/jnape/palatable/lambda/iterators/ReversingIteratorTest.java b/src/test/java/com/jnape/palatable/lambda/iterators/ReversingIteratorTest.java index 4d9c5d3d2..22164a729 100644 --- a/src/test/java/com/jnape/palatable/lambda/iterators/ReversingIteratorTest.java +++ b/src/test/java/com/jnape/palatable/lambda/iterators/ReversingIteratorTest.java @@ -1,6 +1,5 @@ package com.jnape.palatable.lambda.iterators; -import org.hamcrest.core.Is; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -40,11 +39,11 @@ public void doesNotHaveNextIfIteratorIsEmpty() { public void reversesIterator() { mockIteratorToHaveValues(iterator, 1, 2, 3, 4, 5); - assertThat(reversingIterator.next(), Is.is(5)); - assertThat(reversingIterator.next(), Is.is(4)); - assertThat(reversingIterator.next(), Is.is(3)); - assertThat(reversingIterator.next(), Is.is(2)); - assertThat(reversingIterator.next(), Is.is(1)); + assertThat(reversingIterator.next(), is(5)); + assertThat(reversingIterator.next(), is(4)); + assertThat(reversingIterator.next(), is(3)); + assertThat(reversingIterator.next(), is(2)); + assertThat(reversingIterator.next(), is(1)); } @Test @@ -54,6 +53,7 @@ public void doesNotReverseUntilNextIsCalled() { } @Test + @SuppressWarnings("Duplicates") public void doesNotHaveNextIfFinishedReversingIterator() { mockIteratorToHaveValues(iterator, 1, 2, 3); reversingIterator.next(); diff --git a/src/test/java/com/jnape/palatable/lambda/iterators/RewindableIteratorTest.java b/src/test/java/com/jnape/palatable/lambda/iterators/RewindableIteratorTest.java index a5c966d3c..544e23fed 100644 --- a/src/test/java/com/jnape/palatable/lambda/iterators/RewindableIteratorTest.java +++ b/src/test/java/com/jnape/palatable/lambda/iterators/RewindableIteratorTest.java @@ -1,6 +1,5 @@ package com.jnape.palatable.lambda.iterators; -import org.hamcrest.core.Is; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -33,7 +32,7 @@ public void rewindingQueuesPreviousElementUpForNext() { rewindableIterator.next(); rewindableIterator.rewind(); - assertThat(rewindableIterator.next(), Is.is(2)); + assertThat(rewindableIterator.next(), is(2)); } @Test @@ -62,12 +61,12 @@ public void cannotRewindTheSameElementTwice() { } @Test + @SuppressWarnings("Duplicates") public void doesNotHaveNextIfNoMoreElementsAndIsNotRewound() { mockIteratorToHaveValues(iterator, 1, 2, 3); rewindableIterator.next(); rewindableIterator.next(); rewindableIterator.next(); - assertThat(rewindableIterator.hasNext(), is(false)); } } diff --git a/src/test/java/com/jnape/palatable/lambda/lens/LensTest.java b/src/test/java/com/jnape/palatable/lambda/lens/LensTest.java index f48e30cef..3e093dc9e 100644 --- a/src/test/java/com/jnape/palatable/lambda/lens/LensTest.java +++ b/src/test/java/com/jnape/palatable/lambda/lens/LensTest.java @@ -3,7 +3,12 @@ import com.jnape.palatable.lambda.functions.Fn1; import com.jnape.palatable.lambda.functor.builtin.Const; import com.jnape.palatable.lambda.functor.builtin.Identity; +import com.jnape.palatable.traitor.annotations.TestTraits; +import com.jnape.palatable.traitor.runners.Traits; import org.junit.Test; +import org.junit.runner.RunWith; +import testsupport.EqualityAwareLens; +import testsupport.traits.FunctorLaws; import java.util.List; import java.util.Map; @@ -15,16 +20,23 @@ import static com.jnape.palatable.lambda.lens.functions.View.view; import static java.lang.Integer.parseInt; import static java.util.Arrays.asList; +import static java.util.Collections.emptyMap; import static java.util.Collections.singleton; import static java.util.Collections.singletonList; import static java.util.Collections.singletonMap; import static org.junit.Assert.assertEquals; +@RunWith(Traits.class) public class LensTest { private static final Lens>, Map>, List, Set> EARLIER_LENS = lens(m -> m.get("foo"), (m, s) -> singletonMap("foo", s)); private static final Lens, Set, String, Integer> LENS = lens(xs -> xs.get(0), (xs, i) -> singleton(i)); + @TestTraits({FunctorLaws.class}) + public Lens, List, Integer, String> testSubject() { + return new EqualityAwareLens<>(emptyMap(), lens(m -> m.get("foo"), (m, s) -> singletonList(m.get(s)))); + } + @Test public void setsUnderIdentity() { Set ints = LENS.>, Identity>apply(s -> new Identity<>(s.length()), asList("foo", "bar", "baz")).runIdentity(); @@ -48,11 +60,6 @@ public void fix() { assertEquals(unfixedLensResult, fixedLensResult); } - @Test - public void functorProperties() { - assertEquals(false, set(LENS.fmap(Set::isEmpty), 1, singletonList("foo"))); - } - @Test public void mapsIndividuallyOverParameters() { Lens lens = lens(s -> s.charAt(0), (s, b) -> s.length() == b); diff --git a/src/test/java/testsupport/EqualityAwareFn1.java b/src/test/java/testsupport/EqualityAwareFn1.java new file mode 100644 index 000000000..7b6392e7b --- /dev/null +++ b/src/test/java/testsupport/EqualityAwareFn1.java @@ -0,0 +1,38 @@ +package testsupport; + +import com.jnape.palatable.lambda.functions.Fn1; + +import java.util.function.Function; + +import static java.util.Objects.hash; + +public final class EqualityAwareFn1 implements Fn1 { + private final A a; + private final Fn1 fn; + + public EqualityAwareFn1(A a, Fn1 fn) { + this.a = a; + this.fn = fn; + } + + @Override + public B apply(A a) { + return fn.apply(a); + } + + @Override + public EqualityAwareFn1 fmap(Function f) { + return new EqualityAwareFn1<>(a, fn.fmap(f)); + } + + @Override + @SuppressWarnings("unchecked") + public boolean equals(Object other) { + return other instanceof Fn1 && ((Fn1) other).apply(a).equals(apply(a)); + } + + @Override + public int hashCode() { + return hash(a, fn); + } +} diff --git a/src/test/java/testsupport/EqualityAwareLens.java b/src/test/java/testsupport/EqualityAwareLens.java new file mode 100644 index 000000000..20cf4d872 --- /dev/null +++ b/src/test/java/testsupport/EqualityAwareLens.java @@ -0,0 +1,37 @@ +package testsupport; + +import com.jnape.palatable.lambda.functor.Functor; +import com.jnape.palatable.lambda.functor.builtin.Const; +import com.jnape.palatable.lambda.lens.Lens; + +import java.util.Objects; +import java.util.function.Function; + +public final class EqualityAwareLens implements Lens { + private final S s; + private final Lens lens; + + public EqualityAwareLens(S s, Lens lens) { + this.s = s; + this.lens = lens; + } + + @Override + public , FB extends Functor> FT apply( + Function fn, S s) { + return lens.apply(fn, s); + } + + @Override + public EqualityAwareLens fmap(Function fn) { + return new EqualityAwareLens<>(s, lens.fmap(fn)); + } + + @Override + @SuppressWarnings("unchecked") + public boolean equals(Object other) { + return other instanceof Lens + && Objects.equals(((Lens) other)., Const, Const>apply(Const::new, s).runConst(), + this., Const, Const>apply(Const::new, s).runConst()); + } +} diff --git a/src/test/java/testsupport/traits/FunctorLaws.java b/src/test/java/testsupport/traits/FunctorLaws.java new file mode 100644 index 000000000..ad45231cf --- /dev/null +++ b/src/test/java/testsupport/traits/FunctorLaws.java @@ -0,0 +1,39 @@ +package testsupport.traits; + +import com.jnape.palatable.lambda.functor.Functor; +import com.jnape.palatable.lambda.monoid.builtin.Present; +import com.jnape.palatable.traitor.traits.Trait; + +import java.util.Optional; +import java.util.function.Function; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; +import static java.util.Arrays.asList; +import static java.util.function.Function.identity; + +public class FunctorLaws implements Trait> { + + @Override + public void test(Functor f) { + Present.present((x, y) -> x + "\n\t - " + y) + .reduceLeft(asList(testIdentity(f), testComposition(f))) + .ifPresent(s -> { + throw new AssertionError("The following Functor laws did not hold for instance of " + f.getClass() + ": \n\t - " + s); + }); + } + + private Optional testIdentity(Functor f) { + return f.fmap(identity()).equals(f) + ? Optional.empty() + : Optional.of("identity (f.fmap(identity()).equals(f))"); + } + + private Optional testComposition(Functor functor) { + Functor subject = functor.fmap(constantly(1)); + Function f = x -> x * 3; + Function g = x -> x - 2; + return subject.fmap(f.compose(g)).equals(subject.fmap(g).fmap(f)) + ? Optional.empty() + : Optional.of("composition (functor.fmap(f.compose(g)).equals(functor.fmap(g).fmap(f)))"); + } +} From 4ee9daf6a43c4c61246def7b46f361335c0e89c6 Mon Sep 17 00:00:00 2001 From: jnape Date: Sun, 16 Apr 2017 20:20:03 -0400 Subject: [PATCH 04/24] Applicatives arrive --- CHANGELOG.md | 5 +- pom.xml | 5 +- .../jnape/palatable/lambda/adt/Either.java | 17 ++++- .../palatable/lambda/adt/choice/Choice2.java | 17 ++++- .../palatable/lambda/adt/choice/Choice3.java | 18 ++++- .../palatable/lambda/adt/choice/Choice4.java | 17 ++++- .../palatable/lambda/adt/choice/Choice5.java | 17 ++++- .../lambda/adt/hlist/SingletonHList.java | 20 ++++- .../palatable/lambda/adt/hlist/Tuple2.java | 19 ++++- .../palatable/lambda/adt/hlist/Tuple3.java | 19 ++++- .../palatable/lambda/adt/hlist/Tuple4.java | 19 ++++- .../palatable/lambda/adt/hlist/Tuple5.java | 19 ++++- .../jnape/palatable/lambda/functions/Fn1.java | 22 +++++- .../palatable/lambda/functor/Applicative.java | 48 ++++++++++++ .../lambda/functor/builtin/Const.java | 16 +++- .../lambda/functor/builtin/Identity.java | 17 ++++- .../com/jnape/palatable/lambda/lens/Lens.java | 20 ++++- .../palatable/lambda/adt/EitherTest.java | 14 ++-- .../lambda/adt/choice/Choice2Test.java | 14 ++-- .../lambda/adt/choice/Choice3Test.java | 19 ++--- .../lambda/adt/choice/Choice4Test.java | 24 ++---- .../lambda/adt/choice/Choice5Test.java | 29 ++----- .../lambda/adt/hlist/SingletonHListTest.java | 3 +- .../lambda/adt/hlist/Tuple2Test.java | 3 +- .../lambda/adt/hlist/Tuple3Test.java | 3 +- .../lambda/adt/hlist/Tuple4Test.java | 3 +- .../lambda/adt/hlist/Tuple5Test.java | 3 +- .../palatable/lambda/functions/Fn1Test.java | 3 +- .../lambda/functor/builtin/ConstTest.java | 3 +- .../lambda/functor/builtin/IdentityTest.java | 3 +- .../jnape/palatable/lambda/lens/LensTest.java | 3 +- .../java/testsupport/EqualityAwareFn1.java | 11 +++ .../java/testsupport/EqualityAwareLens.java | 12 +++ .../testsupport/traits/ApplicativeLaws.java | 75 +++++++++++++++++++ 34 files changed, 422 insertions(+), 118 deletions(-) create mode 100644 src/main/java/com/jnape/palatable/lambda/functor/Applicative.java create mode 100644 src/test/java/testsupport/traits/ApplicativeLaws.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 1a8d11fce..b6f6f633d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,9 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/). ## [Unreleased] +### Added +- `Applicative` arrives; all functors gain applicative properties + ### Changed - `Functor`, `Bifunctor`, and `Profunctor` (as well as all instances) get a unification parameter - `Identity` supports value equality @@ -115,4 +118,4 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/). [1.3]: https://github.com/palatable/lambda/compare/lambda-1.2...lambda-1.3 [1.2]: https://github.com/palatable/lambda/compare/lambda-1.1...lambda-1.2 [1.1]: https://github.com/palatable/lambda/compare/lambda-1.0...lambda-1.1 -[1.0]: https://github.com/palatable/lambda/commits/lambda-1.0 \ No newline at end of file +[1.0]: https://github.com/palatable/lambda/commits/lambda-1.0 diff --git a/pom.xml b/pom.xml index 6d651dc0c..8b8fdc383 100644 --- a/pom.xml +++ b/pom.xml @@ -1,5 +1,6 @@ - + 4.0.0 @@ -54,7 +55,7 @@ 3.1 - 1.0 + 1.2 3.3 1.3 diff --git a/src/main/java/com/jnape/palatable/lambda/adt/Either.java b/src/main/java/com/jnape/palatable/lambda/adt/Either.java index 887ce8330..9c1b0fd8d 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/Either.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/Either.java @@ -3,8 +3,8 @@ import com.jnape.palatable.lambda.adt.coproduct.CoProduct2; import com.jnape.palatable.lambda.functions.specialized.checked.CheckedFn1; import com.jnape.palatable.lambda.functions.specialized.checked.CheckedSupplier; +import com.jnape.palatable.lambda.functor.Applicative; import com.jnape.palatable.lambda.functor.Bifunctor; -import com.jnape.palatable.lambda.functor.Functor; import java.util.Objects; import java.util.Optional; @@ -25,7 +25,7 @@ * @param The left parameter type * @param The right parameter type */ -public abstract class Either implements CoProduct2, Functor>, Bifunctor { +public abstract class Either implements CoProduct2, Applicative>, Bifunctor { private Either() { } @@ -195,8 +195,9 @@ public Either peek(Consumer leftConsumer, Consumer rightConsumer) { public abstract V match(Function leftFn, Function rightFn); @Override + @SuppressWarnings("unchecked") public final Either fmap(Function fn) { - return biMapR(fn); + return (Either) Applicative.super.fmap(fn); } @Override @@ -217,6 +218,16 @@ public final Either biMap(Function lef return match(l -> left(leftFn.apply(l)), r -> right(rightFn.apply(r))); } + @Override + public Either pure(R2 r2) { + return right(r2); + } + + @Override + public Either zip(Applicative, Either> appFn) { + return appFn.>>coerce().flatMap(this::biMapR); + } + /** * In the left case, returns an {@link Optional#empty}; otherwise, returns {@link Optional#ofNullable} around the * right value. diff --git a/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice2.java b/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice2.java index e1579c565..94799eec9 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice2.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice2.java @@ -2,6 +2,7 @@ import com.jnape.palatable.lambda.adt.Either; import com.jnape.palatable.lambda.adt.coproduct.CoProduct2; +import com.jnape.palatable.lambda.functor.Applicative; import com.jnape.palatable.lambda.functor.Bifunctor; import com.jnape.palatable.lambda.functor.Functor; @@ -18,7 +19,7 @@ * @see Either * @see Choice3 */ -public abstract class Choice2 implements CoProduct2, Functor>, Bifunctor { +public abstract class Choice2 implements CoProduct2, Applicative>, Bifunctor { private Choice2() { } @@ -29,8 +30,9 @@ public final Choice3 diverge() { } @Override + @SuppressWarnings("unchecked") public final Choice2 fmap(Function fn) { - return biMapR(fn); + return (Choice2) Applicative.super.fmap(fn); } @Override @@ -51,6 +53,17 @@ public final Choice2 biMap(Function lFn, return match(a -> a(lFn.apply(a)), b -> b(rFn.apply(b))); } + @Override + public Choice2 pure(C c) { + return b(c); + } + + @Override + public Choice2 zip(Applicative, Choice2> appFn) { + return appFn.>>coerce() + .match(Choice2::a, this::biMapR); + } + /** * Static factory method for wrapping a value of type A in a {@link Choice2}. * diff --git a/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice3.java b/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice3.java index 7cb8b7b02..4ef55c43b 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice3.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice3.java @@ -2,6 +2,7 @@ import com.jnape.palatable.lambda.adt.coproduct.CoProduct2; import com.jnape.palatable.lambda.adt.coproduct.CoProduct3; +import com.jnape.palatable.lambda.functor.Applicative; import com.jnape.palatable.lambda.functor.Bifunctor; import com.jnape.palatable.lambda.functor.Functor; @@ -17,7 +18,7 @@ * @see Choice2 * @see Choice4 */ -public abstract class Choice3 implements CoProduct3, Functor>, Bifunctor> { +public abstract class Choice3 implements CoProduct3, Applicative>, Bifunctor> { private Choice3() { } @@ -33,8 +34,9 @@ public final Choice2 converge(Function Choice3 fmap(Function fn) { - return biMapR(fn); + return (Choice3) Applicative.super.fmap(fn); } @Override @@ -55,6 +57,18 @@ public final Choice3 biMap(Function lFn, return match(Choice3::a, b -> b(lFn.apply(b)), c -> c(rFn.apply(c))); } + @Override + public Choice3 pure(D d) { + return c(d); + } + + @Override + public Choice3 zip( + Applicative, Choice3> appFn) { + return appFn.>>coerce() + .match(Choice3::a, Choice3::b, this::biMapR); + } + /** * Static factory method for wrapping a value of type A in a {@link Choice3}. * diff --git a/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice4.java b/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice4.java index eb3fa2467..12d562125 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice4.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice4.java @@ -2,6 +2,7 @@ import com.jnape.palatable.lambda.adt.coproduct.CoProduct3; import com.jnape.palatable.lambda.adt.coproduct.CoProduct4; +import com.jnape.palatable.lambda.functor.Applicative; import com.jnape.palatable.lambda.functor.Bifunctor; import com.jnape.palatable.lambda.functor.Functor; @@ -18,7 +19,7 @@ * @see Choice3 * @see Choice5 */ -public abstract class Choice4 implements CoProduct4, Functor>, Bifunctor> { +public abstract class Choice4 implements CoProduct4, Applicative>, Bifunctor> { private Choice4() { } @@ -37,8 +38,9 @@ public Choice3 converge(Function Choice4 fmap(Function fn) { - return biMapR(fn); + return (Choice4) Applicative.super.fmap(fn); } @Override @@ -59,6 +61,17 @@ public final Choice4 biMap(Function l return match(Choice4::a, Choice4::b, c -> c(lFn.apply(c)), d -> d(rFn.apply(d))); } + @Override + public Choice4 pure(E e) { + return d(e); + } + + @Override + public Choice4 zip(Applicative, Choice4> appFn) { + return appFn.>>coerce() + .match(Choice4::a, Choice4::b, Choice4::c, this::biMapR); + } + /** * Static factory method for wrapping a value of type A in a {@link Choice4}. * diff --git a/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice5.java b/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice5.java index 805657bc3..33d5cb79b 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice5.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice5.java @@ -2,6 +2,7 @@ import com.jnape.palatable.lambda.adt.coproduct.CoProduct4; import com.jnape.palatable.lambda.adt.coproduct.CoProduct5; +import com.jnape.palatable.lambda.functor.Applicative; import com.jnape.palatable.lambda.functor.Bifunctor; import com.jnape.palatable.lambda.functor.Functor; @@ -18,7 +19,7 @@ * @param a type parameter representing the fifth possible type of this choice * @see Choice4 */ -public abstract class Choice5 implements CoProduct5, Functor>, Bifunctor> { +public abstract class Choice5 implements CoProduct5, Applicative>, Bifunctor> { private Choice5() { } @@ -33,8 +34,9 @@ public Choice4 converge(Function Choice5 fmap(Function fn) { - return biMapR(fn); + return (Choice5) Applicative.super.fmap(fn); } @Override @@ -56,6 +58,17 @@ public Choice5 biMap(Function lFn, return match(Choice5::a, Choice5::b, Choice5::c, d -> d(lFn.apply(d)), e -> e(rFn.apply(e))); } + @Override + public Choice5 pure(F f) { + return e(f); + } + + @Override + public Choice5 zip(Applicative, Choice5> appFn) { + return appFn.>>coerce() + .match(Choice5::a, Choice5::b, Choice5::c, Choice5::d, this::biMapR); + } + /** * Static factory method for wrapping a value of type A in a {@link Choice5}. * 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 c97b15f27..b56c78d62 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,7 +2,7 @@ import com.jnape.palatable.lambda.adt.hlist.HList.HCons; import com.jnape.palatable.lambda.adt.hlist.HList.HNil; -import com.jnape.palatable.lambda.functor.Functor; +import com.jnape.palatable.lambda.functor.Applicative; import java.util.function.Function; @@ -16,7 +16,7 @@ * @see Tuple4 * @see Tuple5 */ -public class SingletonHList<_1> extends HCons<_1, HNil> implements Functor<_1, SingletonHList> { +public class SingletonHList<_1> extends HCons<_1, HNil> implements Applicative<_1, SingletonHList> { SingletonHList(_1 _1) { super(_1, nil()); @@ -28,7 +28,21 @@ public <_0> Tuple2<_0, _1> cons(_0 _0) { } @Override + @SuppressWarnings("unchecked") public <_1Prime> SingletonHList<_1Prime> fmap(Function fn) { - return new SingletonHList<>(fn.apply(head())); + return (SingletonHList<_1Prime>) Applicative.super.fmap(fn); + } + + @Override + public <_1Prime> SingletonHList<_1Prime> pure(_1Prime _1Prime) { + return singletonHList(_1Prime); + } + + @Override + public <_1Prime> SingletonHList<_1Prime> zip( + Applicative, SingletonHList> appFn) { + return new SingletonHList<>(appFn.>>coerce() + .head() + .apply(head())); } } 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 31478a292..128dc2b3a 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 @@ -1,8 +1,8 @@ package com.jnape.palatable.lambda.adt.hlist; import com.jnape.palatable.lambda.adt.hlist.HList.HCons; +import com.jnape.palatable.lambda.functor.Applicative; import com.jnape.palatable.lambda.functor.Bifunctor; -import com.jnape.palatable.lambda.functor.Functor; import java.util.Map; import java.util.function.BiFunction; @@ -19,7 +19,8 @@ * @see Tuple4 * @see Tuple5 */ -public class Tuple2<_1, _2> extends HCons<_1, SingletonHList<_2>> implements Map.Entry<_1, _2>, Functor<_2, Tuple2<_1, ?>>, Bifunctor<_1, _2, Tuple2> { +public class Tuple2<_1, _2> extends HCons<_1, SingletonHList<_2>> + implements Map.Entry<_1, _2>, Applicative<_2, Tuple2<_1, ?>>, Bifunctor<_1, _2, Tuple2> { private final _1 _1; private final _2 _2; @@ -81,8 +82,9 @@ public _2 setValue(_2 value) { } @Override + @SuppressWarnings("unchecked") public <_2Prime> Tuple2<_1, _2Prime> fmap(Function fn) { - return tuple(_1(), fn.apply(_2())); + return (Tuple2<_1, _2Prime>) Applicative.super.fmap(fn); } @Override @@ -103,6 +105,17 @@ public <_1Prime, _2Prime> Tuple2<_1Prime, _2Prime> biMap(Function(lFn.apply(_1()), tail().fmap(rFn)); } + @Override + public <_2Prime> Tuple2<_1, _2Prime> pure(_2Prime _2Prime) { + return tuple(_1, _2Prime); + } + + @Override + public <_2Prime> Tuple2<_1, _2Prime> zip( + Applicative, Tuple2<_1, ?>> appFn) { + return biMapR(appFn.>>coerce()._2()); + } + /** * Static factory method for creating Tuple2s from {@link java.util.Map.Entry}s. * diff --git a/src/main/java/com/jnape/palatable/lambda/adt/hlist/Tuple3.java b/src/main/java/com/jnape/palatable/lambda/adt/hlist/Tuple3.java index 36685ebeb..46705a2d5 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 @@ -2,8 +2,8 @@ import com.jnape.palatable.lambda.adt.hlist.HList.HCons; import com.jnape.palatable.lambda.functions.Fn3; +import com.jnape.palatable.lambda.functor.Applicative; import com.jnape.palatable.lambda.functor.Bifunctor; -import com.jnape.palatable.lambda.functor.Functor; import java.util.function.Function; @@ -19,7 +19,8 @@ * @see Tuple4 * @see Tuple5 */ -public class Tuple3<_1, _2, _3> extends HCons<_1, Tuple2<_2, _3>> implements Functor<_3, Tuple3<_1, _2, ?>>, Bifunctor<_2, _3, Tuple3<_1, ?, ?>> { +public class Tuple3<_1, _2, _3> extends HCons<_1, Tuple2<_2, _3>> + implements Applicative<_3, Tuple3<_1, _2, ?>>, Bifunctor<_2, _3, Tuple3<_1, ?, ?>> { private final _1 _1; private final _2 _2; private final _3 _3; @@ -77,8 +78,9 @@ public R into(Fn3 fn) { } @Override + @SuppressWarnings("unchecked") public <_3Prime> Tuple3<_1, _2, _3Prime> fmap(Function fn) { - return biMapR(fn); + return (Tuple3<_1, _2, _3Prime>) Applicative.super.fmap(fn); } @Override @@ -99,6 +101,17 @@ public <_2Prime, _3Prime> Tuple3<_1, _2Prime, _3Prime> biMap(Function(_1(), tail().biMap(lFn, rFn)); } + @Override + public <_3Prime> Tuple3<_1, _2, _3Prime> pure(_3Prime _3Prime) { + return tuple(_1, _2, _3Prime); + } + + @Override + public <_3Prime> Tuple3<_1, _2, _3Prime> zip( + Applicative, Tuple3<_1, _2, ?>> appFn) { + return biMapR(appFn.>>coerce()._3()); + } + /** * Given a value of type A, produced an instance of this tuple with each slot set to that value. * diff --git a/src/main/java/com/jnape/palatable/lambda/adt/hlist/Tuple4.java b/src/main/java/com/jnape/palatable/lambda/adt/hlist/Tuple4.java index 01a2c4a95..de5d8bd89 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 @@ -2,8 +2,8 @@ import com.jnape.palatable.lambda.adt.hlist.HList.HCons; import com.jnape.palatable.lambda.functions.Fn4; +import com.jnape.palatable.lambda.functor.Applicative; import com.jnape.palatable.lambda.functor.Bifunctor; -import com.jnape.palatable.lambda.functor.Functor; import java.util.function.Function; @@ -20,7 +20,8 @@ * @see Tuple3 * @see Tuple5 */ -public class Tuple4<_1, _2, _3, _4> extends HCons<_1, Tuple3<_2, _3, _4>> implements Functor<_4, Tuple4<_1, _2, _3, ?>>, Bifunctor<_3, _4, Tuple4<_1, _2, ?, ?>> { +public class Tuple4<_1, _2, _3, _4> extends HCons<_1, Tuple3<_2, _3, _4>> + implements Applicative<_4, Tuple4<_1, _2, _3, ?>>, Bifunctor<_3, _4, Tuple4<_1, _2, ?, ?>> { private final _1 _1; private final _2 _2; private final _3 _3; @@ -89,8 +90,9 @@ public R into(Fn4 Tuple4<_1, _2, _3, _4Prime> fmap(Function fn) { - return biMapR(fn); + return (Tuple4<_1, _2, _3, _4Prime>) Applicative.super.fmap(fn); } @Override @@ -111,6 +113,17 @@ public <_3Prime, _4Prime> Tuple4<_1, _2, _3Prime, _4Prime> biMap(Function(_1(), tail().biMap(lFn, rFn)); } + @Override + public <_4Prime> Tuple4<_1, _2, _3, _4Prime> pure(_4Prime _4Prime) { + return tuple(_1, _2, _3, _4Prime); + } + + @Override + public <_4Prime> Tuple4<_1, _2, _3, _4Prime> zip( + Applicative, Tuple4<_1, _2, _3, ?>> appFn) { + return biMapR(appFn.>>coerce()._4()); + } + /** * Given a value of type A, produced an instance of this tuple with each slot set to that value. * diff --git a/src/main/java/com/jnape/palatable/lambda/adt/hlist/Tuple5.java b/src/main/java/com/jnape/palatable/lambda/adt/hlist/Tuple5.java index 2e31cd99a..766287806 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 @@ -1,8 +1,8 @@ package com.jnape.palatable.lambda.adt.hlist; import com.jnape.palatable.lambda.adt.hlist.HList.HCons; +import com.jnape.palatable.lambda.functor.Applicative; import com.jnape.palatable.lambda.functor.Bifunctor; -import com.jnape.palatable.lambda.functor.Functor; import java.util.function.Function; @@ -20,7 +20,8 @@ * @see Tuple3 * @see Tuple4 */ -public class Tuple5<_1, _2, _3, _4, _5> extends HCons<_1, Tuple4<_2, _3, _4, _5>> implements Functor<_5, Tuple5<_1, _2, _3, _4, ?>>, Bifunctor<_4, _5, Tuple5<_1, _2, _3, ?, ?>> { +public class Tuple5<_1, _2, _3, _4, _5> extends HCons<_1, Tuple4<_2, _3, _4, _5>> + implements Applicative<_5, Tuple5<_1, _2, _3, _4, ?>>, Bifunctor<_4, _5, Tuple5<_1, _2, _3, ?, ?>> { private final _1 _1; private final _2 _2; private final _3 _3; @@ -87,8 +88,9 @@ public _5 _5() { } @Override + @SuppressWarnings("unchecked") public <_5Prime> Tuple5<_1, _2, _3, _4, _5Prime> fmap(Function fn) { - return biMapR(fn); + return (Tuple5<_1, _2, _3, _4, _5Prime>) Applicative.super.fmap(fn); } @Override @@ -109,6 +111,17 @@ public <_4Prime, _5Prime> Tuple5<_1, _2, _3, _4Prime, _5Prime> biMap(Function(_1(), tail().biMap(lFn, rFn)); } + @Override + public <_5Prime> Tuple5<_1, _2, _3, _4, _5Prime> pure(_5Prime _5Prime) { + return tuple(_1, _2, _3, _4, _5Prime); + } + + @Override + public <_5Prime> Tuple5<_1, _2, _3, _4, _5Prime> zip( + Applicative, Tuple5<_1, _2, _3, _4, ?>> appFn) { + return biMapR(appFn.>>coerce()._5()); + } + /** * Given a value of type A, produced an instance of this tuple with each slot set to that value. * diff --git a/src/main/java/com/jnape/palatable/lambda/functions/Fn1.java b/src/main/java/com/jnape/palatable/lambda/functions/Fn1.java index b850daa42..90ef0d8e2 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/Fn1.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/Fn1.java @@ -1,6 +1,6 @@ package com.jnape.palatable.lambda.functions; -import com.jnape.palatable.lambda.functor.Functor; +import com.jnape.palatable.lambda.functor.Applicative; import com.jnape.palatable.lambda.functor.Profunctor; import java.util.function.Function; @@ -13,7 +13,7 @@ * @param The result type */ @FunctionalInterface -public interface Fn1 extends Functor>, Profunctor, Function { +public interface Fn1 extends Applicative>, Profunctor, Function { /** * Invoke this function with the given argument. @@ -44,8 +44,24 @@ default Fn1 then(Function f) { * @see Fn1#then(Function) */ @Override + @SuppressWarnings("unchecked") default Fn1 fmap(Function f) { - return a -> f.apply(apply(a)); + return (Fn1) Applicative.super.fmap(f); + } + + @Override + default Fn1 pure(C c) { + return __ -> c; + } + + @Override + default Fn1 zip(Applicative, Fn1> appFn) { + return a -> appFn.>>coerce().apply(a).apply(apply(a)); + } + + @SuppressWarnings("unchecked") + default Fn1 zip(Fn2 appFn) { + return zip((Fn1>) (Object) appFn); } /** diff --git a/src/main/java/com/jnape/palatable/lambda/functor/Applicative.java b/src/main/java/com/jnape/palatable/lambda/functor/Applicative.java new file mode 100644 index 000000000..f0a01e6df --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functor/Applicative.java @@ -0,0 +1,48 @@ +package com.jnape.palatable.lambda.functor; + +import java.util.function.Function; + +/** + * Generic interface for the applicative functor type, which supports a lifting operation pure and a + * flattening application zip. + * + * @param The type of the parameter + * @param The unification parameter + */ +public interface Applicative extends Functor { + + /** + * Lift the value b into this applicative functor. + * + * @param b the value + * @param the type of the returned applicative's parameter + * @return an instance of this applicative over b + */ + Applicative pure(B b); + + /** + * Given another instance of this applicative over a mapping function, "zip" the two instances together using + * whatever application semantics the current applicative supports + * + * @param appFn the other applicative instance + * @param the resulting applicative parameter type + * @return the mapped applicative + */ + Applicative zip(Applicative, App> appFn); + + @Override + default Applicative fmap(Function fn) { + return zip(pure(fn)); + } + + /** + * Convenience method for coercing this applicative instance into another concrete type. Unsafe. + * + * @param the concrete applicative instance to coerce this applicative to + * @return the coerced applicative + */ + @SuppressWarnings("unchecked") + default > Concrete coerce() { + return (Concrete) this; + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functor/builtin/Const.java b/src/main/java/com/jnape/palatable/lambda/functor/builtin/Const.java index a4b997b58..4921f5b19 100644 --- a/src/main/java/com/jnape/palatable/lambda/functor/builtin/Const.java +++ b/src/main/java/com/jnape/palatable/lambda/functor/builtin/Const.java @@ -1,7 +1,7 @@ package com.jnape.palatable.lambda.functor.builtin; +import com.jnape.palatable.lambda.functor.Applicative; import com.jnape.palatable.lambda.functor.Bifunctor; -import com.jnape.palatable.lambda.functor.Functor; import java.util.function.Function; @@ -14,7 +14,7 @@ * @param the left parameter type, and the type of the stored value * @param the right (phantom) parameter type */ -public final class Const implements Functor>, Bifunctor { +public final class Const implements Applicative>, Bifunctor { private final A a; @@ -42,6 +42,18 @@ public A runConst() { @Override @SuppressWarnings("unchecked") public Const fmap(Function fn) { + return (Const) Applicative.super.fmap(fn); + } + + @Override + @SuppressWarnings("unchecked") + public Const pure(C c) { + return (Const) this; + } + + @Override + @SuppressWarnings("unchecked") + public Const zip(Applicative, Const> appFn) { return (Const) this; } 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 bb98e41a5..c6e22aba1 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,6 +1,6 @@ package com.jnape.palatable.lambda.functor.builtin; -import com.jnape.palatable.lambda.functor.Functor; +import com.jnape.palatable.lambda.functor.Applicative; import java.util.Objects; import java.util.function.Function; @@ -10,7 +10,7 @@ * * @param the value type */ -public final class Identity implements Functor { +public final class Identity implements Applicative { private final A a; @@ -35,8 +35,19 @@ public A runIdentity() { * @return an Identity over B (the new value) */ @Override + @SuppressWarnings("unchecked") public Identity fmap(Function fn) { - return new Identity<>(fn.apply(a)); + return (Identity) Applicative.super.fmap(fn); + } + + @Override + public Identity pure(B b) { + return new Identity<>(b); + } + + @Override + public Identity zip(Applicative, Identity> appFn) { + return new Identity<>(appFn.>>coerce().runIdentity().apply(a)); } @Override diff --git a/src/main/java/com/jnape/palatable/lambda/lens/Lens.java b/src/main/java/com/jnape/palatable/lambda/lens/Lens.java index f3aed795f..4fb3cdd0c 100644 --- a/src/main/java/com/jnape/palatable/lambda/lens/Lens.java +++ b/src/main/java/com/jnape/palatable/lambda/lens/Lens.java @@ -1,6 +1,7 @@ package com.jnape.palatable.lambda.lens; import com.jnape.palatable.lambda.functions.Fn2; +import com.jnape.palatable.lambda.functor.Applicative; import com.jnape.palatable.lambda.functor.Functor; import java.util.function.BiFunction; @@ -132,7 +133,7 @@ * @param the type of the "smaller" update value */ @FunctionalInterface -public interface Lens extends Functor> { +public interface Lens extends Applicative> { , FB extends Functor> FT apply( Function fn, S s); @@ -152,8 +153,23 @@ default , FB extends Functor> } @Override + @SuppressWarnings("unchecked") default Lens fmap(Function fn) { - return compose(Lens.lens(id(), (s, t) -> fn.apply(t))); + return (Lens) Applicative.super.fmap(fn); + } + + @Override + default Lens pure(U u) { + return lens(view(this), (s, b) -> u); + } + + + + @Override + default Lens zip(Applicative, Lens> appFn) { + return lens(view(this), + (s, b) -> set(appFn., A, B>>coerce(), b, s) + .apply(set(this, b, s))); } /** diff --git a/src/test/java/com/jnape/palatable/lambda/adt/EitherTest.java b/src/test/java/com/jnape/palatable/lambda/adt/EitherTest.java index 5afd36b03..776358c45 100644 --- a/src/test/java/com/jnape/palatable/lambda/adt/EitherTest.java +++ b/src/test/java/com/jnape/palatable/lambda/adt/EitherTest.java @@ -1,11 +1,13 @@ package com.jnape.palatable.lambda.adt; import com.jnape.palatable.traitor.annotations.TestTraits; +import com.jnape.palatable.traitor.framework.Subjects; import com.jnape.palatable.traitor.runners.Traits; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import org.junit.runner.RunWith; +import testsupport.traits.ApplicativeLaws; import testsupport.traits.FunctorLaws; import java.util.Optional; @@ -16,6 +18,7 @@ import static com.jnape.palatable.lambda.adt.Either.fromOptional; import static com.jnape.palatable.lambda.adt.Either.left; import static com.jnape.palatable.lambda.adt.Either.right; +import static com.jnape.palatable.traitor.framework.Subjects.subjects; import static org.hamcrest.core.Is.is; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThat; @@ -26,14 +29,9 @@ public class EitherTest { @Rule public ExpectedException thrown = ExpectedException.none(); - @TestTraits({FunctorLaws.class}) - public Either testRightTraits() { - return right(1); - } - - @TestTraits({FunctorLaws.class}) - public Either testLeftTraits() { - return left("foo"); + @TestTraits({FunctorLaws.class, ApplicativeLaws.class}) + public Subjects> testSubjects() { + return subjects(left("foo"), right(1)); } @Test diff --git a/src/test/java/com/jnape/palatable/lambda/adt/choice/Choice2Test.java b/src/test/java/com/jnape/palatable/lambda/adt/choice/Choice2Test.java index 47ac423cd..1e8501ea0 100644 --- a/src/test/java/com/jnape/palatable/lambda/adt/choice/Choice2Test.java +++ b/src/test/java/com/jnape/palatable/lambda/adt/choice/Choice2Test.java @@ -1,14 +1,17 @@ package com.jnape.palatable.lambda.adt.choice; import com.jnape.palatable.traitor.annotations.TestTraits; +import com.jnape.palatable.traitor.framework.Subjects; import com.jnape.palatable.traitor.runners.Traits; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import testsupport.traits.ApplicativeLaws; import testsupport.traits.FunctorLaws; 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.traitor.framework.Subjects.subjects; import static org.junit.Assert.assertEquals; @RunWith(Traits.class) @@ -23,14 +26,9 @@ public void setUp() { b = b(true); } - @TestTraits({FunctorLaws.class}) - public Choice2 testSubjectA() { - return a("foo"); - } - - @TestTraits({FunctorLaws.class}) - public Choice2 testSubjectB() { - return b(1); + @TestTraits({FunctorLaws.class, ApplicativeLaws.class}) + public Subjects> testSubjects() { + return subjects(a("foo"), b(1)); } @Test diff --git a/src/test/java/com/jnape/palatable/lambda/adt/choice/Choice3Test.java b/src/test/java/com/jnape/palatable/lambda/adt/choice/Choice3Test.java index f188f2139..bc9fab215 100644 --- a/src/test/java/com/jnape/palatable/lambda/adt/choice/Choice3Test.java +++ b/src/test/java/com/jnape/palatable/lambda/adt/choice/Choice3Test.java @@ -1,15 +1,18 @@ package com.jnape.palatable.lambda.adt.choice; import com.jnape.palatable.traitor.annotations.TestTraits; +import com.jnape.palatable.traitor.framework.Subjects; import com.jnape.palatable.traitor.runners.Traits; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import testsupport.traits.ApplicativeLaws; import testsupport.traits.FunctorLaws; import static com.jnape.palatable.lambda.adt.choice.Choice3.a; import static com.jnape.palatable.lambda.adt.choice.Choice3.b; import static com.jnape.palatable.lambda.adt.choice.Choice3.c; +import static com.jnape.palatable.traitor.framework.Subjects.subjects; import static org.junit.Assert.assertEquals; @RunWith(Traits.class) @@ -26,19 +29,9 @@ public void setUp() { c = Choice3.c(true); } - @TestTraits({FunctorLaws.class}) - public Choice3 testSubjectA() { - return a("foo"); - } - - @TestTraits({FunctorLaws.class}) - public Choice3 testSubjectB() { - return b(1); - } - - @TestTraits({FunctorLaws.class}) - public Choice3 testSubjectC() { - return c(true); + @TestTraits({FunctorLaws.class, ApplicativeLaws.class}) + public Subjects> testSubjects() { + return subjects(a("foo"), b(1), c(true)); } @Test diff --git a/src/test/java/com/jnape/palatable/lambda/adt/choice/Choice4Test.java b/src/test/java/com/jnape/palatable/lambda/adt/choice/Choice4Test.java index 93cd29130..2de1ec0f4 100644 --- a/src/test/java/com/jnape/palatable/lambda/adt/choice/Choice4Test.java +++ b/src/test/java/com/jnape/palatable/lambda/adt/choice/Choice4Test.java @@ -1,16 +1,19 @@ package com.jnape.palatable.lambda.adt.choice; import com.jnape.palatable.traitor.annotations.TestTraits; +import com.jnape.palatable.traitor.framework.Subjects; import com.jnape.palatable.traitor.runners.Traits; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import testsupport.traits.ApplicativeLaws; import testsupport.traits.FunctorLaws; import static com.jnape.palatable.lambda.adt.choice.Choice4.a; import static com.jnape.palatable.lambda.adt.choice.Choice4.b; import static com.jnape.palatable.lambda.adt.choice.Choice4.c; import static com.jnape.palatable.lambda.adt.choice.Choice4.d; +import static com.jnape.palatable.traitor.framework.Subjects.subjects; import static org.junit.Assert.assertEquals; @RunWith(Traits.class) @@ -29,24 +32,9 @@ public void setUp() { d = d(4D); } - @TestTraits({FunctorLaws.class}) - public Choice4 testSubjectA() { - return a("foo"); - } - - @TestTraits({FunctorLaws.class}) - public Choice4 testSubjectB() { - return b(1); - } - - @TestTraits({FunctorLaws.class}) - public Choice4 testSubjectC() { - return c(true); - } - - @TestTraits({FunctorLaws.class}) - public Choice4 testSubjectD() { - return d('a'); + @TestTraits({FunctorLaws.class, ApplicativeLaws.class}) + public Subjects> testSubjects() { + return subjects(a("foo"), b(1), c(true), d('a')); } @Test diff --git a/src/test/java/com/jnape/palatable/lambda/adt/choice/Choice5Test.java b/src/test/java/com/jnape/palatable/lambda/adt/choice/Choice5Test.java index 65d911f9b..83a28d8f5 100644 --- a/src/test/java/com/jnape/palatable/lambda/adt/choice/Choice5Test.java +++ b/src/test/java/com/jnape/palatable/lambda/adt/choice/Choice5Test.java @@ -1,10 +1,12 @@ package com.jnape.palatable.lambda.adt.choice; import com.jnape.palatable.traitor.annotations.TestTraits; +import com.jnape.palatable.traitor.framework.Subjects; import com.jnape.palatable.traitor.runners.Traits; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import testsupport.traits.ApplicativeLaws; import testsupport.traits.FunctorLaws; import static com.jnape.palatable.lambda.adt.choice.Choice5.a; @@ -12,6 +14,7 @@ import static com.jnape.palatable.lambda.adt.choice.Choice5.c; import static com.jnape.palatable.lambda.adt.choice.Choice5.d; import static com.jnape.palatable.lambda.adt.choice.Choice5.e; +import static com.jnape.palatable.traitor.framework.Subjects.subjects; import static org.junit.Assert.assertEquals; @RunWith(Traits.class) @@ -32,29 +35,9 @@ public void setUp() { e = e('z'); } - @TestTraits({FunctorLaws.class}) - public Choice5 testSubjectA() { - return Choice5.a("foo"); - } - - @TestTraits({FunctorLaws.class}) - public Choice5 testSubjectB() { - return Choice5.b(1); - } - - @TestTraits({FunctorLaws.class}) - public Choice5 testSubjectC() { - return Choice5.c(true); - } - - @TestTraits({FunctorLaws.class}) - public Choice5 testSubjectD() { - return Choice5.d('a'); - } - - @TestTraits({FunctorLaws.class}) - public Choice5 testSubjectE() { - return Choice5.e(2d); + @TestTraits({FunctorLaws.class, ApplicativeLaws.class}) + public Subjects> testSubjects() { + return subjects(Choice5.a("foo"), Choice5.b(1), Choice5.c(true), Choice5.d('a'), Choice5.e(2d)); } @Test diff --git a/src/test/java/com/jnape/palatable/lambda/adt/hlist/SingletonHListTest.java b/src/test/java/com/jnape/palatable/lambda/adt/hlist/SingletonHListTest.java index de4118de3..59f40b0d0 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,6 +5,7 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import testsupport.traits.ApplicativeLaws; import testsupport.traits.FunctorLaws; import static com.jnape.palatable.lambda.adt.hlist.HList.nil; @@ -21,7 +22,7 @@ public void setUp() { singletonHList = new SingletonHList<>(1); } - @TestTraits({FunctorLaws.class}) + @TestTraits({FunctorLaws.class, ApplicativeLaws.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 d9991f56f..47fb3c6da 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 @@ -5,6 +5,7 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import testsupport.traits.ApplicativeLaws; import testsupport.traits.FunctorLaws; import java.util.HashMap; @@ -28,7 +29,7 @@ public void setUp() throws Exception { tuple2 = new Tuple2<>(1, new SingletonHList<>(2)); } - @TestTraits({FunctorLaws.class}) + @TestTraits({FunctorLaws.class, ApplicativeLaws.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 18ae10153..67d7a85bb 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 @@ -5,6 +5,7 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import testsupport.traits.ApplicativeLaws; import testsupport.traits.FunctorLaws; import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; @@ -24,7 +25,7 @@ public void setUp() { tuple3 = new Tuple3<>(1, new Tuple2<>("2", new SingletonHList<>('3'))); } - @TestTraits({FunctorLaws.class}) + @TestTraits({FunctorLaws.class, ApplicativeLaws.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 bafbdfd07..d66ce2875 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 @@ -5,6 +5,7 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import testsupport.traits.ApplicativeLaws; import testsupport.traits.FunctorLaws; import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; @@ -24,7 +25,7 @@ public void setUp() { tuple4 = new Tuple4<>(1, new Tuple3<>("2", new Tuple2<>('3', new SingletonHList<>(false)))); } - @TestTraits({FunctorLaws.class}) + @TestTraits({FunctorLaws.class, ApplicativeLaws.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 8d2b137d1..48dd6efe2 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 @@ -6,6 +6,7 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import testsupport.traits.ApplicativeLaws; import testsupport.traits.FunctorLaws; import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; @@ -25,7 +26,7 @@ public void setUp() { tuple5 = new Tuple5<>(1, new Tuple4<>("2", new Tuple3<>('3', new Tuple2<>(false, new SingletonHList<>(5L))))); } - @TestTraits({FunctorLaws.class}) + @TestTraits({FunctorLaws.class, ApplicativeLaws.class}) public Tuple5 testSubject() { return tuple("one", 2, 3d, 4f, '5'); } 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 5fedfd78c..878929a31 100644 --- a/src/test/java/com/jnape/palatable/lambda/functions/Fn1Test.java +++ b/src/test/java/com/jnape/palatable/lambda/functions/Fn1Test.java @@ -5,6 +5,7 @@ import org.junit.Test; import org.junit.runner.RunWith; import testsupport.EqualityAwareFn1; +import testsupport.traits.ApplicativeLaws; import testsupport.traits.FunctorLaws; import java.util.function.Function; @@ -16,7 +17,7 @@ @RunWith(Traits.class) public class Fn1Test { - @TestTraits({FunctorLaws.class}) + @TestTraits({FunctorLaws.class, ApplicativeLaws.class}) public Fn1 testSubject() { return new EqualityAwareFn1<>("1", Integer::parseInt); } diff --git a/src/test/java/com/jnape/palatable/lambda/functor/builtin/ConstTest.java b/src/test/java/com/jnape/palatable/lambda/functor/builtin/ConstTest.java index ee66aeefd..e8b99720d 100644 --- a/src/test/java/com/jnape/palatable/lambda/functor/builtin/ConstTest.java +++ b/src/test/java/com/jnape/palatable/lambda/functor/builtin/ConstTest.java @@ -4,6 +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 static org.junit.Assert.assertEquals; @@ -11,7 +12,7 @@ @RunWith(Traits.class) public class ConstTest { - @TestTraits({FunctorLaws.class}) + @TestTraits({FunctorLaws.class, ApplicativeLaws.class}) public Const testSubject() { return new Const<>(1); } 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 a4efc3e6a..d627e8482 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,12 +3,13 @@ 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; @RunWith(Traits.class) public class IdentityTest { - @TestTraits({FunctorLaws.class}) + @TestTraits({FunctorLaws.class, ApplicativeLaws.class}) public Identity testSubject() { return new Identity<>(""); } diff --git a/src/test/java/com/jnape/palatable/lambda/lens/LensTest.java b/src/test/java/com/jnape/palatable/lambda/lens/LensTest.java index 3e093dc9e..3e4ba46e6 100644 --- a/src/test/java/com/jnape/palatable/lambda/lens/LensTest.java +++ b/src/test/java/com/jnape/palatable/lambda/lens/LensTest.java @@ -8,6 +8,7 @@ import org.junit.Test; import org.junit.runner.RunWith; import testsupport.EqualityAwareLens; +import testsupport.traits.ApplicativeLaws; import testsupport.traits.FunctorLaws; import java.util.List; @@ -32,7 +33,7 @@ public class LensTest { private static final Lens>, Map>, List, Set> EARLIER_LENS = lens(m -> m.get("foo"), (m, s) -> singletonMap("foo", s)); private static final Lens, Set, String, Integer> LENS = lens(xs -> xs.get(0), (xs, i) -> singleton(i)); - @TestTraits({FunctorLaws.class}) + @TestTraits({FunctorLaws.class, ApplicativeLaws.class}) public Lens, List, Integer, String> testSubject() { return new EqualityAwareLens<>(emptyMap(), lens(m -> m.get("foo"), (m, s) -> singletonList(m.get(s)))); } diff --git a/src/test/java/testsupport/EqualityAwareFn1.java b/src/test/java/testsupport/EqualityAwareFn1.java index 7b6392e7b..8e121a761 100644 --- a/src/test/java/testsupport/EqualityAwareFn1.java +++ b/src/test/java/testsupport/EqualityAwareFn1.java @@ -1,6 +1,7 @@ package testsupport; import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functor.Applicative; import java.util.function.Function; @@ -25,6 +26,16 @@ public EqualityAwareFn1 fmap(Function f) { return new EqualityAwareFn1<>(a, fn.fmap(f)); } + @Override + public EqualityAwareFn1 zip(Applicative, Fn1> appFn) { + return new EqualityAwareFn1<>(a, fn.zip(appFn)); + } + + @Override + public EqualityAwareFn1 pure(C c) { + return new EqualityAwareFn1<>(a, fn.pure(c)); + } + @Override @SuppressWarnings("unchecked") public boolean equals(Object other) { diff --git a/src/test/java/testsupport/EqualityAwareLens.java b/src/test/java/testsupport/EqualityAwareLens.java index 20cf4d872..e2426c35c 100644 --- a/src/test/java/testsupport/EqualityAwareLens.java +++ b/src/test/java/testsupport/EqualityAwareLens.java @@ -1,5 +1,6 @@ package testsupport; +import com.jnape.palatable.lambda.functor.Applicative; import com.jnape.palatable.lambda.functor.Functor; import com.jnape.palatable.lambda.functor.builtin.Const; import com.jnape.palatable.lambda.lens.Lens; @@ -27,6 +28,17 @@ public EqualityAwareLens fmap(Function f return new EqualityAwareLens<>(s, lens.fmap(fn)); } + @Override + public EqualityAwareLens pure(U u) { + return new EqualityAwareLens<>(s, lens.pure(u)); + } + + @Override + public EqualityAwareLens zip( + Applicative, Lens> appFn) { + return new EqualityAwareLens<>(s, lens.zip(appFn)); + } + @Override @SuppressWarnings("unchecked") public boolean equals(Object other) { diff --git a/src/test/java/testsupport/traits/ApplicativeLaws.java b/src/test/java/testsupport/traits/ApplicativeLaws.java new file mode 100644 index 000000000..397b791df --- /dev/null +++ b/src/test/java/testsupport/traits/ApplicativeLaws.java @@ -0,0 +1,75 @@ +package testsupport.traits; + +import com.jnape.palatable.lambda.functions.builtin.fn2.Map; +import com.jnape.palatable.lambda.functor.Applicative; +import com.jnape.palatable.lambda.monoid.builtin.Present; +import com.jnape.palatable.traitor.traits.Trait; + +import java.util.Optional; +import java.util.Random; +import java.util.function.Function; + +import static java.util.Arrays.asList; +import static java.util.function.Function.identity; + +public class ApplicativeLaws implements Trait> { + + @Override + public void test(Applicative applicative) { + Iterable> testResults = Map., Optional>, Optional>map( + f -> f.apply(applicative), + asList(this::testIdentity, this::testComposition, this::testHomomorphism, this::testInterchange) + ); + Present.present((x, y) -> x + "\n\t - " + y) + .reduceLeft(testResults) + .ifPresent(s -> { + throw new AssertionError("The following Applicative laws did not hold for instance of " + applicative.getClass() + ": \n\t - " + s); + }); + } + + private Optional testIdentity(Applicative applicative) { + Applicative v = applicative.pure(1); + Applicative, App> pureId = v.pure(identity()); + return v.zip(pureId).equals(v) + ? Optional.empty() + : Optional.of("identity (v.zip(pureId).equals(v))"); + } + + private Optional testComposition(Applicative applicative) { + Random random = new Random(); + Integer firstInt = random.nextInt(100); + Integer secondInt = random.nextInt(100); + + Function, ? extends Function, ? extends Function>> compose = x -> x::compose; + Applicative, App> u = applicative.pure(x -> x + firstInt); + Applicative, App> v = applicative.pure(x -> x + secondInt); + Applicative w = applicative.pure("result: "); + + Applicative, ? extends Function, ? extends Function>>, App> pureCompose = u.pure(compose); + return w.zip(v.zip(u.zip(pureCompose))).equals(w.zip(v).zip(u)) + ? Optional.empty() + : Optional.of("composition (w.zip(v.zip(u.zip(pureCompose))).equals((w.zip(v)).zip(u)))"); + } + + private Optional testHomomorphism(Applicative applicative) { + Function f = x -> x + 1; + int x = 1; + + Applicative pureX = applicative.pure(x); + Applicative, App> pureF = applicative.pure(f); + Applicative pureFx = applicative.pure(f.apply(x)); + return pureX.zip(pureF).equals(pureFx) + ? Optional.empty() + : Optional.of("homomorphism (pureX.zip(pureF).equals(pureFx))"); + } + + private Optional testInterchange(Applicative applicative) { + Applicative, App> u = applicative.pure(x -> x + 1); + int y = 1; + + Applicative pureY = applicative.pure(y); + return pureY.zip(u).equals(u.zip(applicative.pure(f -> f.apply(y)))) + ? Optional.empty() + : Optional.of("interchange (pureY.zip(u).equals(u.zip(applicative.pure(f -> f.apply(y)))))"); + } +} From 245a209a3ffbc410f733a07ea68dfdd845b2a4eb Mon Sep 17 00:00:00 2001 From: jnape Date: Sun, 16 Apr 2017 20:39:28 -0400 Subject: [PATCH 05/24] Adding BifunctorLaws to assist with testing properties --- CHANGELOG.md | 1 + .../lambda/functor/builtin/Const.java | 11 +++++ .../palatable/lambda/adt/EitherTest.java | 12 +---- .../lambda/adt/choice/Choice2Test.java | 9 +--- .../lambda/adt/choice/Choice3Test.java | 10 +--- .../lambda/adt/choice/Choice4Test.java | 11 +---- .../lambda/adt/choice/Choice5Test.java | 12 +---- .../lambda/adt/hlist/Tuple2Test.java | 8 +--- .../lambda/adt/hlist/Tuple3Test.java | 8 +--- .../lambda/adt/hlist/Tuple4Test.java | 9 +--- .../lambda/adt/hlist/Tuple5Test.java | 9 +--- .../lambda/functor/builtin/ConstTest.java | 11 +---- .../testsupport/traits/BifunctorLaws.java | 46 +++++++++++++++++++ 13 files changed, 78 insertions(+), 79 deletions(-) create mode 100644 src/test/java/testsupport/traits/BifunctorLaws.java diff --git a/CHANGELOG.md b/CHANGELOG.md index b6f6f633d..9c5de5d8a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/). ### Changed - `Functor`, `Bifunctor`, and `Profunctor` (as well as all instances) get a unification parameter - `Identity` supports value equality +- `Const` supports value equality ## [1.5.6] - 2017-02-11 ### Added diff --git a/src/main/java/com/jnape/palatable/lambda/functor/builtin/Const.java b/src/main/java/com/jnape/palatable/lambda/functor/builtin/Const.java index 4921f5b19..5f7867999 100644 --- a/src/main/java/com/jnape/palatable/lambda/functor/builtin/Const.java +++ b/src/main/java/com/jnape/palatable/lambda/functor/builtin/Const.java @@ -3,6 +3,7 @@ import com.jnape.palatable.lambda.functor.Applicative; import com.jnape.palatable.lambda.functor.Bifunctor; +import java.util.Objects; import java.util.function.Function; /** @@ -31,6 +32,16 @@ public A runConst() { return a; } + @Override + public boolean equals(Object other) { + return other instanceof Const && Objects.equals(a, ((Const) other).a); + } + + @Override + public int hashCode() { + return Objects.hash(a); + } + /** * Map over the right parameter. Note that because B is never actually known quantity outside of a type * signature, this is effectively a no-op that serves only to alter Const's type signature. diff --git a/src/test/java/com/jnape/palatable/lambda/adt/EitherTest.java b/src/test/java/com/jnape/palatable/lambda/adt/EitherTest.java index 776358c45..9ffd5db41 100644 --- a/src/test/java/com/jnape/palatable/lambda/adt/EitherTest.java +++ b/src/test/java/com/jnape/palatable/lambda/adt/EitherTest.java @@ -8,6 +8,7 @@ import org.junit.rules.ExpectedException; import org.junit.runner.RunWith; import testsupport.traits.ApplicativeLaws; +import testsupport.traits.BifunctorLaws; import testsupport.traits.FunctorLaws; import java.util.Optional; @@ -29,7 +30,7 @@ public class EitherTest { @Rule public ExpectedException thrown = ExpectedException.none(); - @TestTraits({FunctorLaws.class, ApplicativeLaws.class}) + @TestTraits({FunctorLaws.class, ApplicativeLaws.class, BifunctorLaws.class}) public Subjects> testSubjects() { return subjects(left("foo"), right(1)); } @@ -163,15 +164,6 @@ public void fromOptionalDoesNotEvaluateLeftFnForRight() { assertThat(atomicInteger.get(), is(0)); } - @Test - public void bifunctorProperties() { - Either left = left("foo"); - Either right = right(1); - - assertThat(left.biMap(l -> l + "bar", r -> r + 1), is(left("foobar"))); - assertThat(right.biMap(l -> l + "bar", r -> r + 1), is(right(2))); - } - @Test public void monadicTryingLiftsCheckedSupplier() { assertEquals(right(1), Either.trying(() -> 1)); diff --git a/src/test/java/com/jnape/palatable/lambda/adt/choice/Choice2Test.java b/src/test/java/com/jnape/palatable/lambda/adt/choice/Choice2Test.java index 1e8501ea0..86d5e6287 100644 --- a/src/test/java/com/jnape/palatable/lambda/adt/choice/Choice2Test.java +++ b/src/test/java/com/jnape/palatable/lambda/adt/choice/Choice2Test.java @@ -7,6 +7,7 @@ import org.junit.Test; import org.junit.runner.RunWith; import testsupport.traits.ApplicativeLaws; +import testsupport.traits.BifunctorLaws; import testsupport.traits.FunctorLaws; import static com.jnape.palatable.lambda.adt.choice.Choice2.a; @@ -26,7 +27,7 @@ public void setUp() { b = b(true); } - @TestTraits({FunctorLaws.class, ApplicativeLaws.class}) + @TestTraits({FunctorLaws.class, ApplicativeLaws.class, BifunctorLaws.class}) public Subjects> testSubjects() { return subjects(a("foo"), b(1)); } @@ -36,10 +37,4 @@ public void divergeStaysInChoice() { assertEquals(Choice3.a(1), a.diverge()); assertEquals(Choice3.b(true), b.diverge()); } - - @Test - public void bifunctorProperties() { - assertEquals(a(-1), a.biMap(i -> i * -1, bool -> !bool)); - assertEquals(b(false), b.biMap(i -> i * -1, bool -> !bool)); - } } \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/adt/choice/Choice3Test.java b/src/test/java/com/jnape/palatable/lambda/adt/choice/Choice3Test.java index bc9fab215..c8720f81d 100644 --- a/src/test/java/com/jnape/palatable/lambda/adt/choice/Choice3Test.java +++ b/src/test/java/com/jnape/palatable/lambda/adt/choice/Choice3Test.java @@ -7,6 +7,7 @@ import org.junit.Test; import org.junit.runner.RunWith; import testsupport.traits.ApplicativeLaws; +import testsupport.traits.BifunctorLaws; import testsupport.traits.FunctorLaws; import static com.jnape.palatable.lambda.adt.choice.Choice3.a; @@ -29,7 +30,7 @@ public void setUp() { c = Choice3.c(true); } - @TestTraits({FunctorLaws.class, ApplicativeLaws.class}) + @TestTraits({FunctorLaws.class, ApplicativeLaws.class, BifunctorLaws.class}) public Subjects> testSubjects() { return subjects(a("foo"), b(1), c(true)); } @@ -47,11 +48,4 @@ public void divergeStaysInChoice() { assertEquals(Choice4.b("two"), b.diverge()); assertEquals(Choice4.c(true), c.diverge()); } - - @Test - public void bifunctorProperties() { - assertEquals(a, a.biMap(String::toUpperCase, bool -> !bool)); - assertEquals(b("TWO"), b.biMap(String::toUpperCase, bool -> !bool)); - assertEquals(c(false), c.biMap(String::toUpperCase, bool -> !bool)); - } } \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/adt/choice/Choice4Test.java b/src/test/java/com/jnape/palatable/lambda/adt/choice/Choice4Test.java index 2de1ec0f4..2b7f3bc88 100644 --- a/src/test/java/com/jnape/palatable/lambda/adt/choice/Choice4Test.java +++ b/src/test/java/com/jnape/palatable/lambda/adt/choice/Choice4Test.java @@ -7,6 +7,7 @@ import org.junit.Test; import org.junit.runner.RunWith; import testsupport.traits.ApplicativeLaws; +import testsupport.traits.BifunctorLaws; import testsupport.traits.FunctorLaws; import static com.jnape.palatable.lambda.adt.choice.Choice4.a; @@ -32,7 +33,7 @@ public void setUp() { d = d(4D); } - @TestTraits({FunctorLaws.class, ApplicativeLaws.class}) + @TestTraits({FunctorLaws.class, ApplicativeLaws.class, BifunctorLaws.class}) public Subjects> testSubjects() { return subjects(a("foo"), b(1), c(true), d('a')); } @@ -52,12 +53,4 @@ public void divergeStaysInChoice() { assertEquals(Choice5.c(true), c.diverge()); assertEquals(Choice5.d(4D), d.diverge()); } - - @Test - public void bifunctorProperties() { - assertEquals(a, a.biMap(c -> !c, d -> -d)); - assertEquals(b, b.biMap(c -> !c, d -> -d)); - assertEquals(c(false), c.biMap(c -> !c, d -> -d)); - assertEquals(d(-4D), d.biMap(c -> !c, d -> -d)); - } } \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/adt/choice/Choice5Test.java b/src/test/java/com/jnape/palatable/lambda/adt/choice/Choice5Test.java index 83a28d8f5..d699f07cf 100644 --- a/src/test/java/com/jnape/palatable/lambda/adt/choice/Choice5Test.java +++ b/src/test/java/com/jnape/palatable/lambda/adt/choice/Choice5Test.java @@ -7,6 +7,7 @@ import org.junit.Test; import org.junit.runner.RunWith; import testsupport.traits.ApplicativeLaws; +import testsupport.traits.BifunctorLaws; import testsupport.traits.FunctorLaws; import static com.jnape.palatable.lambda.adt.choice.Choice5.a; @@ -35,7 +36,7 @@ public void setUp() { e = e('z'); } - @TestTraits({FunctorLaws.class, ApplicativeLaws.class}) + @TestTraits({FunctorLaws.class, ApplicativeLaws.class, BifunctorLaws.class}) public Subjects> testSubjects() { return subjects(Choice5.a("foo"), Choice5.b(1), Choice5.c(true), Choice5.d('a'), Choice5.e(2d)); } @@ -48,13 +49,4 @@ public void convergeStaysInChoice() { assertEquals(Choice4.d(4d), d.converge(e -> Choice4.b(e.toString()))); assertEquals(Choice4.b("z"), e.converge(e -> Choice4.b(e.toString()))); } - - @Test - public void bifunctorProperties() { - assertEquals(a, a.biMap(d -> -d, Character::toUpperCase)); - assertEquals(b, b.biMap(d -> -d, Character::toUpperCase)); - assertEquals(c, c.biMap(d -> -d, Character::toUpperCase)); - assertEquals(d(-4D), d.biMap(d -> -d, Character::toUpperCase)); - assertEquals(e('Z'), e.biMap(d -> -d, Character::toUpperCase)); - } } \ 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 47fb3c6da..f4ea2bb82 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,6 +6,7 @@ import org.junit.Test; import org.junit.runner.RunWith; import testsupport.traits.ApplicativeLaws; +import testsupport.traits.BifunctorLaws; import testsupport.traits.FunctorLaws; import java.util.HashMap; @@ -29,7 +30,7 @@ public void setUp() throws Exception { tuple2 = new Tuple2<>(1, new SingletonHList<>(2)); } - @TestTraits({FunctorLaws.class, ApplicativeLaws.class}) + @TestTraits({FunctorLaws.class, ApplicativeLaws.class, BifunctorLaws.class}) public Tuple2 testSubject() { return tuple("one", 2); } @@ -77,11 +78,6 @@ public void fill() { assertEquals(tuple("foo", "foo"), Tuple2.fill("foo")); } - @Test - public void bifunctorProperties() { - assertEquals(new Tuple2<>("1", new SingletonHList<>("2")), tuple2.biMap(Object::toString, Object::toString)); - } - @Test public void mapEntryProperties() { assertEquals((Integer) 1, tuple2.getKey()); 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 67d7a85bb..068bbb036 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,6 +6,7 @@ import org.junit.Test; import org.junit.runner.RunWith; import testsupport.traits.ApplicativeLaws; +import testsupport.traits.BifunctorLaws; import testsupport.traits.FunctorLaws; import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; @@ -25,7 +26,7 @@ public void setUp() { tuple3 = new Tuple3<>(1, new Tuple2<>("2", new SingletonHList<>('3'))); } - @TestTraits({FunctorLaws.class, ApplicativeLaws.class}) + @TestTraits({FunctorLaws.class, ApplicativeLaws.class, BifunctorLaws.class}) public Tuple3 testSubject() { return tuple("one", 2, 3d); } @@ -75,9 +76,4 @@ public void into() { public void fill() { assertEquals(tuple("foo", "foo", "foo"), Tuple3.fill("foo")); } - - @Test - public void bifunctorProperties() { - assertEquals(new Tuple3<>(1, new Tuple2<>(2, new SingletonHList<>("3"))), tuple3.biMap(Integer::parseInt, Object::toString)); - } } 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 d66ce2875..8aa607afb 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,6 +6,7 @@ import org.junit.Test; import org.junit.runner.RunWith; import testsupport.traits.ApplicativeLaws; +import testsupport.traits.BifunctorLaws; import testsupport.traits.FunctorLaws; import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; @@ -25,7 +26,7 @@ public void setUp() { tuple4 = new Tuple4<>(1, new Tuple3<>("2", new Tuple2<>('3', new SingletonHList<>(false)))); } - @TestTraits({FunctorLaws.class, ApplicativeLaws.class}) + @TestTraits({FunctorLaws.class, ApplicativeLaws.class, BifunctorLaws.class}) public Tuple4 testSubject() { return tuple("one", 2, 3d, 4f); } @@ -78,10 +79,4 @@ public void into() { public void fill() { assertEquals(tuple("foo", "foo", "foo", "foo"), Tuple4.fill("foo")); } - - @Test - public void bifunctorProperties() { - assertEquals(new Tuple4<>(1, new Tuple3<>("2", new Tuple2<>("3", new SingletonHList<>(true)))), - tuple4.biMap(Object::toString, x -> !x)); - } } \ 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 48dd6efe2..128f4e385 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,6 +7,7 @@ import org.junit.Test; import org.junit.runner.RunWith; import testsupport.traits.ApplicativeLaws; +import testsupport.traits.BifunctorLaws; import testsupport.traits.FunctorLaws; import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; @@ -26,7 +27,7 @@ public void setUp() { tuple5 = new Tuple5<>(1, new Tuple4<>("2", new Tuple3<>('3', new Tuple2<>(false, new SingletonHList<>(5L))))); } - @TestTraits({FunctorLaws.class, ApplicativeLaws.class}) + @TestTraits({FunctorLaws.class, ApplicativeLaws.class, BifunctorLaws.class}) public Tuple5 testSubject() { return tuple("one", 2, 3d, 4f, '5'); } @@ -76,10 +77,4 @@ public void randomAccess() { public void fill() { assertEquals(tuple("foo", "foo", "foo", "foo", "foo"), Tuple5.fill("foo")); } - - @Test - public void bifunctorProperties() { - assertEquals(new Tuple5<>(1, new Tuple4<>("2", new Tuple3<>('3', new Tuple2<>(true, new SingletonHList<>("5"))))), - tuple5.biMap(x -> !x, Object::toString)); - } } \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functor/builtin/ConstTest.java b/src/test/java/com/jnape/palatable/lambda/functor/builtin/ConstTest.java index e8b99720d..eb9bb0167 100644 --- a/src/test/java/com/jnape/palatable/lambda/functor/builtin/ConstTest.java +++ b/src/test/java/com/jnape/palatable/lambda/functor/builtin/ConstTest.java @@ -2,23 +2,16 @@ 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.ApplicativeLaws; +import testsupport.traits.BifunctorLaws; import testsupport.traits.FunctorLaws; -import static org.junit.Assert.assertEquals; - @RunWith(Traits.class) public class ConstTest { - @TestTraits({FunctorLaws.class, ApplicativeLaws.class}) + @TestTraits({FunctorLaws.class, ApplicativeLaws.class, BifunctorLaws.class}) public Const testSubject() { return new Const<>(1); } - - @Test - public void bifunctorProperties() { - assertEquals("FOO", new Const("foo").biMap(String::toUpperCase, x -> x + 1).runConst()); - } } \ No newline at end of file diff --git a/src/test/java/testsupport/traits/BifunctorLaws.java b/src/test/java/testsupport/traits/BifunctorLaws.java new file mode 100644 index 000000000..b8f410472 --- /dev/null +++ b/src/test/java/testsupport/traits/BifunctorLaws.java @@ -0,0 +1,46 @@ +package testsupport.traits; + +import com.jnape.palatable.lambda.functions.builtin.fn2.Map; +import com.jnape.palatable.lambda.functor.Bifunctor; +import com.jnape.palatable.lambda.monoid.builtin.Present; +import com.jnape.palatable.traitor.traits.Trait; + +import java.util.Optional; +import java.util.function.Function; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; +import static java.util.Arrays.asList; + +public class BifunctorLaws implements Trait> { + + @Override + public void test(Bifunctor bifunctor) { + Iterable> testResults = Map., Optional>, Optional>map( + f -> f.apply(bifunctor), + asList(this::testLeftIdentity, this::testRightIdentity, this::testMutualIdentity) + ); + Present.present((x, y) -> x + "\n\t - " + y) + .reduceLeft(testResults) + .ifPresent(s -> { + throw new AssertionError("The following Bifunctor laws did not hold for instance of " + bifunctor.getClass() + ": \n\t - " + s); + }); + } + + private Optional testLeftIdentity(Bifunctor bifunctor) { + return bifunctor.biMapL(id()).equals(bifunctor) + ? Optional.empty() + : Optional.of("left identity (bifunctor.biMapL(id()).equals(bifunctor))"); + } + + private Optional testRightIdentity(Bifunctor bifunctor) { + return bifunctor.biMapR(id()).equals(bifunctor) + ? Optional.empty() + : Optional.of("right identity (bifunctor.biMapR(id()).equals(bifunctor))"); + } + + private Optional testMutualIdentity(Bifunctor bifunctor) { + return bifunctor.biMapL(id()).biMapR(id()).equals(bifunctor.biMap(id(), id())) + ? Optional.empty() + : Optional.of("mutual identity (bifunctor.biMapL(id()).biMapR(id()).equals(bifunctor.biMap(id(),id()))"); + } +} From 5bd8d2b4e23420cfdd727ba68940f1aee6ff402e Mon Sep 17 00:00:00 2001 From: jnape Date: Sun, 23 Apr 2017 11:51:02 -0500 Subject: [PATCH 06/24] Pulling Either#invert up to CoProduct2 --- CHANGELOG.md | 1 + .../com/jnape/palatable/lambda/adt/Either.java | 6 ------ .../jnape/palatable/lambda/adt/choice/Choice2.java | 5 +++++ .../palatable/lambda/adt/coproduct/CoProduct2.java | 14 ++++++++++++++ .../com/jnape/palatable/lambda/adt/EitherTest.java | 9 --------- .../lambda/adt/coproduct/CoProduct2Test.java | 6 ++++++ 6 files changed, 26 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c5de5d8a..71fae051e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/). ## [Unreleased] ### Added - `Applicative` arrives; all functors gain applicative properties +- `Either#invert` is pulled up into `CoProduct2` ### Changed - `Functor`, `Bifunctor`, and `Profunctor` (as well as all instances) get a unification parameter diff --git a/src/main/java/com/jnape/palatable/lambda/adt/Either.java b/src/main/java/com/jnape/palatable/lambda/adt/Either.java index 9c1b0fd8d..0660c1840 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/Either.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/Either.java @@ -123,12 +123,6 @@ public final Either flatMap(FunctionEither<L, R> into an Either<R, L>, preserving the current - * values. - * - * @return The inverted either - */ public final Either invert() { return flatMap(Either::right, Either::left); } diff --git a/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice2.java b/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice2.java index 94799eec9..05ccb9e1c 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice2.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice2.java @@ -29,6 +29,11 @@ public final Choice3 diverge() { return match(Choice3::a, Choice3::b); } + @Override + public Choice2 invert() { + return match(Choice2::b, Choice2::a); + } + @Override @SuppressWarnings("unchecked") public final Choice2 fmap(Function fn) { diff --git a/src/main/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct2.java b/src/main/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct2.java index 77f347412..33d3d7fcf 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct2.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct2.java @@ -93,4 +93,18 @@ default Optional projectA() { default Optional projectB() { return project()._2(); } + + /** + * Swap the type parameters. + * + * @return The inverted coproduct + */ + default CoProduct2 invert() { + return new CoProduct2() { + @Override + public R match(Function aFn, Function bFn) { + return CoProduct2.this.match(bFn, aFn); + } + }; + } } diff --git a/src/test/java/com/jnape/palatable/lambda/adt/EitherTest.java b/src/test/java/com/jnape/palatable/lambda/adt/EitherTest.java index 9ffd5db41..688161a0e 100644 --- a/src/test/java/com/jnape/palatable/lambda/adt/EitherTest.java +++ b/src/test/java/com/jnape/palatable/lambda/adt/EitherTest.java @@ -104,15 +104,6 @@ public void dyadicFlatMapDuallyLiftsAndFlattensBackToEither() { assertThat(right.flatMap(l -> left(l + "bar"), r -> right(r + 1)), is(right(2))); } - @Test - public void invertSwapsParameters() { - Either left = left("foo"); - assertEquals(right("foo"), left.invert()); - - Either right = right(1); - assertEquals(left(1), right.invert()); - } - @Test public void mergeDuallyLiftsAndCombinesBiasingLeft() { Either left1 = left("foo"); diff --git a/src/test/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct2Test.java b/src/test/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct2Test.java index 3f5be9f76..b9ac2f70d 100644 --- a/src/test/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct2Test.java +++ b/src/test/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct2Test.java @@ -48,4 +48,10 @@ public void projections() { assertEquals(tuple(a.projectA(), a.projectB()), a.project()); assertEquals(tuple(b.projectA(), b.projectB()), b.project()); } + + @Test + public void invert() { + assertEquals(Optional.of(1), a.invert().projectB()); + assertEquals(Optional.of(true), b.invert().projectA()); + } } \ No newline at end of file From 9e24d54bcd3e5506b62001b38a83d3bcfabfc154 Mon Sep 17 00:00:00 2001 From: jnape Date: Sun, 30 Apr 2017 13:41:55 -0500 Subject: [PATCH 07/24] Loosening Partition constraint on Either up to CoProduct2 --- .../functions/builtin/fn2/Partition.java | 36 +++++++++---------- .../functions/builtin/fn2/PartitionTest.java | 21 ++++++----- 2 files changed, 28 insertions(+), 29 deletions(-) diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Partition.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Partition.java index 90f142288..97abfc3b9 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Partition.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Partition.java @@ -1,6 +1,6 @@ package com.jnape.palatable.lambda.functions.builtin.fn2; -import com.jnape.palatable.lambda.adt.Either; +import com.jnape.palatable.lambda.adt.coproduct.CoProduct2; import com.jnape.palatable.lambda.adt.hlist.Tuple2; import com.jnape.palatable.lambda.functions.Fn1; import com.jnape.palatable.lambda.functions.Fn2; @@ -9,22 +9,22 @@ import java.util.function.Function; 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.Filter.filter; import static com.jnape.palatable.lambda.functions.builtin.fn2.Map.map; /** - * Given an Iterable<A> as and a disjoint mapping function a -> Either<L, - * R>, return a {@link Tuple2} over the lazily unwrapped left L and right R values - * in the first and second slots, respectively. Note that while the tuple must be constructed eagerly, the left and - * right iterables contained therein are both lazy, so comprehension over infinite iterables is supported. + * Given an Iterable<A> as and a disjoint mapping function a -> + * CoProduct2<A, B>, return a {@link Tuple2} over the lazily unwrapped left A and right + * B values in the first and second slots, respectively. Note that while the tuple must be constructed + * eagerly, the left and right iterables contained therein are both lazy, so comprehension over infinite iterables is + * supported. * * @param A type contravariant to the input Iterable element type - * @param The output left Iterable element type, as well as the Either L type - * @param The output right Iterable element type, as well as the Either R type - * @see Either + * @param The output left Iterable element type, as well as the CoProduct2 A type + * @param The output right Iterable element type, as well as the CoProduct2 B type + * @see CoProduct2 */ -public final class Partition implements Fn2>, Iterable, Tuple2, Iterable>> { +public final class Partition implements Fn2>, Iterable, Tuple2, Iterable>> { private static final Partition INSTANCE = new Partition(); @@ -32,32 +32,28 @@ private Partition() { } @Override - public Tuple2, Iterable> apply(Function> function, + public Tuple2, Iterable> apply(Function> function, Iterable as) { - Iterable> eithers = map(function, as); + Iterable> coproducts = map(function, as); - Iterable lefts = unwrapRight(map(Either::invert, eithers)); - Iterable rights = unwrapRight(map(id(), eithers)); + Iterable lefts = map(Optional::get, filter(Optional::isPresent, map(CoProduct2::projectA, coproducts))); + Iterable rights = map(Optional::get, filter(Optional::isPresent, map(CoProduct2::projectB, coproducts))); return tuple(lefts, rights); } - private Iterable unwrapRight(Iterable> eithers) { - return map(Optional::get, filter(Optional::isPresent, map(Either::toOptional, eithers))); - } - @SuppressWarnings("unchecked") public static Partition partition() { return INSTANCE; } public static Fn1, Tuple2, Iterable>> partition( - Function> function) { + Function> function) { return Partition.partition().apply(function); } public static Tuple2, Iterable> partition( - Function> function, + Function> function, Iterable as) { return Partition.partition(function).apply(as); } diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/PartitionTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/PartitionTest.java index 3f0fe8b84..506c6e5ae 100644 --- a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/PartitionTest.java +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/PartitionTest.java @@ -1,9 +1,10 @@ package com.jnape.palatable.lambda.functions.builtin.fn2; -import com.jnape.palatable.lambda.adt.Either; +import com.jnape.palatable.lambda.adt.coproduct.CoProduct2; 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.framework.Subjects; import com.jnape.palatable.traitor.runners.Traits; import org.junit.Test; import org.junit.runner.RunWith; @@ -12,13 +13,14 @@ import testsupport.traits.ImmutableIteration; import testsupport.traits.Laziness; -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.choice.Choice2.a; +import static com.jnape.palatable.lambda.adt.choice.Choice2.b; import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; import static com.jnape.palatable.lambda.functions.builtin.fn1.Cycle.cycle; import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; import static com.jnape.palatable.lambda.functions.builtin.fn2.Partition.partition; import static com.jnape.palatable.lambda.functions.builtin.fn2.Take.take; +import static com.jnape.palatable.traitor.framework.Subjects.subjects; import static java.util.Arrays.asList; import static org.junit.Assert.assertThat; import static testsupport.matchers.IterableMatcher.iterates; @@ -27,14 +29,15 @@ public class PartitionTest { @TestTraits({Laziness.class, EmptyIterableSupport.class, FiniteIteration.class, ImmutableIteration.class}) - public Fn1 createTraitsLeftTestSubject() { - return partition(constantly(left(1))).andThen(Tuple2::_1); + public Subjects> createTraitsTestSubject() { + return subjects(partition(constantly(a(1))).andThen(Tuple2::_1), + partition(constantly(b(1))).andThen(Tuple2::_2)); } @Test - public void partitionsIterableIntoLeftsAndRights() { + public void partitionsIterableIntoAsAndBs() { Iterable strings = asList("one", "two", "three", "four", "five"); - Tuple2, Iterable> partition = partition(s -> s.length() % 2 == 1 ? left(s) : right(s.length()), strings); + Tuple2, Iterable> partition = partition(s -> s.length() % 2 == 1 ? a(s) : b(s.length()), strings); assertThat(partition._1(), iterates("one", "two", "three")); assertThat(partition._2(), iterates(4, 4)); @@ -42,8 +45,8 @@ public void partitionsIterableIntoLeftsAndRights() { @Test public void infiniteListSupport() { - Iterable> eithers = cycle(left("left"), right(1)); - Tuple2, Iterable> partition = partition(id(), eithers); + Iterable> coproducts = cycle(a("left"), b(1)); + Tuple2, Iterable> partition = partition(id(), coproducts); assertThat(take(3, partition._1()), iterates("left", "left", "left")); assertThat(take(3, partition._2()), iterates(1, 1, 1)); From 49ce63816fa232cad1fa9b2503debc1d4d21ae3c Mon Sep 17 00:00:00 2001 From: jnape Date: Sun, 30 Apr 2017 15:11:29 -0500 Subject: [PATCH 08/24] Adding CoProductN#embed --- CHANGELOG.md | 5 +- .../jnape/palatable/lambda/adt/Either.java | 4 +- .../palatable/lambda/adt/choice/Choice2.java | 2 +- .../palatable/lambda/adt/choice/Choice3.java | 4 +- .../palatable/lambda/adt/choice/Choice4.java | 4 +- .../palatable/lambda/adt/choice/Choice5.java | 4 +- .../lambda/adt/coproduct/CoProduct2.java | 28 ++++++++--- .../lambda/adt/coproduct/CoProduct3.java | 33 ++++++++++--- .../lambda/adt/coproduct/CoProduct4.java | 39 ++++++++++++--- .../lambda/adt/coproduct/CoProduct5.java | 39 ++++++++++++--- .../functions/builtin/fn2/Partition.java | 10 ++-- .../lambda/adt/coproduct/CoProduct2Test.java | 18 ++++--- .../lambda/adt/coproduct/CoProduct3Test.java | 25 ++++++---- .../lambda/adt/coproduct/CoProduct4Test.java | 28 +++++++---- .../lambda/adt/coproduct/CoProduct5Test.java | 47 +++++++++++-------- .../functions/builtin/fn2/PartitionTest.java | 2 +- 16 files changed, 208 insertions(+), 84 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 71fae051e..6c6a78308 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,12 +6,15 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/). ## [Unreleased] ### Added - `Applicative` arrives; all functors gain applicative properties -- `Either#invert` is pulled up into `CoProduct2` +- `Either#invert` is pulled up into `CoProduct2` and additionally specialized for `Choice2` +- `CoProductN#embed` ### Changed - `Functor`, `Bifunctor`, and `Profunctor` (as well as all instances) get a unification parameter - `Identity` supports value equality - `Const` supports value equality +- `partition` now only requires iterables of `CoProudct2` +- `CoProductN`s receive a unification parameter, which trickles down to `Either` and `Choice`s ## [1.5.6] - 2017-02-11 ### Added diff --git a/src/main/java/com/jnape/palatable/lambda/adt/Either.java b/src/main/java/com/jnape/palatable/lambda/adt/Either.java index 0660c1840..9f36d0bb5 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/Either.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/Either.java @@ -25,7 +25,7 @@ * @param The left parameter type * @param The right parameter type */ -public abstract class Either implements CoProduct2, Applicative>, Bifunctor { +public abstract class Either implements CoProduct2>, Applicative>, Bifunctor { private Either() { } @@ -123,6 +123,7 @@ public final Either flatMap(Function invert() { return flatMap(Either::right, Either::left); } @@ -186,6 +187,7 @@ public Either peek(Consumer leftConsumer, Consumer rightConsumer) { * @param the result type * @return the result of applying the appropriate mapping function to the wrapped value */ + @Override public abstract V match(Function leftFn, Function rightFn); @Override diff --git a/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice2.java b/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice2.java index 05ccb9e1c..81bf962f2 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice2.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice2.java @@ -19,7 +19,7 @@ * @see Either * @see Choice3 */ -public abstract class Choice2 implements CoProduct2, Applicative>, Bifunctor { +public abstract class Choice2 implements CoProduct2>, Applicative>, Bifunctor { private Choice2() { } diff --git a/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice3.java b/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice3.java index 4ef55c43b..21cd3d5c9 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice3.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice3.java @@ -18,7 +18,7 @@ * @see Choice2 * @see Choice4 */ -public abstract class Choice3 implements CoProduct3, Applicative>, Bifunctor> { +public abstract class Choice3 implements CoProduct3>, Applicative>, Bifunctor> { private Choice3() { } @@ -29,7 +29,7 @@ public final Choice4 diverge() { } @Override - public final Choice2 converge(Function> convergenceFn) { + public final Choice2 converge(Function> convergenceFn) { return match(Choice2::a, Choice2::b, convergenceFn.andThen(cp2 -> cp2.match(Choice2::a, Choice2::b))); } diff --git a/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice4.java b/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice4.java index 12d562125..488216034 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice4.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice4.java @@ -19,7 +19,7 @@ * @see Choice3 * @see Choice5 */ -public abstract class Choice4 implements CoProduct4, Applicative>, Bifunctor> { +public abstract class Choice4 implements CoProduct4>, Applicative>, Bifunctor> { private Choice4() { } @@ -30,7 +30,7 @@ public Choice5 diverge() { } @Override - public Choice3 converge(Function> convergenceFn) { + public Choice3 converge(Function> convergenceFn) { return match(Choice3::a, Choice3::b, Choice3::c, diff --git a/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice5.java b/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice5.java index 33d5cb79b..e21abc057 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice5.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice5.java @@ -19,13 +19,13 @@ * @param a type parameter representing the fifth possible type of this choice * @see Choice4 */ -public abstract class Choice5 implements CoProduct5, Applicative>, Bifunctor> { +public abstract class Choice5 implements CoProduct5>, Applicative>, Bifunctor> { private Choice5() { } @Override - public Choice4 converge(Function> convergenceFn) { + public Choice4 converge(Function> convergenceFn) { return match(Choice4::a, Choice4::b, Choice4::c, diff --git a/src/main/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct2.java b/src/main/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct2.java index 33d3d7fcf..fe2a31da9 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct2.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct2.java @@ -22,7 +22,7 @@ * @see Either */ @FunctionalInterface -public interface CoProduct2 { +public interface CoProduct2> { /** * Type-safe convergence requiring a match against all potential types. @@ -30,7 +30,7 @@ public interface CoProduct2 { * @param aFn morphism A -> R * @param bFn morphism B -> R * @param result type - * @return the result of applying the appropriate morphism from whichever type is represented by this coproduct to R + * @return the result of applying the appropriate morphism to this coproduct's unwrapped value */ R match(Function aFn, Function bFn); @@ -55,8 +55,8 @@ public interface CoProduct2 { * @param the additional possible type of this coproduct * @return a coproduct of the initial types plus the new type */ - default CoProduct3 diverge() { - return new CoProduct3() { + default CoProduct3> diverge() { + return new CoProduct3>() { @Override public R match(Function aFn, Function bFn, Function cFn) { @@ -99,12 +99,28 @@ default Optional projectB() { * * @return The inverted coproduct */ - default CoProduct2 invert() { - return new CoProduct2() { + default CoProduct2> invert() { + return new CoProduct2>() { @Override public R match(Function aFn, Function bFn) { return CoProduct2.this.match(bFn, aFn); } }; } + + /** + * Embed this coproduct inside another value; that is, given morphisms from this coproduct to R, apply + * the appropriate morphism to this coproduct as a whole. Like {@link CoProduct2#match}, but without unwrapping the + * value. + * + * @param aFn morphism A v B -> R, applied in the A case + * @param bFn morphism A v B -> R, applied in the B case + * @param result type + * @return the result of applying the appropriate morphism to this coproduct + */ + @SuppressWarnings("unchecked") + default R embed(Function aFn, + Function bFn) { + return match(__ -> aFn.apply((CP2) this), __ -> bFn.apply((CP2) this)); + } } diff --git a/src/main/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct3.java b/src/main/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct3.java index 1bc4312d7..5eab6ff97 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct3.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct3.java @@ -16,7 +16,7 @@ * @see CoProduct2 */ @FunctionalInterface -public interface CoProduct3 { +public interface CoProduct3> { /** * Type-safe convergence requiring a match against all potential types. @@ -38,8 +38,8 @@ R match(Function aFn, Function CoProduct4 diverge() { - return new CoProduct4() { + default CoProduct4> diverge() { + return new CoProduct4>() { @Override public R match(Function aFn, Function bFn, Function cFn, Function dFn) { @@ -61,13 +61,14 @@ public R match(Function aFn, Function converge(Function> convergenceFn) { - return match(a -> new CoProduct2() { + default CoProduct2> converge( + Function> convergenceFn) { + return match(a -> new CoProduct2>() { @Override public R match(Function aFn, Function bFn) { return aFn.apply(a); } - }, b -> new CoProduct2() { + }, b -> new CoProduct2>() { @Override public R match(Function aFn, Function bFn) { return bFn.apply(b); @@ -113,4 +114,24 @@ default Optional projectB() { default Optional projectC() { return project()._3(); } + + /** + * Embed this coproduct inside another value; that is, given morphisms from this coproduct to R, apply + * the appropriate morphism to this coproduct as a whole. Like {@link CoProduct3#match}, but without unwrapping the + * value. + * + * @param aFn morphism A v B v C -> R, applied in the A case + * @param bFn morphism A v B v C -> R, applied in the B case + * @param cFn morphism A v B v C -> R, applied in the C case + * @param result type + * @return the result of applying the appropriate morphism to this coproduct + */ + @SuppressWarnings("unchecked") + default R embed(Function aFn, + Function bFn, + Function cFn) { + return match(__ -> aFn.apply((CP3) this), + __ -> bFn.apply((CP3) this), + __ -> cFn.apply((CP3) this)); + } } diff --git a/src/main/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct4.java b/src/main/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct4.java index 739252a54..ffc978b15 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct4.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct4.java @@ -17,7 +17,7 @@ * @see CoProduct2 */ @FunctionalInterface -public interface CoProduct4 { +public interface CoProduct4> { /** * Type-safe convergence requiring a match against all potential types. @@ -42,8 +42,8 @@ R match(Function aFn, * @return a Coproduct5<A, B, C, D, E> * @see CoProduct2#diverge() */ - default CoProduct5 diverge() { - return new CoProduct5() { + default CoProduct5> diverge() { + return new CoProduct5>() { @Override public R match(Function aFn, Function bFn, Function cFn, Function dFn, @@ -61,20 +61,21 @@ public R match(Function aFn, Function converge(Function> convergenceFn) { - return match(a -> new CoProduct3() { + default CoProduct3> converge( + Function> convergenceFn) { + return match(a -> new CoProduct3>() { @Override public R match(Function aFn, Function bFn, Function cFn) { return aFn.apply(a); } - }, b -> new CoProduct3() { + }, b -> new CoProduct3>() { @Override public R match(Function aFn, Function bFn, Function cFn) { return bFn.apply(b); } - }, c -> new CoProduct3() { + }, c -> new CoProduct3>() { @Override public R match(Function aFn, Function bFn, Function cFn) { @@ -131,4 +132,28 @@ default Optional projectC() { default Optional projectD() { return project()._4(); } + + /** + * Embed this coproduct inside another value; that is, given morphisms from this coproduct to R, apply + * the appropriate morphism to this coproduct as a whole. Like {@link CoProduct4#match}, but without unwrapping the + * value. + * + * @param aFn morphism A v B v C v D -> R, applied in the A case + * @param bFn morphism A v B v C v D -> R, applied in the B case + * @param cFn morphism A v B v C v D -> R, applied in the C case + * @param dFn morphism A v B v C v D -> R, applied in the D case + * @param result type + * @return the result of applying the appropriate morphism to this coproduct + */ + @SuppressWarnings("unchecked") + default R embed(Function aFn, + Function bFn, + Function cFn, + Function dFn) { + return match(__ -> aFn.apply((CP4) this), + __ -> bFn.apply((CP4) this), + __ -> cFn.apply((CP4) this), + __ -> dFn.apply((CP4) this)); + } + } diff --git a/src/main/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct5.java b/src/main/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct5.java index 9379a3d7c..ddfe27b1d 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct5.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct5.java @@ -19,7 +19,7 @@ * @see CoProduct2 */ @FunctionalInterface -public interface CoProduct5 { +public interface CoProduct5> { /** * Type-safe convergence requiring a match against all potential types. @@ -46,26 +46,27 @@ R match(Function aFn, * @param convergenceFn morphism E -> {@link CoProduct4}<A, B, C, D> * @return a CoProduct4<A, B, C, D> */ - default CoProduct4 converge(Function> convergenceFn) { - return match(a -> new CoProduct4() { + default CoProduct4> converge( + Function> convergenceFn) { + return match(a -> new CoProduct4>() { @Override public R match(Function aFn, Function bFn, Function cFn, Function dFn) { return aFn.apply(a); } - }, b -> new CoProduct4() { + }, b -> new CoProduct4>() { @Override public R match(Function aFn, Function bFn, Function cFn, Function dFn) { return bFn.apply(b); } - }, c -> new CoProduct4() { + }, c -> new CoProduct4>() { @Override public R match(Function aFn, Function bFn, Function cFn, Function dFn) { return cFn.apply(c); } - }, d -> new CoProduct4() { + }, d -> new CoProduct4>() { @Override public R match(Function aFn, Function bFn, Function cFn, Function dFn) { @@ -132,4 +133,30 @@ default Optional projectD() { default Optional projectE() { return project()._5(); } + + /** + * Embed this coproduct inside another value; that is, given morphisms from this coproduct to R, apply + * the appropriate morphism to this coproduct as a whole. Like {@link CoProduct5#match}, but without unwrapping the + * value. + * + * @param aFn morphism A v B v C v D v E -> R, applied in the A case + * @param bFn morphism A v B v C v D v E -> R, applied in the B case + * @param cFn morphism A v B v C v D v E -> R, applied in the C case + * @param dFn morphism A v B v C v D v E -> R, applied in the D case + * @param eFn morphism A v B v C v D v E -> R, applied in the E case + * @param result type + * @return the result of applying the appropriate morphism to this coproduct + */ + @SuppressWarnings("unchecked") + default R embed(Function aFn, + Function bFn, + Function cFn, + Function dFn, + Function eFn) { + return match(__ -> aFn.apply((CP5) this), + __ -> bFn.apply((CP5) this), + __ -> cFn.apply((CP5) this), + __ -> dFn.apply((CP5) this), + __ -> eFn.apply((CP5) this)); + } } diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Partition.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Partition.java index 97abfc3b9..647bc9c33 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Partition.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Partition.java @@ -24,7 +24,7 @@ * @param The output right Iterable element type, as well as the CoProduct2 B type * @see CoProduct2 */ -public final class Partition implements Fn2>, Iterable, Tuple2, Iterable>> { +public final class Partition implements Fn2>, Iterable, Tuple2, Iterable>> { private static final Partition INSTANCE = new Partition(); @@ -32,9 +32,9 @@ private Partition() { } @Override - public Tuple2, Iterable> apply(Function> function, + public Tuple2, Iterable> apply(Function> function, Iterable as) { - Iterable> coproducts = map(function, as); + Iterable> coproducts = map(function, as); Iterable lefts = map(Optional::get, filter(Optional::isPresent, map(CoProduct2::projectA, coproducts))); Iterable rights = map(Optional::get, filter(Optional::isPresent, map(CoProduct2::projectB, coproducts))); @@ -48,12 +48,12 @@ public static Partition partition() { } public static Fn1, Tuple2, Iterable>> partition( - Function> function) { + Function> function) { return Partition.partition().apply(function); } public static Tuple2, Iterable> partition( - Function> function, + Function> function, Iterable as) { return Partition.partition(function).apply(as); } diff --git a/src/test/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct2Test.java b/src/test/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct2Test.java index b9ac2f70d..b5fc3e4fd 100644 --- a/src/test/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct2Test.java +++ b/src/test/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct2Test.java @@ -12,18 +12,18 @@ public class CoProduct2Test { - private CoProduct2 a; - private CoProduct2 b; + private CoProduct2 a; + private CoProduct2 b; @Before public void setUp() { - a = new CoProduct2() { + a = new CoProduct2>() { @Override public R match(Function aFn, Function bFn) { return aFn.apply(1); } }; - b = new CoProduct2() { + b = new CoProduct2>() { @Override public R match(Function aFn, Function bFn) { return bFn.apply(true); @@ -33,10 +33,10 @@ public R match(Function aFn, Function divergeA = a.diverge(); + CoProduct3 divergeA = a.diverge(); assertEquals(1, divergeA.match(id(), id(), id())); - CoProduct3 divergeB = b.diverge(); + CoProduct3 divergeB = b.diverge(); assertEquals(true, divergeB.match(id(), id(), id())); } @@ -54,4 +54,10 @@ public void invert() { assertEquals(Optional.of(1), a.invert().projectB()); assertEquals(Optional.of(true), b.invert().projectA()); } + + @Test + public void embed() { + assertEquals(Optional.of(a), a.embed(Optional::of, Optional::of)); + assertEquals(Optional.of(b), b.embed(Optional::of, Optional::of)); + } } \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct3Test.java b/src/test/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct3Test.java index 2288b3dd2..1e3217b37 100644 --- a/src/test/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct3Test.java +++ b/src/test/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct3Test.java @@ -12,27 +12,27 @@ public class CoProduct3Test { - private CoProduct3 a; - private CoProduct3 b; - private CoProduct3 c; + private CoProduct3 a; + private CoProduct3 b; + private CoProduct3 c; @Before public void setUp() { - a = new CoProduct3() { + a = new CoProduct3>() { @Override public R match(Function aFn, Function bFn, Function cFn) { return aFn.apply(1); } }; - b = new CoProduct3() { + b = new CoProduct3>() { @Override public R match(Function aFn, Function bFn, Function cFn) { return bFn.apply("two"); } }; - c = new CoProduct3() { + c = new CoProduct3>() { @Override public R match(Function aFn, Function bFn, Function cFn) { @@ -57,12 +57,12 @@ public void diverge() { @Test public void converge() { - Function> convergenceFn = x -> x ? new CoProduct2() { + Function> convergenceFn = x -> x ? new CoProduct2>() { @Override public R match(Function aFn, Function bFn) { return aFn.apply(-1); } - } : new CoProduct2() { + } : new CoProduct2>() { @Override public R match(Function aFn, Function bFn) { return bFn.apply("false"); @@ -71,7 +71,7 @@ public R match(Function aFn, Function() { + assertEquals("false", new CoProduct3>() { @Override public R match(Function aFn, Function bFn, Function cFn) { @@ -90,4 +90,11 @@ public void projections() { assertEquals(tuple(b.projectA(), b.projectB(), b.projectC()), b.project()); assertEquals(tuple(c.projectA(), c.projectB(), c.projectC()), c.project()); } + + @Test + public void embed() { + assertEquals(Optional.of(a), a.embed(Optional::of, Optional::of, Optional::of)); + assertEquals(Optional.of(b), b.embed(Optional::of, Optional::of, Optional::of)); + assertEquals(Optional.of(c), c.embed(Optional::of, Optional::of, Optional::of)); + } } \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct4Test.java b/src/test/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct4Test.java index 015aed644..6c38aa5a4 100644 --- a/src/test/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct4Test.java +++ b/src/test/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct4Test.java @@ -16,10 +16,10 @@ public class CoProduct4Test { - private CoProduct4 a; - private CoProduct4 b; - private CoProduct4 c; - private CoProduct4 d; + private CoProduct4 a; + private CoProduct4 b; + private CoProduct4 c; + private CoProduct4 d; @Before public void setUp() throws Exception { @@ -47,20 +47,20 @@ public void diverge() { @Test public void converge() { - Function> convergenceFn = x -> x.equals(1d) ? new CoProduct3() { + Function> convergenceFn = x -> x.equals(1d) ? new CoProduct3>() { @Override public R match(Function aFn, Function bFn, Function cFn) { return aFn.apply(1); } } : x.equals(2d) - ? new CoProduct3() { + ? new CoProduct3>() { @Override public R match(Function aFn, Function bFn, Function cFn) { return bFn.apply("b"); } - } : new CoProduct3() { + } : new CoProduct3>() { @Override public R match(Function aFn, Function bFn, Function cFn) { @@ -70,21 +70,21 @@ public R match(Function aFn, Function() { + assertEquals(1, new CoProduct4>() { @Override public R match(Function aFn, Function bFn, Function cFn, Function dFn) { return dFn.apply(1d); } }.converge(convergenceFn).match(id(), id(), id())); - assertEquals("b", new CoProduct4() { + assertEquals("b", new CoProduct4>() { @Override public R match(Function aFn, Function bFn, Function cFn, Function dFn) { return dFn.apply(2d); } }.converge(convergenceFn).match(id(), id(), id())); - assertEquals(false, new CoProduct4() { + assertEquals(false, new CoProduct4>() { @Override public R match(Function aFn, Function bFn, Function cFn, Function dFn) { @@ -105,4 +105,12 @@ public void projections() { assertEquals(tuple(c.projectA(), c.projectB(), c.projectC(), c.projectD()), c.project()); assertEquals(tuple(d.projectA(), d.projectB(), d.projectC(), d.projectD()), d.project()); } + + @Test + public void embed() { + assertEquals(Optional.of(a), a.embed(Optional::of, Optional::of, Optional::of, Optional::of)); + assertEquals(Optional.of(b), b.embed(Optional::of, Optional::of, Optional::of, Optional::of)); + assertEquals(Optional.of(c), c.embed(Optional::of, Optional::of, Optional::of, Optional::of)); + assertEquals(Optional.of(d), d.embed(Optional::of, Optional::of, Optional::of, Optional::of)); + } } \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct5Test.java b/src/test/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct5Test.java index 5a34cc763..fff9feaac 100644 --- a/src/test/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct5Test.java +++ b/src/test/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct5Test.java @@ -12,15 +12,15 @@ public class CoProduct5Test { - private CoProduct5 a; - private CoProduct5 b; - private CoProduct5 c; - private CoProduct5 d; - private CoProduct5 e; + private CoProduct5 a; + private CoProduct5 b; + private CoProduct5 c; + private CoProduct5 d; + private CoProduct5 e; @Before public void setUp() { - a = new CoProduct5() { + a = new CoProduct5>() { @Override public R match(Function aFn, Function bFn, Function cFn, Function dFn, @@ -28,7 +28,7 @@ public R match(Function aFn, Function() { + b = new CoProduct5>() { @Override public R match(Function aFn, Function bFn, Function cFn, Function dFn, @@ -36,7 +36,7 @@ public R match(Function aFn, Function() { + c = new CoProduct5>() { @Override public R match(Function aFn, Function bFn, Function cFn, Function dFn, @@ -44,7 +44,7 @@ public R match(Function aFn, Function() { + d = new CoProduct5>() { @Override public R match(Function aFn, Function bFn, Function cFn, Function dFn, @@ -52,7 +52,7 @@ public R match(Function aFn, Function() { + e = new CoProduct5>() { @Override public R match(Function aFn, Function bFn, Function cFn, Function dFn, @@ -73,8 +73,8 @@ public void match() { @Test public void converge() { - Function> convergenceFn = x -> - x.equals('a') ? new CoProduct4() { + Function>> convergenceFn = x -> + x.equals('a') ? new CoProduct4>() { @Override public R match(Function aFn, Function bFn, @@ -83,7 +83,7 @@ public R match(Function aFn, return aFn.apply(1); } } : x.equals('b') - ? new CoProduct4() { + ? new CoProduct4>() { @Override public R match(Function aFn, Function bFn, @@ -92,7 +92,7 @@ public R match(Function aFn, return bFn.apply("b"); } } : x.equals('c') - ? new CoProduct4() { + ? new CoProduct4>() { @Override public R match(Function aFn, Function bFn, @@ -100,7 +100,7 @@ public R match(Function aFn, Function dFn) { return cFn.apply(false); } - } : new CoProduct4() { + } : new CoProduct4>() { @Override public R match(Function aFn, Function bFn, @@ -113,7 +113,7 @@ public R match(Function aFn, assertEquals("two", b.converge(convergenceFn).match(id(), id(), id(), id())); assertEquals(true, c.converge(convergenceFn).match(id(), id(), id(), id())); assertEquals(4D, d.converge(convergenceFn).match(id(), id(), id(), id())); - assertEquals(1, new CoProduct5() { + assertEquals(1, new CoProduct5>() { @Override public R match(Function aFn, Function bFn, Function cFn, Function dFn, @@ -121,7 +121,7 @@ public R match(Function aFn, Function() { + assertEquals("b", new CoProduct5>() { @Override public R match(Function aFn, Function bFn, Function cFn, Function dFn, @@ -129,7 +129,7 @@ public R match(Function aFn, Function() { + assertEquals(false, new CoProduct5>() { @Override public R match(Function aFn, Function bFn, Function cFn, Function dFn, @@ -137,7 +137,7 @@ public R match(Function aFn, Function() { + assertEquals(1d, new CoProduct5>() { @Override public R match(Function aFn, Function bFn, Function cFn, Function dFn, @@ -161,4 +161,13 @@ public void projections() { assertEquals(tuple(d.projectA(), d.projectB(), d.projectC(), d.projectD(), d.projectE()), d.project()); assertEquals(tuple(e.projectA(), e.projectB(), e.projectC(), e.projectD(), e.projectE()), e.project()); } + + @Test + public void embed() { + assertEquals(Optional.of(a), a.embed(Optional::of, Optional::of, Optional::of, Optional::of, Optional::of)); + assertEquals(Optional.of(b), b.embed(Optional::of, Optional::of, Optional::of, Optional::of, Optional::of)); + assertEquals(Optional.of(c), c.embed(Optional::of, Optional::of, Optional::of, Optional::of, Optional::of)); + assertEquals(Optional.of(d), d.embed(Optional::of, Optional::of, Optional::of, Optional::of, Optional::of)); + assertEquals(Optional.of(e), e.embed(Optional::of, Optional::of, Optional::of, Optional::of, Optional::of)); + } } \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/PartitionTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/PartitionTest.java index 506c6e5ae..05366dfb4 100644 --- a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/PartitionTest.java +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/PartitionTest.java @@ -45,7 +45,7 @@ public void partitionsIterableIntoAsAndBs() { @Test public void infiniteListSupport() { - Iterable> coproducts = cycle(a("left"), b(1)); + Iterable> coproducts = cycle(a("left"), b(1)); Tuple2, Iterable> partition = partition(id(), coproducts); assertThat(take(3, partition._1()), iterates("left", "left", "left")); From df9ccd214c9e8773eeb8cdd193ac53499a8f56f3 Mon Sep 17 00:00:00 2001 From: jnape Date: Mon, 1 May 2017 00:56:52 -0500 Subject: [PATCH 09/24] Any static overload returns Predicate --- .../com/jnape/palatable/lambda/functions/builtin/fn2/Any.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Any.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Any.java index 09917f2a3..fa8db83b4 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Any.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Any.java @@ -1,7 +1,7 @@ package com.jnape.palatable.lambda.functions.builtin.fn2; -import com.jnape.palatable.lambda.functions.Fn1; import com.jnape.palatable.lambda.functions.specialized.BiPredicate; +import com.jnape.palatable.lambda.functions.specialized.Predicate; import java.util.function.Function; @@ -34,7 +34,7 @@ public static Any any() { return INSTANCE; } - public static Fn1, Boolean> any(Function predicate) { + public static Predicate> any(Function predicate) { return Any.any().apply(predicate); } From ade5e4719fd6cfd7bd00dd55c22a06f9365dccab Mon Sep 17 00:00:00 2001 From: jnape Date: Mon, 1 May 2017 01:06:30 -0500 Subject: [PATCH 10/24] Adding #empty, a simple predicate to determine if an Iterable is empty --- CHANGELOG.md | 4 ++- .../lambda/functions/builtin/fn1/Empty.java | 30 ++++++++++++++++ .../lambda/functions/builtin/fn1/Not.java | 36 +++++++++++++++++++ .../functions/builtin/fn1/EmptyTest.java | 22 ++++++++++++ .../lambda/functions/builtin/fn1/NotTest.java | 18 ++++++++++ 5 files changed, 109 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Empty.java create mode 100644 src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Not.java create mode 100644 src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/EmptyTest.java create mode 100644 src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/NotTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 6c6a78308..b265cba79 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,9 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/). ### Added - `Applicative` arrives; all functors gain applicative properties - `Either#invert` is pulled up into `CoProduct2` and additionally specialized for `Choice2` -- `CoProductN#embed` +- `CoProductN#embed` +- `not`, used for negating predicate functions +- `empty`, used to test if an Iterable is empty ### Changed - `Functor`, `Bifunctor`, and `Profunctor` (as well as all instances) get a unification parameter diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Empty.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Empty.java new file mode 100644 index 000000000..9906aa803 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Empty.java @@ -0,0 +1,30 @@ +package com.jnape.palatable.lambda.functions.builtin.fn1; + +import com.jnape.palatable.lambda.functions.specialized.Predicate; + +/** + * A predicate that returns true if as is empty; false otherwise. + * + * @param the iterable element type + */ +public final class Empty implements Predicate> { + + private static final Empty INSTANCE = new Empty(); + + private Empty() { + } + + @Override + public Boolean apply(Iterable as) { + return !as.iterator().hasNext(); + } + + @SuppressWarnings("unchecked") + public static Empty empty() { + return INSTANCE; + } + + public static Boolean empty(Iterable as) { + return Empty.empty().test(as); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Not.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Not.java new file mode 100644 index 000000000..5f8718c8b --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Not.java @@ -0,0 +1,36 @@ +package com.jnape.palatable.lambda.functions.builtin.fn1; + +import com.jnape.palatable.lambda.functions.specialized.BiPredicate; +import com.jnape.palatable.lambda.functions.specialized.Predicate; + +import java.util.function.Function; + +/** + * Negate a predicate function. + * + * @param the input argument type + */ +public final class Not implements BiPredicate, A> { + private static final Not INSTANCE = new Not(); + + private Not() { + } + + @Override + public Boolean apply(Function pred, A a) { + return !pred.apply(a); + } + + @SuppressWarnings("unchecked") + public static Not not() { + return INSTANCE; + } + + public static Predicate not(Function pred) { + return Not.not().apply(pred); + } + + public static Boolean not(Function pred, A a) { + return not(pred).apply(a); + } +} diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/EmptyTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/EmptyTest.java new file mode 100644 index 000000000..41cab7b1e --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/EmptyTest.java @@ -0,0 +1,22 @@ +package com.jnape.palatable.lambda.functions.builtin.fn1; + +import org.junit.Test; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Empty.empty; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Repeat.repeat; +import static java.util.Collections.emptySet; +import static java.util.Collections.singleton; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class EmptyTest { + + @Test + public void emptiness() { + Empty empty = empty(); + + assertTrue(empty.apply(emptySet())); + assertFalse(empty.apply(singleton(1))); + assertFalse(empty.apply(repeat(1))); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/NotTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/NotTest.java new file mode 100644 index 000000000..6269f526b --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/NotTest.java @@ -0,0 +1,18 @@ +package com.jnape.palatable.lambda.functions.builtin.fn1; + +import com.jnape.palatable.lambda.functions.specialized.Predicate; +import org.junit.Test; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Not.not; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class NotTest { + + @Test + public void negatesPredicate() { + Predicate isTrue = not(a -> !a); + assertTrue(isTrue.apply(true)); + assertFalse(isTrue.apply(false)); + } +} \ No newline at end of file From 74b0e5838092b2ce62a6779ff4a95de25d114359 Mon Sep 17 00:00:00 2001 From: jnape Date: Sat, 6 May 2017 14:41:21 -0500 Subject: [PATCH 11/24] Adding default #discardL and #discardR implementations for Applicatives --- .../jnape/palatable/lambda/adt/Either.java | 10 ++++ .../palatable/lambda/adt/choice/Choice2.java | 10 ++++ .../palatable/lambda/adt/choice/Choice3.java | 10 ++++ .../palatable/lambda/adt/choice/Choice4.java | 10 ++++ .../palatable/lambda/adt/choice/Choice5.java | 11 ++++- .../lambda/adt/hlist/SingletonHList.java | 10 ++++ .../palatable/lambda/adt/hlist/Tuple2.java | 10 ++++ .../palatable/lambda/adt/hlist/Tuple3.java | 10 ++++ .../palatable/lambda/adt/hlist/Tuple4.java | 10 ++++ .../palatable/lambda/adt/hlist/Tuple5.java | 10 ++++ .../jnape/palatable/lambda/functions/Fn1.java | 10 ++++ .../palatable/lambda/functor/Applicative.java | 49 +++++++++++++++++-- .../lambda/functor/builtin/Const.java | 10 ++++ .../lambda/functor/builtin/Identity.java | 10 ++++ .../com/jnape/palatable/lambda/lens/Lens.java | 12 +++-- .../testsupport/traits/ApplicativeLaws.java | 21 +++++++- 16 files changed, 204 insertions(+), 9 deletions(-) diff --git a/src/main/java/com/jnape/palatable/lambda/adt/Either.java b/src/main/java/com/jnape/palatable/lambda/adt/Either.java index 9f36d0bb5..02dba4d93 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/Either.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/Either.java @@ -224,6 +224,16 @@ public Either zip(Applicative, Eit return appFn.>>coerce().flatMap(this::biMapR); } + @Override + public Either discardL(Applicative> appB) { + return Applicative.super.discardL(appB).coerce(); + } + + @Override + public Either discardR(Applicative> appB) { + return Applicative.super.discardR(appB).coerce(); + } + /** * In the left case, returns an {@link Optional#empty}; otherwise, returns {@link Optional#ofNullable} around the * right value. diff --git a/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice2.java b/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice2.java index 81bf962f2..60f116387 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice2.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice2.java @@ -69,6 +69,16 @@ public Choice2 zip(Applicative, Choic .match(Choice2::a, this::biMapR); } + @Override + public Choice2 discardL(Applicative> appB) { + return Applicative.super.discardL(appB).coerce(); + } + + @Override + public Choice2 discardR(Applicative> appB) { + return Applicative.super.discardR(appB).coerce(); + } + /** * Static factory method for wrapping a value of type A in a {@link Choice2}. * diff --git a/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice3.java b/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice3.java index 21cd3d5c9..0a99ed4b5 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice3.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice3.java @@ -69,6 +69,16 @@ public Choice3 zip( .match(Choice3::a, Choice3::b, this::biMapR); } + @Override + public Choice3 discardL(Applicative> appB) { + return Applicative.super.discardL(appB).coerce(); + } + + @Override + public Choice3 discardR(Applicative> appB) { + return Applicative.super.discardR(appB).coerce(); + } + /** * Static factory method for wrapping a value of type A in a {@link Choice3}. * diff --git a/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice4.java b/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice4.java index 488216034..39cb2d518 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice4.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice4.java @@ -72,6 +72,16 @@ public Choice4 zip(Applicative, .match(Choice4::a, Choice4::b, Choice4::c, this::biMapR); } + @Override + public Choice4 discardL(Applicative> appB) { + return Applicative.super.discardL(appB).coerce(); + } + + @Override + public Choice4 discardR(Applicative> appB) { + return Applicative.super.discardR(appB).coerce(); + } + /** * Static factory method for wrapping a value of type A in a {@link Choice4}. * diff --git a/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice5.java b/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice5.java index e21abc057..aa6b4cecb 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice5.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice5.java @@ -52,7 +52,6 @@ public Choice5 biMapR(Function fn) { } @Override - @SuppressWarnings("unchecked") public Choice5 biMap(Function lFn, Function rFn) { return match(Choice5::a, Choice5::b, Choice5::c, d -> d(lFn.apply(d)), e -> e(rFn.apply(e))); @@ -69,6 +68,16 @@ public Choice5 zip(Applicative Choice5 discardL(Applicative> appB) { + return Applicative.super.discardL(appB).coerce(); + } + + @Override + public Choice5 discardR(Applicative> appB) { + return Applicative.super.discardR(appB).coerce(); + } + /** * Static factory method for wrapping a value of type A in a {@link Choice5}. * 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 b56c78d62..b6d57d7d8 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 @@ -45,4 +45,14 @@ public <_1Prime> SingletonHList<_1Prime> zip( .head() .apply(head())); } + + @Override + public <_1Prime> SingletonHList<_1Prime> discardL(Applicative<_1Prime, SingletonHList> appB) { + return Applicative.super.discardL(appB).coerce(); + } + + @Override + public <_1Prime> SingletonHList<_1> discardR(Applicative<_1Prime, SingletonHList> appB) { + return Applicative.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 128dc2b3a..b33283d74 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 @@ -116,6 +116,16 @@ public <_2Prime> Tuple2<_1, _2Prime> zip( return biMapR(appFn.>>coerce()._2()); } + @Override + public <_2Prime> Tuple2<_1, _2Prime> discardL(Applicative<_2Prime, Tuple2<_1, ?>> appB) { + return Applicative.super.discardL(appB).coerce(); + } + + @Override + public <_2Prime> Tuple2<_1, _2> discardR(Applicative<_2Prime, Tuple2<_1, ?>> appB) { + return Applicative.super.discardR(appB).coerce(); + } + /** * Static factory method for creating Tuple2s from {@link java.util.Map.Entry}s. * diff --git a/src/main/java/com/jnape/palatable/lambda/adt/hlist/Tuple3.java b/src/main/java/com/jnape/palatable/lambda/adt/hlist/Tuple3.java index 46705a2d5..2a124904c 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 @@ -112,6 +112,16 @@ public <_3Prime> Tuple3<_1, _2, _3Prime> zip( return biMapR(appFn.>>coerce()._3()); } + @Override + public <_3Prime> Tuple3<_1, _2, _3Prime> discardL(Applicative<_3Prime, Tuple3<_1, _2, ?>> appB) { + return Applicative.super.discardL(appB).coerce(); + } + + @Override + public <_3Prime> Tuple3<_1, _2, _3> discardR(Applicative<_3Prime, Tuple3<_1, _2, ?>> appB) { + return Applicative.super.discardR(appB).coerce(); + } + /** * Given a value of type A, produced an instance of this tuple with each slot set to that value. * diff --git a/src/main/java/com/jnape/palatable/lambda/adt/hlist/Tuple4.java b/src/main/java/com/jnape/palatable/lambda/adt/hlist/Tuple4.java index de5d8bd89..256e5ce5e 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 @@ -124,6 +124,16 @@ public <_4Prime> Tuple4<_1, _2, _3, _4Prime> zip( return biMapR(appFn.>>coerce()._4()); } + @Override + public <_4Prime> Tuple4<_1, _2, _3, _4Prime> discardL(Applicative<_4Prime, Tuple4<_1, _2, _3, ?>> appB) { + return Applicative.super.discardL(appB).coerce(); + } + + @Override + public <_4Prime> Tuple4<_1, _2, _3, _4> discardR(Applicative<_4Prime, Tuple4<_1, _2, _3, ?>> appB) { + return Applicative.super.discardR(appB).coerce(); + } + /** * Given a value of type A, produced an instance of this tuple with each slot set to that value. * diff --git a/src/main/java/com/jnape/palatable/lambda/adt/hlist/Tuple5.java b/src/main/java/com/jnape/palatable/lambda/adt/hlist/Tuple5.java index 766287806..37f6c2271 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 @@ -122,6 +122,16 @@ public <_5Prime> Tuple5<_1, _2, _3, _4, _5Prime> zip( return biMapR(appFn.>>coerce()._5()); } + @Override + public <_5Prime> Tuple5<_1, _2, _3, _4, _5Prime> discardL(Applicative<_5Prime, Tuple5<_1, _2, _3, _4, ?>> appB) { + return Applicative.super.discardL(appB).coerce(); + } + + @Override + public <_5Prime> Tuple5<_1, _2, _3, _4, _5> discardR(Applicative<_5Prime, Tuple5<_1, _2, _3, _4, ?>> appB) { + return Applicative.super.discardR(appB).coerce(); + } + /** * Given a value of type A, produced an instance of this tuple with each slot set to that value. * diff --git a/src/main/java/com/jnape/palatable/lambda/functions/Fn1.java b/src/main/java/com/jnape/palatable/lambda/functions/Fn1.java index 90ef0d8e2..8d24de2b4 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/Fn1.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/Fn1.java @@ -64,6 +64,16 @@ default Fn1 zip(Fn2 appFn) { return zip((Fn1>) (Object) appFn); } + @Override + default Fn1 discardL(Applicative> appB) { + return Applicative.super.discardL(appB).coerce(); + } + + @Override + default Fn1 discardR(Applicative> appB) { + return Applicative.super.discardR(appB).coerce(); + } + /** * Contravariantly map over the argument to this function, producing a function that takes the new argument type, * and produces the same result. diff --git a/src/main/java/com/jnape/palatable/lambda/functor/Applicative.java b/src/main/java/com/jnape/palatable/lambda/functor/Applicative.java index f0a01e6df..8f0f5b855 100644 --- a/src/main/java/com/jnape/palatable/lambda/functor/Applicative.java +++ b/src/main/java/com/jnape/palatable/lambda/functor/Applicative.java @@ -2,12 +2,29 @@ import java.util.function.Function; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; + /** - * Generic interface for the applicative functor type, which supports a lifting operation pure and a - * flattening application zip. + * An interface representing applicative functors - functors that can have their results combined with other functors + * of the same instance in a context-free manner. + *

+ * The same rules that apply to Functor apply to Applicative, along with the following + * additional 4 laws: + *

    + *
  • identity: v.zip(pureId).equals(v)
  • + *
  • composition: w.zip(v.zip(u.zip(pureCompose))).equals((w.zip(v)).zip(u))
  • + *
  • homomorphism: pureX.zip(pureF).equals(pureFx)
  • + *
  • interchange: pureY.zip(u).equals(u.zip(pure(f -> f.apply(y))))
  • + *
+ * As with Functor, Applicative instances that do not satisfy all of the functor laws, as well + * as the above applicative laws, are not well-behaved and often break down in surprising ways. + *

+ * For more information, read about + * Applicative Functors. * * @param The type of the parameter - * @param The unification parameter + * @param The unification parameter to more tightly type-constrain Applicatives to themselves */ public interface Applicative extends Functor { @@ -22,7 +39,7 @@ public interface Applicative extends Functor /** * Given another instance of this applicative over a mapping function, "zip" the two instances together using - * whatever application semantics the current applicative supports + * whatever application semantics the current applicative supports. * * @param appFn the other applicative instance * @param the resulting applicative parameter type @@ -35,6 +52,30 @@ default Applicative fmap(Function fn) { return zip(pure(fn)); } + /** + * Sequence both this Applicative and appB, discarding this Applicative's + * result and returning appB. This is generally useful for sequentially performing side-effects. + * + * @param appB the other Applicative + * @param the type of the returned Applicative's parameter + * @return appB + */ + default Applicative discardL(Applicative appB) { + return appB.zip(zip(pure(constantly(id())))); + } + + /** + * Sequence both this Applicative and appB, discarding appB's result and + * returning this Applicative. This is generally useful for sequentially performing side-effects. + * + * @param appB the other Applicative + * @param the type of appB's parameter + * @return this Applicative + */ + default Applicative discardR(Applicative appB) { + return appB.zip(zip(pure(constantly()))); + } + /** * Convenience method for coercing this applicative instance into another concrete type. Unsafe. * diff --git a/src/main/java/com/jnape/palatable/lambda/functor/builtin/Const.java b/src/main/java/com/jnape/palatable/lambda/functor/builtin/Const.java index 5f7867999..d20936274 100644 --- a/src/main/java/com/jnape/palatable/lambda/functor/builtin/Const.java +++ b/src/main/java/com/jnape/palatable/lambda/functor/builtin/Const.java @@ -68,6 +68,16 @@ public Const zip(Applicative, Const) this; } + @Override + public Const discardL(Applicative> appB) { + return Applicative.super.discardL(appB).coerce(); + } + + @Override + public Const discardR(Applicative> appB) { + return Applicative.super.discardR(appB).coerce(); + } + /** * Covariantly map over the left parameter type (the 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 c6e22aba1..28a2b5627 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 @@ -50,6 +50,16 @@ public Identity zip(Applicative, Identit return new Identity<>(appFn.>>coerce().runIdentity().apply(a)); } + @Override + public Identity discardL(Applicative appB) { + return Applicative.super.discardL(appB).coerce(); + } + + @Override + public Identity discardR(Applicative appB) { + return Applicative.super.discardR(appB).coerce(); + } + @Override public boolean equals(Object other) { return other instanceof Identity && Objects.equals(a, ((Identity) other).a); diff --git a/src/main/java/com/jnape/palatable/lambda/lens/Lens.java b/src/main/java/com/jnape/palatable/lambda/lens/Lens.java index 4fb3cdd0c..3d77fb399 100644 --- a/src/main/java/com/jnape/palatable/lambda/lens/Lens.java +++ b/src/main/java/com/jnape/palatable/lambda/lens/Lens.java @@ -163,13 +163,19 @@ default Lens pure(U u) { return lens(view(this), (s, b) -> u); } + @Override + default Lens discardL(Applicative> appB) { + return Applicative.super.discardL(appB).coerce(); + } + @Override + default Lens discardR(Applicative> appB) { + return Applicative.super.discardR(appB).coerce(); + } @Override default Lens zip(Applicative, Lens> appFn) { - return lens(view(this), - (s, b) -> set(appFn., A, B>>coerce(), b, s) - .apply(set(this, b, s))); + return lens(view(this), (s, b) -> set(appFn., A, B>>coerce(), b, s).apply(set(this, b, s))); } /** diff --git a/src/test/java/testsupport/traits/ApplicativeLaws.java b/src/test/java/testsupport/traits/ApplicativeLaws.java index 397b791df..9ac993b0c 100644 --- a/src/test/java/testsupport/traits/ApplicativeLaws.java +++ b/src/test/java/testsupport/traits/ApplicativeLaws.java @@ -9,6 +9,7 @@ import java.util.Random; import java.util.function.Function; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; import static java.util.Arrays.asList; import static java.util.function.Function.identity; @@ -18,7 +19,7 @@ public class ApplicativeLaws implements Trait applicative) { Iterable> testResults = Map., Optional>, Optional>map( f -> f.apply(applicative), - asList(this::testIdentity, this::testComposition, this::testHomomorphism, this::testInterchange) + asList(this::testIdentity, this::testComposition, this::testHomomorphism, this::testInterchange, this::testDiscardL, this::testDiscardR) ); Present.present((x, y) -> x + "\n\t - " + y) .reduceLeft(testResults) @@ -72,4 +73,22 @@ private Optional testInterchange(Applicative applicative) { ? Optional.empty() : Optional.of("interchange (pureY.zip(u).equals(u.zip(applicative.pure(f -> f.apply(y)))))"); } + + private Optional testDiscardL(Applicative applicative) { + Applicative u = applicative.pure("u"); + Applicative v = applicative.pure("v"); + + return u.discardL(v).equals(v.zip(u.zip(applicative.pure(constantly(identity()))))) + ? Optional.empty() + : Optional.of("discardL u.discardL(v).equals(v.zip(u.zip(applicative.pure(constantly(identity())))))"); + } + + private Optional testDiscardR(Applicative applicative) { + Applicative u = applicative.pure("u"); + Applicative v = applicative.pure("v"); + + return u.discardR(v).equals(v.zip(u.zip(applicative.pure(constantly())))) + ? Optional.empty() + : Optional.of("discardR u.discardR(v).equals(v.zip(u.zip(applicative.pure(constantly()))))"); + } } From f53e5ac9f345a90e99b02e3be9e1321d6c04f73e Mon Sep 17 00:00:00 2001 From: jnape Date: Sat, 6 May 2017 14:49:34 -0500 Subject: [PATCH 12/24] Renaming Concat monoid and semigroup to more collection-appropriate name AddAll --- CHANGELOG.md | 1 + .../builtin/{Concat.java => AddAll.java} | 28 ++++++------- .../lambda/semigroup/builtin/AddAll.java | 42 +++++++++++++++++++ .../lambda/semigroup/builtin/Concat.java | 42 ------------------- .../{ConcatTest.java => AddAllTest.java} | 10 ++--- .../{ConcatTest.java => AddAllTest.java} | 9 ++-- 6 files changed, 65 insertions(+), 67 deletions(-) rename src/main/java/com/jnape/palatable/lambda/monoid/builtin/{Concat.java => AddAll.java} (52%) create mode 100644 src/main/java/com/jnape/palatable/lambda/semigroup/builtin/AddAll.java delete mode 100644 src/main/java/com/jnape/palatable/lambda/semigroup/builtin/Concat.java rename src/test/java/com/jnape/palatable/lambda/monoid/builtin/{ConcatTest.java => AddAllTest.java} (60%) rename src/test/java/com/jnape/palatable/lambda/semigroup/builtin/{ConcatTest.java => AddAllTest.java} (55%) diff --git a/CHANGELOG.md b/CHANGELOG.md index b265cba79..f574a0084 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/). - `Const` supports value equality - `partition` now only requires iterables of `CoProudct2` - `CoProductN`s receive a unification parameter, which trickles down to `Either` and `Choice`s +- Renaming `Concat` semigroup and monoid to more appropriate `AddAll` ## [1.5.6] - 2017-02-11 ### Added diff --git a/src/main/java/com/jnape/palatable/lambda/monoid/builtin/Concat.java b/src/main/java/com/jnape/palatable/lambda/monoid/builtin/AddAll.java similarity index 52% rename from src/main/java/com/jnape/palatable/lambda/monoid/builtin/Concat.java rename to src/main/java/com/jnape/palatable/lambda/monoid/builtin/AddAll.java index 2ea4501fe..fa0d0221d 100644 --- a/src/main/java/com/jnape/palatable/lambda/monoid/builtin/Concat.java +++ b/src/main/java/com/jnape/palatable/lambda/monoid/builtin/AddAll.java @@ -11,40 +11,40 @@ import static com.jnape.palatable.lambda.monoid.Monoid.monoid; /** - * The {@link Monoid} instance formed under concatenation for an arbitrary {@link Collection}. The collection subtype - * (C) must support {@link Collection#addAll(Collection)}. + * The {@link Monoid} instance formed under mutative concatenation for an arbitrary {@link Collection}. The collection + * subtype (C) must support {@link Collection#addAll(Collection)}. *

- * For the {@link Semigroup}, see {@link com.jnape.palatable.lambda.semigroup.builtin.Concat}. + * For the {@link Semigroup}, see {@link com.jnape.palatable.lambda.semigroup.builtin.AddAll}. * * @see Monoid */ -public final class Concat> implements MonoidFactory, C> { +public final class AddAll> implements MonoidFactory, C> { - private static final Concat INSTANCE = new Concat(); + private static final AddAll INSTANCE = new AddAll(); - private Concat() { + private AddAll() { } @Override public Monoid apply(Supplier cSupplier) { - Semigroup semigroup = com.jnape.palatable.lambda.semigroup.builtin.Concat.concat(); + Semigroup semigroup = com.jnape.palatable.lambda.semigroup.builtin.AddAll.addAll(); return monoid(semigroup, cSupplier); } @SuppressWarnings("unchecked") - public static > Concat concat() { + public static > AddAll addAll() { return INSTANCE; } - public static > Monoid concat(Supplier collectionSupplier) { - return Concat.concat().apply(collectionSupplier); + public static > Monoid addAll(Supplier collectionSupplier) { + return AddAll.addAll().apply(collectionSupplier); } - public static > Fn1 concat(Supplier collectionSupplier, C xs) { - return concat(collectionSupplier).apply(xs); + public static > Fn1 addAll(Supplier collectionSupplier, C xs) { + return addAll(collectionSupplier).apply(xs); } - public static > C concat(Supplier collectionSupplier, C xs, C ys) { - return concat(collectionSupplier, xs).apply(ys); + public static > C addAll(Supplier collectionSupplier, C xs, C ys) { + return addAll(collectionSupplier, xs).apply(ys); } } diff --git a/src/main/java/com/jnape/palatable/lambda/semigroup/builtin/AddAll.java b/src/main/java/com/jnape/palatable/lambda/semigroup/builtin/AddAll.java new file mode 100644 index 000000000..557f9454b --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/semigroup/builtin/AddAll.java @@ -0,0 +1,42 @@ +package com.jnape.palatable.lambda.semigroup.builtin; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.monoid.Monoid; +import com.jnape.palatable.lambda.semigroup.Semigroup; + +import java.util.Collection; + +/** + * The {@link Semigroup} instance formed under mutative concatenation for an arbitrary {@link Collection}. The + * collection subtype (C) must support {@link Collection#addAll(Collection)}. + *

+ * For the {@link Monoid}, see {@link com.jnape.palatable.lambda.monoid.builtin.AddAll}. + * + * @see Semigroup + */ +public final class AddAll> implements Semigroup { + + private static final AddAll INSTANCE = new AddAll(); + + private AddAll() { + } + + @Override + public C apply(C xs, C ys) { + xs.addAll(ys); + return xs; + } + + @SuppressWarnings("unchecked") + public static > AddAll addAll() { + return INSTANCE; + } + + public static > Fn1 addAll(C xs) { + return AddAll.addAll().apply(xs); + } + + public static > C addAll(C xs, C ys) { + return addAll(xs).apply(ys); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/semigroup/builtin/Concat.java b/src/main/java/com/jnape/palatable/lambda/semigroup/builtin/Concat.java deleted file mode 100644 index 9d119a97f..000000000 --- a/src/main/java/com/jnape/palatable/lambda/semigroup/builtin/Concat.java +++ /dev/null @@ -1,42 +0,0 @@ -package com.jnape.palatable.lambda.semigroup.builtin; - -import com.jnape.palatable.lambda.functions.Fn1; -import com.jnape.palatable.lambda.monoid.Monoid; -import com.jnape.palatable.lambda.semigroup.Semigroup; - -import java.util.Collection; - -/** - * The {@link Semigroup} instance formed under concatenation for an arbitrary {@link Collection}. The collection subtype - * (C) must support {@link Collection#addAll(Collection)}. - *

- * For the {@link Monoid}, see {@link com.jnape.palatable.lambda.monoid.builtin.Concat}. - * - * @see Semigroup - */ -public final class Concat> implements Semigroup { - - private static final Concat INSTANCE = new Concat(); - - private Concat() { - } - - @Override - public C apply(C xs, C ys) { - xs.addAll(ys); - return xs; - } - - @SuppressWarnings("unchecked") - public static > Concat concat() { - return INSTANCE; - } - - public static > Fn1 concat(C xs) { - return Concat.concat().apply(xs); - } - - public static > C concat(C xs, C ys) { - return concat(xs).apply(ys); - } -} diff --git a/src/test/java/com/jnape/palatable/lambda/monoid/builtin/ConcatTest.java b/src/test/java/com/jnape/palatable/lambda/monoid/builtin/AddAllTest.java similarity index 60% rename from src/test/java/com/jnape/palatable/lambda/monoid/builtin/ConcatTest.java rename to src/test/java/com/jnape/palatable/lambda/monoid/builtin/AddAllTest.java index f48e628a2..8a997382b 100644 --- a/src/test/java/com/jnape/palatable/lambda/monoid/builtin/ConcatTest.java +++ b/src/test/java/com/jnape/palatable/lambda/monoid/builtin/AddAllTest.java @@ -6,20 +6,20 @@ import java.util.HashSet; import java.util.Set; -import static com.jnape.palatable.lambda.monoid.builtin.Concat.concat; +import static com.jnape.palatable.lambda.monoid.builtin.AddAll.addAll; import static java.util.Collections.singleton; import static org.junit.Assert.assertEquals; -public class ConcatTest { +public class AddAllTest { @Test public void monoid() { - Monoid> concat = concat(HashSet::new); + Monoid> addAll = addAll(HashSet::new); - assertEquals(new HashSet<>(), concat.identity()); + assertEquals(new HashSet<>(), addAll.identity()); assertEquals(new HashSet() {{ add(1); add(2); - }}, concat.apply(new HashSet<>(singleton(1)), new HashSet<>(singleton(2)))); + }}, addAll.apply(new HashSet<>(singleton(1)), new HashSet<>(singleton(2)))); } } \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/semigroup/builtin/ConcatTest.java b/src/test/java/com/jnape/palatable/lambda/semigroup/builtin/AddAllTest.java similarity index 55% rename from src/test/java/com/jnape/palatable/lambda/semigroup/builtin/ConcatTest.java rename to src/test/java/com/jnape/palatable/lambda/semigroup/builtin/AddAllTest.java index e712c8c0d..f1840b5de 100644 --- a/src/test/java/com/jnape/palatable/lambda/semigroup/builtin/ConcatTest.java +++ b/src/test/java/com/jnape/palatable/lambda/semigroup/builtin/AddAllTest.java @@ -3,21 +3,18 @@ import org.junit.Test; import java.util.HashSet; -import java.util.Set; -import static com.jnape.palatable.lambda.semigroup.builtin.Concat.concat; +import static com.jnape.palatable.lambda.semigroup.builtin.AddAll.addAll; import static java.util.Collections.singleton; import static org.junit.Assert.assertEquals; -public class ConcatTest { +public class AddAllTest { @Test public void semigroup() { - Concat> concat = concat(); - assertEquals(new HashSet() {{ add(1); add(2); - }}, concat.apply(new HashSet<>(singleton(1)), new HashSet<>(singleton(2)))); + }}, addAll(new HashSet<>(singleton(1)), new HashSet<>(singleton(2)))); } } \ No newline at end of file From 02b62dbc5d81b5823f4951260af7fb5445e7fe96 Mon Sep 17 00:00:00 2001 From: jnape Date: Sun, 7 May 2017 13:15:26 -0500 Subject: [PATCH 13/24] Adding Concat, monoid formed under concatenation for Iterable --- CHANGELOG.md | 2 +- .../iterators/ConcatenatingIterator.java | 80 +++++++++++++ .../lambda/monoid/builtin/Concat.java | 43 +++++++ .../iterators/ConcatenatingIteratorTest.java | 109 ++++++++++++++++++ .../lambda/monoid/builtin/ConcatTest.java | 19 +++ 5 files changed, 252 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/jnape/palatable/lambda/iterators/ConcatenatingIterator.java create mode 100644 src/main/java/com/jnape/palatable/lambda/monoid/builtin/Concat.java create mode 100644 src/test/java/com/jnape/palatable/lambda/iterators/ConcatenatingIteratorTest.java create mode 100644 src/test/java/com/jnape/palatable/lambda/monoid/builtin/ConcatTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md index f574a0084..fc336a428 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,7 +17,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/). - `Const` supports value equality - `partition` now only requires iterables of `CoProudct2` - `CoProductN`s receive a unification parameter, which trickles down to `Either` and `Choice`s -- Renaming `Concat` semigroup and monoid to more appropriate `AddAll` +- `Concat` now represents a monoid for `Iterable`; previous `Concat` semigroup and monoid renamed to more appropriate `AddAll` ## [1.5.6] - 2017-02-11 ### Added diff --git a/src/main/java/com/jnape/palatable/lambda/iterators/ConcatenatingIterator.java b/src/main/java/com/jnape/palatable/lambda/iterators/ConcatenatingIterator.java new file mode 100644 index 000000000..0191fd58a --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/iterators/ConcatenatingIterator.java @@ -0,0 +1,80 @@ +package com.jnape.palatable.lambda.iterators; + +import java.util.Iterator; +import java.util.LinkedList; +import java.util.NoSuchElementException; +import java.util.Queue; +import java.util.concurrent.atomic.AtomicReference; + +public class ConcatenatingIterator implements Iterator { + + private final Iterable xs; + private final Iterable ys; + private final AtomicReference> xsRef; + private final AtomicReference> ysRef; + private final Queue> afterXs; + private final Queue> afterYs; + + public ConcatenatingIterator(Iterable xs, Iterable ys) { + this.xs = xs; + this.ys = ys; + xsRef = new AtomicReference<>(); + ysRef = new AtomicReference<>(); + afterXs = new LinkedList<>(); + afterYs = new LinkedList<>(); + } + + @Override + public boolean hasNext() { + queueNext(xsRef, xs, afterXs); + + if (xsIterator().hasNext()) + return true; + + queueNext(ysRef, ys, afterYs); + + return ysIterator().hasNext(); + } + + @Override + public A next() { + if (!hasNext()) + throw new NoSuchElementException(); + + return xsIterator().hasNext() ? xsIterator().next() : ysIterator().next(); + } + + private Iterator xsIterator() { + return xsRef.get(); + } + + private Iterator ysIterator() { + return ysRef.get(); + } + + private void queueNext(AtomicReference> iteratorRef, Iterable iterable, + Queue> queued) { + iteratorRef.updateAndGet(iterator -> { + if (iterator == null) + iterator = iterable.iterator(); + + while (iterator instanceof ConcatenatingIterator && iterator.hasNext()) { + ConcatenatingIterator concatenatingXsIterator = (ConcatenatingIterator) iterator; + + if (concatenatingXsIterator.xsIterator().hasNext()) { + queued.add(concatenatingXsIterator.ys); + iterator = concatenatingXsIterator.xsIterator(); + } else { + iterator = concatenatingXsIterator.ysIterator(); + } + } + + while (!iterator.hasNext() && !queued.isEmpty()) { + iterator = queued.poll().iterator(); + } + + return iterator; + }); + } + +} diff --git a/src/main/java/com/jnape/palatable/lambda/monoid/builtin/Concat.java b/src/main/java/com/jnape/palatable/lambda/monoid/builtin/Concat.java new file mode 100644 index 000000000..f48e4cc5a --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/monoid/builtin/Concat.java @@ -0,0 +1,43 @@ +package com.jnape.palatable.lambda.monoid.builtin; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.iterators.ConcatenatingIterator; +import com.jnape.palatable.lambda.monoid.Monoid; + +import java.util.Collections; + +/** + * The {@link Monoid} instance formed under concatenation for an arbitrary {@link Iterable}. + * + * @see Monoid + */ +public final class Concat implements Monoid> { + + private static final Concat INSTANCE = new Concat(); + + private Concat() { + } + + @Override + public Iterable identity() { + return Collections::emptyIterator; + } + + @Override + public Iterable apply(Iterable xs, Iterable ys) { + return () -> new ConcatenatingIterator<>(xs, ys); + } + + @SuppressWarnings("unchecked") + public static Concat concat() { + return INSTANCE; + } + + public static Fn1, Iterable> concat(Iterable xs) { + return Concat.concat().apply(xs); + } + + public static Iterable concat(Iterable xs, Iterable ys) { + return concat(xs).apply(ys); + } +} diff --git a/src/test/java/com/jnape/palatable/lambda/iterators/ConcatenatingIteratorTest.java b/src/test/java/com/jnape/palatable/lambda/iterators/ConcatenatingIteratorTest.java new file mode 100644 index 000000000..f60bbcb3c --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/iterators/ConcatenatingIteratorTest.java @@ -0,0 +1,109 @@ +package com.jnape.palatable.lambda.iterators; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; + +import java.util.Collections; +import java.util.Iterator; + +import static com.jnape.palatable.lambda.functions.builtin.fn2.Drop.drop; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Iterate.iterate; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Map.map; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Take.take; +import static com.jnape.palatable.lambda.functions.builtin.fn3.FoldRight.foldRight; +import static java.util.Arrays.asList; +import static org.hamcrest.core.Is.is; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.when; +import static testsupport.Mocking.mockIteratorToHaveValues; + +@RunWith(MockitoJUnitRunner.class) +public class ConcatenatingIteratorTest { + + @Mock private Iterator xs; + @Mock private Iterator ys; + private ConcatenatingIterator concatenatingIterator; + + @Before + public void setUp() throws Exception { + concatenatingIterator = new ConcatenatingIterator<>(() -> xs, () -> ys); + } + + @Test + public void hasNextIfMoreXsAndMoreYs() { + when(xs.hasNext()).thenReturn(true); + when(ys.hasNext()).thenReturn(true); + + assertTrue(concatenatingIterator.hasNext()); + } + + @Test + public void hasNextIfJustMoreXs() { + when(xs.hasNext()).thenReturn(true); + when(ys.hasNext()).thenReturn(false); + + assertTrue(concatenatingIterator.hasNext()); + } + + @Test + public void hasNextIfJustMoreYs() { + when(xs.hasNext()).thenReturn(false); + when(ys.hasNext()).thenReturn(true); + + assertTrue(concatenatingIterator.hasNext()); + } + + @Test + public void doesNotHaveNextIfNeitherMoreXsNorMoreYs() { + when(xs.hasNext()).thenReturn(false); + when(ys.hasNext()).thenReturn(false); + + assertFalse(concatenatingIterator.hasNext()); + } + + @Test + public void nextPullsFromXsFirstThenFromYs() { + mockIteratorToHaveValues(xs, 1, 2); + mockIteratorToHaveValues(ys, 3); + + assertThat(concatenatingIterator.hasNext(), is(true)); + assertThat(concatenatingIterator.next(), is(1)); + assertThat(concatenatingIterator.hasNext(), is(true)); + assertThat(concatenatingIterator.next(), is(2)); + assertThat(concatenatingIterator.hasNext(), is(true)); + assertThat(concatenatingIterator.next(), is(3)); + assertThat(concatenatingIterator.hasNext(), is(false)); + } + + @Test + public void concatenatingIteratorInXsPosition() { + ConcatenatingIterator iterator = + new ConcatenatingIterator<>(() -> new ConcatenatingIterator<>(asList(1, 2), asList(3, 4)), + asList(5, 6)); + + assertThat(iterator.next(), is(1)); + assertThat(iterator.next(), is(2)); + assertThat(iterator.next(), is(3)); + assertThat(iterator.next(), is(4)); + assertThat(iterator.next(), is(5)); + assertThat(iterator.next(), is(6)); + } + + @Test + public void stackSafetyInYsPosition() { + Integer stackBlowingNumber = 100000; + Iterable> xss = map(Collections::singleton, take(stackBlowingNumber, iterate(x -> x + 1, 1))); + Iterable ints = foldRight((xs, ys) -> () -> new ConcatenatingIterator<>(xs, ys), + (Iterable) Collections.emptySet(), + xss); + + assertEquals(stackBlowingNumber, + take(1, drop(stackBlowingNumber - 1, ints)).iterator().next()); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/monoid/builtin/ConcatTest.java b/src/test/java/com/jnape/palatable/lambda/monoid/builtin/ConcatTest.java new file mode 100644 index 000000000..a0f89e394 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/monoid/builtin/ConcatTest.java @@ -0,0 +1,19 @@ +package com.jnape.palatable.lambda.monoid.builtin; + +import org.junit.Test; + +import static com.jnape.palatable.lambda.monoid.builtin.Concat.concat; +import static java.util.Arrays.asList; +import static java.util.Collections.singleton; +import static org.junit.Assert.assertThat; +import static testsupport.matchers.IterableMatcher.isEmpty; +import static testsupport.matchers.IterableMatcher.iterates; + +public class ConcatTest { + + @Test + public void monoid() { + assertThat(concat().identity(), isEmpty()); + assertThat(concat(asList(1, 2), singleton(3)), iterates(1, 2, 3)); + } +} \ No newline at end of file From ff5d7b710dd939be04a9fbedcee980f5c045c551 Mon Sep 17 00:00:00 2001 From: jnape Date: Sun, 7 May 2017 14:52:05 -0500 Subject: [PATCH 14/24] Fixing broken link in changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fc336a428..a30beee83 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -115,7 +115,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/). - `Functor`, `BiFunctor`, `ProFunctor` [Unreleased]: https://github.com/palatable/lambda/compare/lambda-1.5.6...HEAD -[1.5.6]: https://github.com/palatable/lambda/compare/lambda-1.5.5...1.5.6 +[1.5.6]: https://github.com/palatable/lambda/compare/lambda-1.5.5...lambda-1.5.6 [1.5.5]: https://github.com/palatable/lambda/compare/lambda-1.5.4...lambda-1.5.5 [1.5.4]: https://github.com/palatable/lambda/compare/lambda-1.5.3...lambda-1.5.4 [1.5.3]: https://github.com/palatable/lambda/compare/lambda-1.5.2...lambda-1.5.3 From 904062189989827c1c186cc38df9077b86a8619a Mon Sep 17 00:00:00 2001 From: jnape Date: Sun, 14 May 2017 15:06:45 -0500 Subject: [PATCH 15/24] Adding GroupBy for folding an Iterable into a Map --- CHANGELOG.md | 1 + .../lambda/functions/builtin/fn2/GroupBy.java | 50 +++++++++++++++++++ .../functions/builtin/fn2/GroupByTest.java | 30 +++++++++++ 3 files changed, 81 insertions(+) create mode 100644 src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/GroupBy.java create mode 100644 src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/GroupByTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md index a30beee83..d51e3d196 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/). - `CoProductN#embed` - `not`, used for negating predicate functions - `empty`, used to test if an Iterable is empty +- `groupBy`, for folding an Iterable into a Map given a key function ### Changed - `Functor`, `Bifunctor`, and `Profunctor` (as well as all instances) get a unification parameter diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/GroupBy.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/GroupBy.java new file mode 100644 index 000000000..5759c0e3a --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/GroupBy.java @@ -0,0 +1,50 @@ +package com.jnape.palatable.lambda.functions.builtin.fn2; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.Fn2; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Function; + +import static com.jnape.palatable.lambda.functions.builtin.fn3.FoldLeft.foldLeft; + +/** + * Given an Iterable<V> vs and a key function V -> K f, fold + * vs into a Map<K, List<V>> by applying f to each element of + * vs, retaining values that map to the same key in a list, in the order they were iterated in. + * + * @param the Map key type + * @param the Map value type + * @see InGroupsOf + */ +public class GroupBy implements Fn2, Iterable, Map>> { + + private static final GroupBy INSTANCE = new GroupBy(); + + private GroupBy() { + } + + @Override + public Map> apply(Function keyFn, Iterable vs) { + return foldLeft((m, v) -> { + m.computeIfAbsent(keyFn.apply(v), __ -> new ArrayList<>()).add(v); + return m; + }, new HashMap>(), vs); + } + + @SuppressWarnings("unchecked") + public static GroupBy groupBy() { + return INSTANCE; + } + + public static Fn1, Map>> groupBy(Function keyFn) { + return GroupBy.groupBy().apply(keyFn); + } + + public static Map> groupBy(Function keyFn, Iterable vs) { + return GroupBy.groupBy(keyFn).apply(vs); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/GroupByTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/GroupByTest.java new file mode 100644 index 000000000..02f8e8bf2 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/GroupByTest.java @@ -0,0 +1,30 @@ +package com.jnape.palatable.lambda.functions.builtin.fn2; + +import org.junit.Test; + +import java.util.HashMap; +import java.util.List; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; +import static com.jnape.palatable.lambda.functions.builtin.fn2.GroupBy.groupBy; +import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; +import static java.util.Collections.emptyMap; +import static java.util.Collections.singletonList; +import static org.junit.Assert.assertEquals; + +public class GroupByTest { + + @Test + public void grouping() { + assertEquals(new HashMap>() {{ + put(3, asList("one", "two")); + put(5, singletonList("three")); + }}, groupBy(String::length, asList("one", "two", "three"))); + } + + @Test + public void emptyIterableProducesEmptyMap() { + assertEquals(emptyMap(), groupBy(id(), emptyList())); + } +} \ No newline at end of file From 5be2bd87f6d36738d04c6e71f67c584cd2dada35 Mon Sep 17 00:00:00 2001 From: jnape Date: Sun, 14 May 2017 15:47:27 -0500 Subject: [PATCH 16/24] adding missing javadocs --- .../lambda/functions/builtin/fn2/Intersperse.java | 7 +++++++ .../lambda/functions/builtin/fn2/PrependAll.java | 7 +++++++ .../palatable/lambda/functions/builtin/fn2/ToMap.java | 9 ++++++--- 3 files changed, 20 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Intersperse.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Intersperse.java index d7f0e3b63..68d7bc54d 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Intersperse.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Intersperse.java @@ -6,6 +6,13 @@ import static com.jnape.palatable.lambda.functions.builtin.fn1.Tail.tail; import static com.jnape.palatable.lambda.functions.builtin.fn2.PrependAll.prependAll; +/** + * Lazily inject the provided separator value between each value in the supplied Iterable. An empty + * Iterable is left untouched. + * + * @param the Iterable parameter type + * @see PrependAll + */ public final class Intersperse implements Fn2, Iterable> { private static final Intersperse INSTANCE = new Intersperse(); diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/PrependAll.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/PrependAll.java index d29ae7c6c..dd2dbcc81 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/PrependAll.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/PrependAll.java @@ -8,6 +8,13 @@ import static com.jnape.palatable.lambda.functions.builtin.fn2.Cons.cons; import static java.util.Collections.emptyList; +/** + * Lazily prepend each value with of the Iterable with the supplied separator value. An empty + * Iterable is left untouched. + * + * @param the Iterable parameter type + * @see Intersperse + */ public final class PrependAll implements Fn2, Iterable> { private static final PrependAll INSTANCE = new PrependAll(); diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/ToMap.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/ToMap.java index 7db0b82ab..fa69c61c5 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/ToMap.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/ToMap.java @@ -6,6 +6,8 @@ import java.util.Map; import java.util.function.Supplier; +import static com.jnape.palatable.lambda.functions.builtin.fn3.FoldLeft.foldLeft; + /** * Given a {@link Supplier} of some {@link Map} M, create an instance of M and put * all of the entries in the provided Iterable into the instance. Note that instances of M @@ -24,9 +26,10 @@ private ToMap() { @Override public M apply(Supplier mSupplier, Iterable> entries) { - M m = mSupplier.get(); - entries.forEach(kv -> m.put(kv.getKey(), kv.getValue())); - return m; + return foldLeft((m, kv) -> { + m.put(kv.getKey(), kv.getValue()); + return m; + }, mSupplier.get(), entries); } @SuppressWarnings("unchecked") From b2d929dde940a8d87c50085270e3f29bf097975d Mon Sep 17 00:00:00 2001 From: jnape Date: Sat, 13 May 2017 17:33:15 -0500 Subject: [PATCH 17/24] Traversable arrives --- .../jnape/palatable/lambda/adt/Either.java | 11 +++- .../palatable/lambda/adt/choice/Choice2.java | 11 +++- .../palatable/lambda/adt/choice/Choice3.java | 19 +++++- .../palatable/lambda/adt/choice/Choice4.java | 17 ++++- .../palatable/lambda/adt/choice/Choice5.java | 18 ++++- .../lambda/adt/hlist/SingletonHList.java | 10 ++- .../palatable/lambda/adt/hlist/Tuple2.java | 12 +++- .../palatable/lambda/adt/hlist/Tuple3.java | 12 +++- .../palatable/lambda/adt/hlist/Tuple4.java | 12 +++- .../palatable/lambda/adt/hlist/Tuple5.java | 12 +++- .../functions/builtin/fn2/Sequence.java | 59 +++++++++++++++++ .../lambda/functor/builtin/Compose.java | 52 +++++++++++++++ .../lambda/functor/builtin/Const.java | 10 ++- .../lambda/functor/builtin/Identity.java | 10 ++- .../com/jnape/palatable/lambda/lens/Lens.java | 10 +-- .../lambda/lens/lenses/HListLens.java | 3 + .../palatable/lambda/lens/lenses/MapLens.java | 2 +- .../lambda/traversable/Traversable.java | 51 ++++++++++++++ .../palatable/lambda/adt/EitherTest.java | 5 +- .../lambda/adt/choice/Choice2Test.java | 3 +- .../lambda/adt/choice/Choice3Test.java | 3 +- .../lambda/adt/choice/Choice4Test.java | 3 +- .../lambda/adt/choice/Choice5Test.java | 3 +- .../lambda/adt/hlist/SingletonHListTest.java | 3 +- .../lambda/adt/hlist/Tuple2Test.java | 3 +- .../lambda/adt/hlist/Tuple3Test.java | 3 +- .../lambda/adt/hlist/Tuple4Test.java | 3 +- .../lambda/adt/hlist/Tuple5Test.java | 3 +- .../functions/builtin/fn2/SequenceTest.java | 39 +++++++++++ .../lambda/functor/builtin/ComposeTest.java | 16 +++++ .../lambda/functor/builtin/ConstTest.java | 3 +- .../lambda/functor/builtin/IdentityTest.java | 3 +- .../testsupport/traits/TraversableLaws.java | 66 +++++++++++++++++++ 33 files changed, 457 insertions(+), 33 deletions(-) create mode 100644 src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Sequence.java create mode 100644 src/main/java/com/jnape/palatable/lambda/functor/builtin/Compose.java create mode 100644 src/main/java/com/jnape/palatable/lambda/traversable/Traversable.java create mode 100644 src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/SequenceTest.java create mode 100644 src/test/java/com/jnape/palatable/lambda/functor/builtin/ComposeTest.java create mode 100644 src/test/java/testsupport/traits/TraversableLaws.java diff --git a/src/main/java/com/jnape/palatable/lambda/adt/Either.java b/src/main/java/com/jnape/palatable/lambda/adt/Either.java index 02dba4d93..c666b58f0 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/Either.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/Either.java @@ -5,6 +5,7 @@ import com.jnape.palatable.lambda.functions.specialized.checked.CheckedSupplier; import com.jnape.palatable.lambda.functor.Applicative; import com.jnape.palatable.lambda.functor.Bifunctor; +import com.jnape.palatable.lambda.traversable.Traversable; import java.util.Objects; import java.util.Optional; @@ -25,7 +26,7 @@ * @param The left parameter type * @param The right parameter type */ -public abstract class Either implements CoProduct2>, Applicative>, Bifunctor { +public abstract class Either implements CoProduct2>, Applicative>, Traversable>, Bifunctor { private Either() { } @@ -234,6 +235,14 @@ public Either discardR(Applicative> appB) { return Applicative.super.discardR(appB).coerce(); } + @Override + public Applicative, App> traverse( + Function> fn, + Function>, ? extends Applicative>, App>> pure) { + return match(l -> pure.apply(left(l)).fmap(x -> (Either) x), + r -> fn.apply(r).fmap(Either::right)); + } + /** * In the left case, returns an {@link Optional#empty}; otherwise, returns {@link Optional#ofNullable} around the * right value. diff --git a/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice2.java b/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice2.java index 60f116387..8c7838886 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice2.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice2.java @@ -5,6 +5,7 @@ import com.jnape.palatable.lambda.functor.Applicative; import com.jnape.palatable.lambda.functor.Bifunctor; import com.jnape.palatable.lambda.functor.Functor; +import com.jnape.palatable.lambda.traversable.Traversable; import java.util.Objects; import java.util.function.Function; @@ -19,7 +20,7 @@ * @see Either * @see Choice3 */ -public abstract class Choice2 implements CoProduct2>, Applicative>, Bifunctor { +public abstract class Choice2 implements CoProduct2>, Applicative>, Bifunctor, Traversable> { private Choice2() { } @@ -79,6 +80,14 @@ public Choice2 discardR(Applicative> appB) { return Applicative.super.discardR(appB).coerce(); } + @Override + public Applicative, App> traverse( + Function> fn, + Function>, ? extends Applicative>, App>> pure) { + return match(a -> pure.apply(a(a)).fmap(x -> (Choice2) x), + b -> fn.apply(b).fmap(Choice2::b)); + } + /** * Static factory method for wrapping a value of type A in a {@link Choice2}. * diff --git a/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice3.java b/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice3.java index 0a99ed4b5..d80ce9268 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice3.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice3.java @@ -5,6 +5,7 @@ import com.jnape.palatable.lambda.functor.Applicative; import com.jnape.palatable.lambda.functor.Bifunctor; import com.jnape.palatable.lambda.functor.Functor; +import com.jnape.palatable.lambda.traversable.Traversable; import java.util.Objects; import java.util.function.Function; @@ -18,7 +19,11 @@ * @see Choice2 * @see Choice4 */ -public abstract class Choice3 implements CoProduct3>, Applicative>, Bifunctor> { +public abstract class Choice3 implements + CoProduct3>, + Applicative>, + Bifunctor>, + Traversable> { private Choice3() { } @@ -63,8 +68,7 @@ public Choice3 pure(D d) { } @Override - public Choice3 zip( - Applicative, Choice3> appFn) { + public Choice3 zip(Applicative, Choice3> appFn) { return appFn.>>coerce() .match(Choice3::a, Choice3::b, this::biMapR); } @@ -79,6 +83,15 @@ public Choice3 discardR(Applicative> appB) { return Applicative.super.discardR(appB).coerce(); } + @Override + public Applicative, App> traverse( + Function> fn, + Function>, ? extends Applicative>, App>> pure) { + return match(a -> pure.apply(a(a)).fmap(x -> (Choice3) x), + b -> pure.apply(b(b)).fmap(x -> (Choice3) x), + c -> fn.apply(c).fmap(Choice3::c)); + } + /** * Static factory method for wrapping a value of type A in a {@link Choice3}. * diff --git a/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice4.java b/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice4.java index 39cb2d518..10528b12b 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice4.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice4.java @@ -5,6 +5,7 @@ import com.jnape.palatable.lambda.functor.Applicative; import com.jnape.palatable.lambda.functor.Bifunctor; import com.jnape.palatable.lambda.functor.Functor; +import com.jnape.palatable.lambda.traversable.Traversable; import java.util.Objects; import java.util.function.Function; @@ -19,7 +20,11 @@ * @see Choice3 * @see Choice5 */ -public abstract class Choice4 implements CoProduct4>, Applicative>, Bifunctor> { +public abstract class Choice4 implements + CoProduct4>, + Applicative>, + Bifunctor>, + Traversable> { private Choice4() { } @@ -82,6 +87,16 @@ public Choice4 discardR(Applicative> appB return Applicative.super.discardR(appB).coerce(); } + @Override + public Applicative, App> traverse( + Function> fn, + Function>, ? extends Applicative>, App>> pure) { + return match(a -> pure.apply(a(a)).fmap(x -> (Choice4) x), + b -> pure.apply(b(b)).fmap(x -> (Choice4) x), + c -> pure.apply(c(c)).fmap(x -> (Choice4) x), + d -> fn.apply(d).fmap(Choice4::d)); + } + /** * Static factory method for wrapping a value of type A in a {@link Choice4}. * diff --git a/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice5.java b/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice5.java index aa6b4cecb..60d52e3f9 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice5.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice5.java @@ -5,6 +5,7 @@ import com.jnape.palatable.lambda.functor.Applicative; import com.jnape.palatable.lambda.functor.Bifunctor; import com.jnape.palatable.lambda.functor.Functor; +import com.jnape.palatable.lambda.traversable.Traversable; import java.util.Objects; import java.util.function.Function; @@ -19,7 +20,11 @@ * @param a type parameter representing the fifth possible type of this choice * @see Choice4 */ -public abstract class Choice5 implements CoProduct5>, Applicative>, Bifunctor> { +public abstract class Choice5 implements + CoProduct5>, + Applicative>, + Bifunctor>, + Traversable> { private Choice5() { } @@ -78,6 +83,17 @@ public Choice5 discardR(Applicative return Applicative.super.discardR(appB).coerce(); } + @Override + public Applicative, App> traverse( + Function> fn, + Function>, ? extends Applicative>, App>> pure) { + return match(a -> pure.apply(a(a)).fmap(x -> (Choice5) x), + b -> pure.apply(b(b)).fmap(x -> (Choice5) x), + c -> pure.apply(c(c)).fmap(x -> (Choice5) x), + d -> pure.apply(d(d)).fmap(x -> (Choice5) x), + e -> fn.apply(e).fmap(Choice5::e)); + } + /** * Static factory method for wrapping a value of type A in a {@link Choice5}. * 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 b6d57d7d8..c74aa83aa 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 @@ -3,6 +3,7 @@ import com.jnape.palatable.lambda.adt.hlist.HList.HCons; import com.jnape.palatable.lambda.adt.hlist.HList.HNil; import com.jnape.palatable.lambda.functor.Applicative; +import com.jnape.palatable.lambda.traversable.Traversable; import java.util.function.Function; @@ -16,7 +17,7 @@ * @see Tuple4 * @see Tuple5 */ -public class SingletonHList<_1> extends HCons<_1, HNil> implements Applicative<_1, SingletonHList> { +public class SingletonHList<_1> extends HCons<_1, HNil> implements Applicative<_1, SingletonHList>, Traversable<_1, SingletonHList> { SingletonHList(_1 _1) { super(_1, nil()); @@ -55,4 +56,11 @@ public <_1Prime> SingletonHList<_1Prime> discardL(Applicative<_1Prime, Singleton public <_1Prime> SingletonHList<_1> discardR(Applicative<_1Prime, SingletonHList> appB) { return Applicative.super.discardR(appB).coerce(); } + + @Override + public <_1Prime, App extends Applicative> Applicative, App> traverse( + Function> fn, + Function, ? extends Applicative, App>> pure) { + return fn.apply(head()).fmap(SingletonHList::new); + } } 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 b33283d74..aa9fa1f93 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,11 +3,14 @@ import com.jnape.palatable.lambda.adt.hlist.HList.HCons; import com.jnape.palatable.lambda.functor.Applicative; import com.jnape.palatable.lambda.functor.Bifunctor; +import com.jnape.palatable.lambda.traversable.Traversable; import java.util.Map; import java.util.function.BiFunction; import java.util.function.Function; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; + /** * A 2-element tuple product type, implemented as a specialized HList. Supports random access. * @@ -20,7 +23,7 @@ * @see Tuple5 */ public class Tuple2<_1, _2> extends HCons<_1, SingletonHList<_2>> - implements Map.Entry<_1, _2>, Applicative<_2, Tuple2<_1, ?>>, Bifunctor<_1, _2, Tuple2> { + implements Map.Entry<_1, _2>, Applicative<_2, Tuple2<_1, ?>>, Bifunctor<_1, _2, Tuple2>, Traversable<_2, Tuple2<_1, ?>> { private final _1 _1; private final _2 _2; @@ -126,6 +129,13 @@ public <_2Prime> Tuple2<_1, _2> discardR(Applicative<_2Prime, Tuple2<_1, ?>> app return Applicative.super.discardR(appB).coerce(); } + @Override + public <_2Prime, App extends Applicative> Applicative, App> traverse( + Function> fn, + Function>, ? extends Applicative>, App>> pure) { + return fn.apply(_2).fmap(_2Prime -> fmap(constantly(_2Prime))); + } + /** * Static factory method for creating Tuple2s from {@link java.util.Map.Entry}s. * diff --git a/src/main/java/com/jnape/palatable/lambda/adt/hlist/Tuple3.java b/src/main/java/com/jnape/palatable/lambda/adt/hlist/Tuple3.java index 2a124904c..d4a485467 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 @@ -4,9 +4,12 @@ import com.jnape.palatable.lambda.functions.Fn3; import com.jnape.palatable.lambda.functor.Applicative; import com.jnape.palatable.lambda.functor.Bifunctor; +import com.jnape.palatable.lambda.traversable.Traversable; import java.util.function.Function; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; + /** * A 3-element tuple product type, implemented as a specialized HList. Supports random access. * @@ -20,7 +23,7 @@ * @see Tuple5 */ public class Tuple3<_1, _2, _3> extends HCons<_1, Tuple2<_2, _3>> - implements Applicative<_3, Tuple3<_1, _2, ?>>, Bifunctor<_2, _3, Tuple3<_1, ?, ?>> { + implements Applicative<_3, Tuple3<_1, _2, ?>>, Bifunctor<_2, _3, Tuple3<_1, ?, ?>>, Traversable<_3, Tuple3<_1, _2, ?>> { private final _1 _1; private final _2 _2; private final _3 _3; @@ -122,6 +125,13 @@ public <_3Prime> Tuple3<_1, _2, _3> discardR(Applicative<_3Prime, Tuple3<_1, _2, return Applicative.super.discardR(appB).coerce(); } + @Override + public <_3Prime, App extends Applicative> Applicative, App> traverse( + Function> fn, + Function>, ? extends Applicative>, App>> pure) { + return fn.apply(_3).fmap(_3Prime -> fmap(constantly(_3Prime))); + } + /** * Given a value of type A, produced an instance of this tuple with each slot set to that value. * diff --git a/src/main/java/com/jnape/palatable/lambda/adt/hlist/Tuple4.java b/src/main/java/com/jnape/palatable/lambda/adt/hlist/Tuple4.java index 256e5ce5e..f8294de82 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 @@ -4,9 +4,12 @@ import com.jnape.palatable.lambda.functions.Fn4; import com.jnape.palatable.lambda.functor.Applicative; import com.jnape.palatable.lambda.functor.Bifunctor; +import com.jnape.palatable.lambda.traversable.Traversable; import java.util.function.Function; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; + /** * A 4-element tuple product type, implemented as a specialized HList. Supports random access. * @@ -21,7 +24,7 @@ * @see Tuple5 */ public class Tuple4<_1, _2, _3, _4> extends HCons<_1, Tuple3<_2, _3, _4>> - implements Applicative<_4, Tuple4<_1, _2, _3, ?>>, Bifunctor<_3, _4, Tuple4<_1, _2, ?, ?>> { + implements Applicative<_4, Tuple4<_1, _2, _3, ?>>, Bifunctor<_3, _4, Tuple4<_1, _2, ?, ?>>, Traversable<_4, Tuple4<_1, _2, _3, ?>> { private final _1 _1; private final _2 _2; private final _3 _3; @@ -134,6 +137,13 @@ public <_4Prime> Tuple4<_1, _2, _3, _4> discardR(Applicative<_4Prime, Tuple4<_1, return Applicative.super.discardR(appB).coerce(); } + @Override + public <_4Prime, App extends Applicative> Applicative, App> traverse( + Function> fn, + Function>, ? extends Applicative>, App>> pure) { + return fn.apply(_4).fmap(_4Prime -> fmap(constantly(_4Prime))); + } + /** * Given a value of type A, produced an instance of this tuple with each slot set to that value. * diff --git a/src/main/java/com/jnape/palatable/lambda/adt/hlist/Tuple5.java b/src/main/java/com/jnape/palatable/lambda/adt/hlist/Tuple5.java index 37f6c2271..5bfd06f15 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,9 +3,12 @@ import com.jnape.palatable.lambda.adt.hlist.HList.HCons; import com.jnape.palatable.lambda.functor.Applicative; import com.jnape.palatable.lambda.functor.Bifunctor; +import com.jnape.palatable.lambda.traversable.Traversable; import java.util.function.Function; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; + /** * A 5-element tuple product type, implemented as a specialized HList. Supports random access. * @@ -21,7 +24,7 @@ * @see Tuple4 */ public class Tuple5<_1, _2, _3, _4, _5> extends HCons<_1, Tuple4<_2, _3, _4, _5>> - implements Applicative<_5, Tuple5<_1, _2, _3, _4, ?>>, Bifunctor<_4, _5, Tuple5<_1, _2, _3, ?, ?>> { + implements Applicative<_5, Tuple5<_1, _2, _3, _4, ?>>, Bifunctor<_4, _5, Tuple5<_1, _2, _3, ?, ?>>, Traversable<_5, Tuple5<_1, _2, _3, _4, ?>> { private final _1 _1; private final _2 _2; private final _3 _3; @@ -132,6 +135,13 @@ public <_5Prime> Tuple5<_1, _2, _3, _4, _5> discardR(Applicative<_5Prime, Tuple5 return Applicative.super.discardR(appB).coerce(); } + @Override + public <_5Prime, App extends Applicative> Applicative, App> traverse( + Function> fn, + Function>, ? extends Applicative>, App>> pure) { + return fn.apply(_5).fmap(_5Prime -> fmap(constantly(_5Prime))); + } + /** * Given a value of type A, produced an instance of this tuple with each slot set to that value. * diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Sequence.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Sequence.java new file mode 100644 index 000000000..d09510a7a --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Sequence.java @@ -0,0 +1,59 @@ +package com.jnape.palatable.lambda.functions.builtin.fn2; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.Fn2; +import com.jnape.palatable.lambda.functor.Applicative; +import com.jnape.palatable.lambda.traversable.Traversable; + +import java.util.function.Function; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; + +/** + * Given a {@link Traversable} of {@link Applicative}s and a pure {@link Applicative} constructor, traverse the elements + * from left to right, zipping the applicatives together and collecting the results. If the traversable is empty, simply + * wrap it in the applicative by calling pure. + *

+ * Modulo any type-level coercion, this is equivalent to traversable.traverse(id(), pure). + * + * @param the Traversable element type + * @param the Applicative unification parameter + * @param the Traversable unification parameter + * @param the concrete parametrized output Applicative type + * @param the concrete parametrized input Traversable type + */ +public final class Sequence, App>, + TravApp extends Traversable, Trav>> implements Fn2, ? extends AppTrav>, AppTrav> { + + private static final Sequence INSTANCE = new Sequence(); + + private Sequence() { + } + + @Override + @SuppressWarnings("unchecked") + public AppTrav apply(TravApp traversable, Function, ? extends AppTrav> pure) { + return (AppTrav) traversable.traverse(id(), pure); + } + + @SuppressWarnings("unchecked") + public static , App>, + TravApp extends Traversable, Trav>> Sequence sequence() { + return INSTANCE; + } + + public static , App>, + TravApp extends Traversable, Trav>> Fn1, ? extends AppTrav>, AppTrav> sequence( + TravApp traversable) { + return Sequence.sequence().apply(traversable); + } + + public static , App>, + TravApp extends Traversable, Trav>> AppTrav sequence(TravApp traversable, + Function, ? extends AppTrav> pure) { + return Sequence.sequence(traversable).apply(pure); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functor/builtin/Compose.java b/src/main/java/com/jnape/palatable/lambda/functor/builtin/Compose.java new file mode 100644 index 000000000..70a5c39a9 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functor/builtin/Compose.java @@ -0,0 +1,52 @@ +package com.jnape.palatable.lambda.functor.builtin; + +import com.jnape.palatable.lambda.functor.Applicative; + +import java.util.Objects; +import java.util.function.Function; + +/** + * A functor representing the type-level composition of two {@link Applicative} functors; useful for preserving nested + * type-level transformations during traversal of a {@link com.jnape.palatable.lambda.traversable.Traversable}. + * + * @param The outer applicative + * @param The inner applicative + * @param The carrier type + */ +public class Compose implements Applicative> { + + private final Applicative, F> fga; + + public Compose(Applicative, F> fga) { + this.fga = fga; + } + + public Applicative, F> getCompose() { + return fga; + } + + @Override + public Compose fmap(Function fn) { + return new Compose<>(fga.fmap(g -> g.fmap(fn))); + } + + @Override + public Compose pure(B b) { + return new Compose<>(fga.fmap(g -> g.pure(b))); + } + + @Override + public Compose zip(Applicative, Compose> appFn) { + return new Compose<>(fga.zip(appFn.>>coerce().getCompose().fmap(gFn -> g -> g.zip(gFn)))); + } + + @Override + public boolean equals(Object other) { + return other instanceof Compose && Objects.equals(fga, ((Compose) other).fga); + } + + @Override + public int hashCode() { + return Objects.hash(fga); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functor/builtin/Const.java b/src/main/java/com/jnape/palatable/lambda/functor/builtin/Const.java index d20936274..d2b58f83c 100644 --- a/src/main/java/com/jnape/palatable/lambda/functor/builtin/Const.java +++ b/src/main/java/com/jnape/palatable/lambda/functor/builtin/Const.java @@ -2,6 +2,7 @@ import com.jnape.palatable.lambda.functor.Applicative; import com.jnape.palatable.lambda.functor.Bifunctor; +import com.jnape.palatable.lambda.traversable.Traversable; import java.util.Objects; import java.util.function.Function; @@ -15,7 +16,7 @@ * @param the left parameter type, and the type of the stored value * @param the right (phantom) parameter type */ -public final class Const implements Applicative>, Bifunctor { +public final class Const implements Applicative>, Bifunctor, Traversable> { private final A a; @@ -78,6 +79,13 @@ public Const discardR(Applicative> appB) { return Applicative.super.discardR(appB).coerce(); } + @Override + public Applicative, App> traverse( + Function> fn, + Function>, ? extends Applicative>, App>> pure) { + return pure.apply(coerce()).fmap(x -> (Const) x); + } + /** * Covariantly map over the left parameter type (the 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 28a2b5627..9f0a5cda5 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,6 +1,7 @@ package com.jnape.palatable.lambda.functor.builtin; import com.jnape.palatable.lambda.functor.Applicative; +import com.jnape.palatable.lambda.traversable.Traversable; import java.util.Objects; import java.util.function.Function; @@ -10,7 +11,7 @@ * * @param the value type */ -public final class Identity implements Applicative { +public final class Identity implements Applicative, Traversable { private final A a; @@ -60,6 +61,13 @@ public Identity discardR(Applicative appB) { return Applicative.super.discardR(appB).coerce(); } + @Override + public Applicative, App> traverse( + Function> fn, + Function, ? extends Applicative, App>> pure) { + return fn.apply(runIdentity()).fmap(Identity::new); + } + @Override public boolean equals(Object other) { return other instanceof Identity && Objects.equals(a, ((Identity) other).a); diff --git a/src/main/java/com/jnape/palatable/lambda/lens/Lens.java b/src/main/java/com/jnape/palatable/lambda/lens/Lens.java index 3d77fb399..9dadb582b 100644 --- a/src/main/java/com/jnape/palatable/lambda/lens/Lens.java +++ b/src/main/java/com/jnape/palatable/lambda/lens/Lens.java @@ -163,6 +163,11 @@ default Lens pure(U u) { return lens(view(this), (s, b) -> u); } + @Override + default Lens zip(Applicative, Lens> appFn) { + return lens(view(this), (s, b) -> set(appFn., A, B>>coerce(), b, s).apply(set(this, b, s))); + } + @Override default Lens discardL(Applicative> appB) { return Applicative.super.discardL(appB).coerce(); @@ -173,11 +178,6 @@ default Lens discardR(Applicative> appB) { return Applicative.super.discardR(appB).coerce(); } - @Override - default Lens zip(Applicative, Lens> appFn) { - return lens(view(this), (s, b) -> set(appFn., A, B>>coerce(), b, s).apply(set(this, b, s))); - } - /** * Contravariantly map S to R, yielding a new lens. * diff --git a/src/main/java/com/jnape/palatable/lambda/lens/lenses/HListLens.java b/src/main/java/com/jnape/palatable/lambda/lens/lenses/HListLens.java index b211d5918..d8780ed73 100644 --- a/src/main/java/com/jnape/palatable/lambda/lens/lenses/HListLens.java +++ b/src/main/java/com/jnape/palatable/lambda/lens/lenses/HListLens.java @@ -8,6 +8,9 @@ import static com.jnape.palatable.lambda.adt.hlist.HList.cons; import static com.jnape.palatable.lambda.lens.Lens.simpleLens; +/** + * Lenses that operate on {@link HList}s. + */ public final class HListLens { /** diff --git a/src/main/java/com/jnape/palatable/lambda/lens/lenses/MapLens.java b/src/main/java/com/jnape/palatable/lambda/lens/lenses/MapLens.java index 8cb9ff4dc..f14f0ec40 100644 --- a/src/main/java/com/jnape/palatable/lambda/lens/lenses/MapLens.java +++ b/src/main/java/com/jnape/palatable/lambda/lens/lenses/MapLens.java @@ -117,7 +117,7 @@ public static Lens.Simple, Collection> values() { public static Lens.Simple, Map> inverted() { return simpleLens(m -> { Map inverted = new HashMap<>(); - m.entrySet().forEach(entry -> inverted.put(entry.getValue(), entry.getKey())); + m.forEach((key, value) -> inverted.put(value, key)); return inverted; }, (m, im) -> { m.clear(); diff --git a/src/main/java/com/jnape/palatable/lambda/traversable/Traversable.java b/src/main/java/com/jnape/palatable/lambda/traversable/Traversable.java new file mode 100644 index 000000000..6d45f64fd --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/traversable/Traversable.java @@ -0,0 +1,51 @@ +package com.jnape.palatable.lambda.traversable; + +import com.jnape.palatable.lambda.functor.Applicative; +import com.jnape.palatable.lambda.functor.Functor; + +import java.util.function.Function; + +/** + * An interface for a class of data structures that can be "traversed from left to right" in a structure-preserving + * way, successively applying some applicative computation to each element and collapsing the results into a single + * resulting applicative. + *

+ * The same rules that apply to Functor apply to Traversable, along with the following + * additional 3 laws: + *

+ *

+ * For more information, read about + * Traversables. + * + * @param The type of the parameter + * @param The unification parameter + */ +public interface Traversable extends Functor { + + /** + * Apply fn to each element of this traversable from left to right, and collapse the results into + * a single resulting applicative, potentially with the assistance of the applicative's pure function. + * + * @param fn the function to apply + * @param pure the applicative pure function + * @param the resulting element type + * @param the result applicative type + * @return the traversed Traversable, wrapped inside an applicative + */ + Applicative, App> traverse( + Function> fn, + Function, ? extends Applicative, App>> pure); + + @Override + Traversable fmap(Function fn); +} diff --git a/src/test/java/com/jnape/palatable/lambda/adt/EitherTest.java b/src/test/java/com/jnape/palatable/lambda/adt/EitherTest.java index 688161a0e..00785f36f 100644 --- a/src/test/java/com/jnape/palatable/lambda/adt/EitherTest.java +++ b/src/test/java/com/jnape/palatable/lambda/adt/EitherTest.java @@ -10,6 +10,7 @@ import testsupport.traits.ApplicativeLaws; import testsupport.traits.BifunctorLaws; import testsupport.traits.FunctorLaws; +import testsupport.traits.TraversableLaws; import java.util.Optional; import java.util.concurrent.atomic.AtomicInteger; @@ -30,9 +31,9 @@ public class EitherTest { @Rule public ExpectedException thrown = ExpectedException.none(); - @TestTraits({FunctorLaws.class, ApplicativeLaws.class, BifunctorLaws.class}) + @TestTraits({FunctorLaws.class, ApplicativeLaws.class, BifunctorLaws.class, TraversableLaws.class}) public Subjects> testSubjects() { - return subjects(left("foo"), right(1)); + return subjects(right(1)); } @Test diff --git a/src/test/java/com/jnape/palatable/lambda/adt/choice/Choice2Test.java b/src/test/java/com/jnape/palatable/lambda/adt/choice/Choice2Test.java index 86d5e6287..4c540dde2 100644 --- a/src/test/java/com/jnape/palatable/lambda/adt/choice/Choice2Test.java +++ b/src/test/java/com/jnape/palatable/lambda/adt/choice/Choice2Test.java @@ -9,6 +9,7 @@ import testsupport.traits.ApplicativeLaws; import testsupport.traits.BifunctorLaws; import testsupport.traits.FunctorLaws; +import testsupport.traits.TraversableLaws; import static com.jnape.palatable.lambda.adt.choice.Choice2.a; import static com.jnape.palatable.lambda.adt.choice.Choice2.b; @@ -27,7 +28,7 @@ public void setUp() { b = b(true); } - @TestTraits({FunctorLaws.class, ApplicativeLaws.class, BifunctorLaws.class}) + @TestTraits({FunctorLaws.class, ApplicativeLaws.class, BifunctorLaws.class, TraversableLaws.class}) public Subjects> testSubjects() { return subjects(a("foo"), b(1)); } diff --git a/src/test/java/com/jnape/palatable/lambda/adt/choice/Choice3Test.java b/src/test/java/com/jnape/palatable/lambda/adt/choice/Choice3Test.java index c8720f81d..462f2835e 100644 --- a/src/test/java/com/jnape/palatable/lambda/adt/choice/Choice3Test.java +++ b/src/test/java/com/jnape/palatable/lambda/adt/choice/Choice3Test.java @@ -9,6 +9,7 @@ import testsupport.traits.ApplicativeLaws; import testsupport.traits.BifunctorLaws; import testsupport.traits.FunctorLaws; +import testsupport.traits.TraversableLaws; import static com.jnape.palatable.lambda.adt.choice.Choice3.a; import static com.jnape.palatable.lambda.adt.choice.Choice3.b; @@ -30,7 +31,7 @@ public void setUp() { c = Choice3.c(true); } - @TestTraits({FunctorLaws.class, ApplicativeLaws.class, BifunctorLaws.class}) + @TestTraits({FunctorLaws.class, ApplicativeLaws.class, BifunctorLaws.class, TraversableLaws.class}) public Subjects> testSubjects() { return subjects(a("foo"), b(1), c(true)); } diff --git a/src/test/java/com/jnape/palatable/lambda/adt/choice/Choice4Test.java b/src/test/java/com/jnape/palatable/lambda/adt/choice/Choice4Test.java index 2b7f3bc88..05be8aed8 100644 --- a/src/test/java/com/jnape/palatable/lambda/adt/choice/Choice4Test.java +++ b/src/test/java/com/jnape/palatable/lambda/adt/choice/Choice4Test.java @@ -9,6 +9,7 @@ import testsupport.traits.ApplicativeLaws; import testsupport.traits.BifunctorLaws; import testsupport.traits.FunctorLaws; +import testsupport.traits.TraversableLaws; import static com.jnape.palatable.lambda.adt.choice.Choice4.a; import static com.jnape.palatable.lambda.adt.choice.Choice4.b; @@ -33,7 +34,7 @@ public void setUp() { d = d(4D); } - @TestTraits({FunctorLaws.class, ApplicativeLaws.class, BifunctorLaws.class}) + @TestTraits({FunctorLaws.class, ApplicativeLaws.class, BifunctorLaws.class, TraversableLaws.class}) public Subjects> testSubjects() { return subjects(a("foo"), b(1), c(true), d('a')); } diff --git a/src/test/java/com/jnape/palatable/lambda/adt/choice/Choice5Test.java b/src/test/java/com/jnape/palatable/lambda/adt/choice/Choice5Test.java index d699f07cf..4c19f9992 100644 --- a/src/test/java/com/jnape/palatable/lambda/adt/choice/Choice5Test.java +++ b/src/test/java/com/jnape/palatable/lambda/adt/choice/Choice5Test.java @@ -9,6 +9,7 @@ import testsupport.traits.ApplicativeLaws; import testsupport.traits.BifunctorLaws; import testsupport.traits.FunctorLaws; +import testsupport.traits.TraversableLaws; import static com.jnape.palatable.lambda.adt.choice.Choice5.a; import static com.jnape.palatable.lambda.adt.choice.Choice5.b; @@ -36,7 +37,7 @@ public void setUp() { e = e('z'); } - @TestTraits({FunctorLaws.class, ApplicativeLaws.class, BifunctorLaws.class}) + @TestTraits({FunctorLaws.class, ApplicativeLaws.class, BifunctorLaws.class, TraversableLaws.class}) public Subjects> testSubjects() { return subjects(Choice5.a("foo"), Choice5.b(1), Choice5.c(true), Choice5.d('a'), Choice5.e(2d)); } 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 59f40b0d0..ecaf43429 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 @@ -7,6 +7,7 @@ import org.junit.runner.RunWith; import testsupport.traits.ApplicativeLaws; import testsupport.traits.FunctorLaws; +import testsupport.traits.TraversableLaws; import static com.jnape.palatable.lambda.adt.hlist.HList.nil; import static com.jnape.palatable.lambda.adt.hlist.HList.singletonHList; @@ -22,7 +23,7 @@ public void setUp() { singletonHList = new SingletonHList<>(1); } - @TestTraits({FunctorLaws.class, ApplicativeLaws.class}) + @TestTraits({FunctorLaws.class, ApplicativeLaws.class, TraversableLaws.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 f4ea2bb82..2e1d4f160 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 @@ -8,6 +8,7 @@ import testsupport.traits.ApplicativeLaws; import testsupport.traits.BifunctorLaws; import testsupport.traits.FunctorLaws; +import testsupport.traits.TraversableLaws; import java.util.HashMap; import java.util.Map; @@ -30,7 +31,7 @@ public void setUp() throws Exception { tuple2 = new Tuple2<>(1, new SingletonHList<>(2)); } - @TestTraits({FunctorLaws.class, ApplicativeLaws.class, BifunctorLaws.class}) + @TestTraits({FunctorLaws.class, ApplicativeLaws.class, BifunctorLaws.class, TraversableLaws.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 068bbb036..c1c1b9fe9 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 @@ -8,6 +8,7 @@ import testsupport.traits.ApplicativeLaws; import testsupport.traits.BifunctorLaws; import testsupport.traits.FunctorLaws; +import testsupport.traits.TraversableLaws; import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; import static org.junit.Assert.assertEquals; @@ -26,7 +27,7 @@ public void setUp() { tuple3 = new Tuple3<>(1, new Tuple2<>("2", new SingletonHList<>('3'))); } - @TestTraits({FunctorLaws.class, ApplicativeLaws.class, BifunctorLaws.class}) + @TestTraits({FunctorLaws.class, ApplicativeLaws.class, BifunctorLaws.class, TraversableLaws.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 8aa607afb..bfa35bc28 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 @@ -8,6 +8,7 @@ import testsupport.traits.ApplicativeLaws; import testsupport.traits.BifunctorLaws; import testsupport.traits.FunctorLaws; +import testsupport.traits.TraversableLaws; import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; import static org.junit.Assert.assertEquals; @@ -26,7 +27,7 @@ public void setUp() { tuple4 = new Tuple4<>(1, new Tuple3<>("2", new Tuple2<>('3', new SingletonHList<>(false)))); } - @TestTraits({FunctorLaws.class, ApplicativeLaws.class, BifunctorLaws.class}) + @TestTraits({FunctorLaws.class, ApplicativeLaws.class, BifunctorLaws.class, TraversableLaws.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 128f4e385..eec152f81 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 @@ -9,6 +9,7 @@ import testsupport.traits.ApplicativeLaws; import testsupport.traits.BifunctorLaws; import testsupport.traits.FunctorLaws; +import testsupport.traits.TraversableLaws; import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; import static org.junit.Assert.assertEquals; @@ -27,7 +28,7 @@ public void setUp() { tuple5 = new Tuple5<>(1, new Tuple4<>("2", new Tuple3<>('3', new Tuple2<>(false, new SingletonHList<>(5L))))); } - @TestTraits({FunctorLaws.class, ApplicativeLaws.class, BifunctorLaws.class}) + @TestTraits({FunctorLaws.class, ApplicativeLaws.class, BifunctorLaws.class, TraversableLaws.class}) public Tuple5 testSubject() { return tuple("one", 2, 3d, 4f, '5'); } diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/SequenceTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/SequenceTest.java new file mode 100644 index 000000000..5f573c9d0 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/SequenceTest.java @@ -0,0 +1,39 @@ +package com.jnape.palatable.lambda.functions.builtin.fn2; + +import com.jnape.palatable.lambda.adt.Either; +import com.jnape.palatable.lambda.functor.builtin.Compose; +import com.jnape.palatable.lambda.functor.builtin.Identity; +import org.junit.Test; + +import java.util.function.Function; + +import static com.jnape.palatable.lambda.adt.Either.right; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Sequence.sequence; +import static org.junit.Assert.assertEquals; + +public class SequenceTest { + + @Test + public void naturality() { + Function, Either> t = id -> right(id.runIdentity()); + Either> traversable = right(new Identity<>(1)); + + assertEquals(t.apply(sequence(traversable, Identity::new).fmap(id())), + sequence(traversable.fmap(t), Either::right)); + } + + @Test + public void identity() { + Either> traversable = right(new Identity<>(1)); + assertEquals(sequence(traversable.fmap(Identity::new), Identity::new), + new Identity<>(traversable)); + } + + @Test + public void composition() { + Either>> traversable = right(new Identity<>(right(1))); + assertEquals(sequence(traversable.fmap(x -> new Compose<>(x.fmap(id()))), x -> new Compose<>(new Identity<>(right(x)))), + new Compose<>(sequence(traversable, Identity::new).fmap(x -> sequence(x, Either::right)).fmap(id()))); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functor/builtin/ComposeTest.java b/src/test/java/com/jnape/palatable/lambda/functor/builtin/ComposeTest.java new file mode 100644 index 000000000..144b71d47 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functor/builtin/ComposeTest.java @@ -0,0 +1,16 @@ +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.ApplicativeLaws; +import testsupport.traits.FunctorLaws; + +@RunWith(Traits.class) +public class ComposeTest { + + @TestTraits({FunctorLaws.class, ApplicativeLaws.class}) + public Compose testSubject() { + return new Compose<>(new Identity<>(new Identity<>(1))); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functor/builtin/ConstTest.java b/src/test/java/com/jnape/palatable/lambda/functor/builtin/ConstTest.java index eb9bb0167..4ffc5bd0e 100644 --- a/src/test/java/com/jnape/palatable/lambda/functor/builtin/ConstTest.java +++ b/src/test/java/com/jnape/palatable/lambda/functor/builtin/ConstTest.java @@ -6,11 +6,12 @@ import testsupport.traits.ApplicativeLaws; import testsupport.traits.BifunctorLaws; import testsupport.traits.FunctorLaws; +import testsupport.traits.TraversableLaws; @RunWith(Traits.class) public class ConstTest { - @TestTraits({FunctorLaws.class, ApplicativeLaws.class, BifunctorLaws.class}) + @TestTraits({FunctorLaws.class, ApplicativeLaws.class, BifunctorLaws.class, TraversableLaws.class}) public Const testSubject() { return new Const<>(1); } 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 d627e8482..8d267d8f2 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 @@ -5,11 +5,12 @@ import org.junit.runner.RunWith; import testsupport.traits.ApplicativeLaws; import testsupport.traits.FunctorLaws; +import testsupport.traits.TraversableLaws; @RunWith(Traits.class) public class IdentityTest { - @TestTraits({FunctorLaws.class, ApplicativeLaws.class}) + @TestTraits({FunctorLaws.class, ApplicativeLaws.class, TraversableLaws.class}) public Identity testSubject() { return new Identity<>(""); } diff --git a/src/test/java/testsupport/traits/TraversableLaws.java b/src/test/java/testsupport/traits/TraversableLaws.java new file mode 100644 index 000000000..3908ed83a --- /dev/null +++ b/src/test/java/testsupport/traits/TraversableLaws.java @@ -0,0 +1,66 @@ +package testsupport.traits; + +import com.jnape.palatable.lambda.adt.Either; +import com.jnape.palatable.lambda.functions.builtin.fn2.Map; +import com.jnape.palatable.lambda.functor.Applicative; +import com.jnape.palatable.lambda.functor.builtin.Compose; +import com.jnape.palatable.lambda.functor.builtin.Identity; +import com.jnape.palatable.lambda.monoid.builtin.Present; +import com.jnape.palatable.lambda.traversable.Traversable; +import com.jnape.palatable.traitor.traits.Trait; + +import java.util.Optional; +import java.util.function.Function; + +import static com.jnape.palatable.lambda.adt.Either.right; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; +import static java.util.Arrays.asList; + +@SuppressWarnings("Convert2MethodRef") +public class TraversableLaws implements Trait> { + + @Override + public void test(Traversable traversable) { + Iterable> testResults = Map., Optional>, Optional>map( + f -> f.apply(traversable), + asList(this::testNaturality, this::testIdentity, this::testComposition) + ); + Present.present((x, y) -> x + "\n\t - " + y) + .reduceLeft(testResults) + .ifPresent(s -> { + throw new AssertionError("The following Traversable laws did not hold for instance of " + traversable.getClass() + ": \n\t - " + s); + }); + } + + private Optional testNaturality(Traversable trav) { + Function> f = Identity::new; + Function, Either> t = id -> right(id.runIdentity()); + + Function, Applicative, Identity>> pureFn = x -> new Identity<>(x); + Function, Applicative, Either>> pureFn2 = x -> right(x); + + return t.apply(trav.traverse(f, pureFn).fmap(id()).coerce()) + .equals(trav.traverse(t.compose(f), pureFn2).fmap(id()).coerce()) + ? Optional.empty() + : Optional.of("naturality (t.apply(trav.traverse(f, pureFn).fmap(id()).coerce())\n" + + " .equals(trav.traverse(t.compose(f), pureFn2).fmap(id()).coerce()))"); + } + + private Optional testIdentity(Traversable trav) { + return trav.traverse(Identity::new, x -> new Identity<>(x)).equals(new Identity<>(trav)) + ? Optional.empty() + : Optional.of("identity (trav.traverse(Identity::new, x -> new Identity<>(x)).equals(new Identity<>(trav))"); + } + + @SuppressWarnings("unchecked") + private Optional testComposition(Traversable trav) { + Function> f = Identity::new; + Function> g = x -> new Identity<>(x); + + return trav.traverse(f.andThen(x -> x.fmap(g)).andThen(Compose::new), x -> new Compose<>(new Identity<>(new Identity<>(x)))) + .equals(new Compose>(trav.traverse(f, x -> new Identity<>(x)).fmap(t -> t.traverse(g, x -> new Identity<>(x))))) + ? Optional.empty() + : Optional.of("compose (trav.traverse(f.andThen(x -> x.fmap(g)).andThen(Compose::new), x -> new Compose<>(new Identity<>(new Identity<>(x))))\n" + + " .equals(new Compose>(trav.traverse(f, x -> new Identity<>(x)).fmap(t -> t.traverse(g, x -> new Identity<>(x))))))"); + } +} From 1a75a0234eca908288da699972b28a61a9817d88 Mon Sep 17 00:00:00 2001 From: jnape Date: Mon, 29 May 2017 18:21:25 -0500 Subject: [PATCH 18/24] Adding Traversable wrappers for Iterable and Optional; adding specialized sequence overloads to support both types directly --- .../functions/builtin/fn2/Sequence.java | 42 ++++++++- .../traversable/TraversableIterable.java | 92 +++++++++++++++++++ .../traversable/TraversableOptional.java | 77 ++++++++++++++++ .../lambda/traversable/Traversables.java | 34 +++++++ .../functions/builtin/fn2/SequenceTest.java | 17 ++++ .../lambda/traversable/TraversablesTest.java | 24 +++++ .../java/spike/TraversableIterableTest.java | 21 +++++ .../java/spike/TraversableOptionalTest.java | 22 +++++ 8 files changed, 327 insertions(+), 2 deletions(-) create mode 100644 src/main/java/com/jnape/palatable/lambda/traversable/TraversableIterable.java create mode 100644 src/main/java/com/jnape/palatable/lambda/traversable/TraversableOptional.java create mode 100644 src/main/java/com/jnape/palatable/lambda/traversable/Traversables.java create mode 100644 src/test/java/com/jnape/palatable/lambda/traversable/TraversablesTest.java create mode 100644 src/test/java/spike/TraversableIterableTest.java create mode 100644 src/test/java/spike/TraversableOptionalTest.java diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Sequence.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Sequence.java index d09510a7a..228536162 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Sequence.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Sequence.java @@ -4,17 +4,25 @@ import com.jnape.palatable.lambda.functions.Fn2; import com.jnape.palatable.lambda.functor.Applicative; import com.jnape.palatable.lambda.traversable.Traversable; +import com.jnape.palatable.lambda.traversable.TraversableIterable; +import com.jnape.palatable.lambda.traversable.TraversableOptional; +import java.util.Optional; import java.util.function.Function; import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; /** - * Given a {@link Traversable} of {@link Applicative}s and a pure {@link Applicative} constructor, traverse the elements - * from left to right, zipping the applicatives together and collecting the results. If the traversable is empty, simply + * Given a {@link Traversable} of {@link Applicative}s and a pure {@link Applicative} constructor, traverse the + * elements + * from left to right, zipping the applicatives together and collecting the results. If the traversable is empty, + * simply * wrap it in the applicative by calling pure. *

* Modulo any type-level coercion, this is equivalent to traversable.traverse(id(), pure). + *

+ * Note that specialized overloads exist for certain built-in JDK types that would otherwise be instances + * {@link Traversable} if it weren't for the extensibility problem. * * @param the Traversable element type * @param the Applicative unification parameter @@ -56,4 +64,34 @@ TravApp extends Traversable, Trav>> AppTrav sequen Function, ? extends AppTrav> pure) { return Sequence.sequence(traversable).apply(pure); } + + @SuppressWarnings("unchecked") + public static , App>, IterableApp extends Iterable>> Fn1, ? extends AppIterable>, AppIterable> sequence( + IterableApp optionalApp) { + return pure -> + (AppIterable) sequence(TraversableIterable.wrap(optionalApp), x -> pure.apply(((TraversableIterable) x).unwrap()) + .fmap(TraversableIterable::wrap)) + .fmap(TraversableIterable::unwrap); + } + + @SuppressWarnings("unchecked") + public static , App>, OptionalApp extends Optional>> Fn1, ? extends AppOptional>, AppOptional> sequence( + OptionalApp optionalApp) { + return pure -> + (AppOptional) sequence(TraversableOptional.wrap(optionalApp), x -> pure.apply(((TraversableOptional) x).unwrap()) + .fmap(TraversableOptional::wrap)) + .fmap(TraversableOptional::unwrap); + } + + public static , App>, + IterableApp extends Iterable>> AppIterable sequence(IterableApp iterableApp, + Function, ? extends AppIterable> pure) { + return Sequence.sequence(iterableApp).apply(pure); + } + + public static , App>, + OptionalApp extends Optional>> AppOptional sequence(OptionalApp optionalApp, + Function, ? extends AppOptional> pure) { + return Sequence.sequence(optionalApp).apply(pure); + } } diff --git a/src/main/java/com/jnape/palatable/lambda/traversable/TraversableIterable.java b/src/main/java/com/jnape/palatable/lambda/traversable/TraversableIterable.java new file mode 100644 index 000000000..340b04a79 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/traversable/TraversableIterable.java @@ -0,0 +1,92 @@ +package com.jnape.palatable.lambda.traversable; + +import com.jnape.palatable.lambda.functor.Applicative; + +import java.util.Iterator; +import java.util.Objects; +import java.util.function.Function; + +import static com.jnape.palatable.lambda.functions.builtin.fn2.Cons.cons; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Map.map; +import static com.jnape.palatable.lambda.functions.builtin.fn3.FoldRight.foldRight; +import static java.util.Collections.emptyList; + +/** + * Wrap an {@link Iterable} in a {@link Traversable} such that {@link Traversable#traverse(Function, Function)} applies + * its computation against each element of the wrapped {@link Iterable}. Returns the result of pure if the + * wrapped {@link Iterable} is empty. + * + * @param the Iterable element type + */ +public final class TraversableIterable implements Traversable { + private final Iterable as; + + @SuppressWarnings("unchecked") + private TraversableIterable(Iterable as) { + this.as = (Iterable) as; + } + + /** + * Unwrap the underlying {@link Iterable}. + * + * @return the wrapped Iterable + */ + public Iterable unwrap() { + return as; + } + + @Override + public TraversableIterable fmap(Function fn) { + return wrap(map(fn, as)); + } + + @Override + public Applicative, App> traverse( + Function> fn, + Function, ? extends Applicative, App>> pure) { + return foldRight((a, appTrav) -> appTrav.zip(fn.apply(a).fmap(b -> bs -> TraversableIterable.wrap(cons(b, bs.unwrap())))), + pure.apply(TraversableIterable.empty()).fmap(ti -> (TraversableIterable) ti), + as); + } + + @Override + public boolean equals(Object other) { + if (other instanceof TraversableIterable) { + Iterator xs = as.iterator(); + Iterator ys = ((TraversableIterable) other).as.iterator(); + + while (xs.hasNext() && ys.hasNext()) + if (!Objects.equals(xs.next(), ys.next())) + return false; + + return xs.hasNext() == ys.hasNext(); + } + return false; + } + + @Override + public int hashCode() { + return Objects.hash(as); + } + + /** + * Wrap an {@link Iterable} in a TraversableIterable. + * + * @param as the Iterable + * @param the Iterable element type + * @return the Iterable wrapped in a TraversableIterable + */ + public static TraversableIterable wrap(Iterable as) { + return new TraversableIterable<>(as); + } + + /** + * Construct an empty TraversableIterable by wrapping {@link java.util.Collections#emptyList()}. + * + * @param the Iterable element type + * @return a TraversableIterable wrapping Collections.emptyList() + */ + public static TraversableIterable empty() { + return wrap(emptyList()); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/traversable/TraversableOptional.java b/src/main/java/com/jnape/palatable/lambda/traversable/TraversableOptional.java new file mode 100644 index 000000000..5bd769526 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/traversable/TraversableOptional.java @@ -0,0 +1,77 @@ +package com.jnape.palatable.lambda.traversable; + +import com.jnape.palatable.lambda.functor.Applicative; + +import java.util.Objects; +import java.util.Optional; +import java.util.function.Function; + +/** + * Wrap an {@link Optional} in a {@link Traversable} such that {@link Traversable#traverse(Function, Function)} applies + * its computation against the value wrapped by the wrapped {@link Optional} (if the {@link Optional} is present). + * Returns the result of pure if the wrapped {@link Optional} is empty. + * + * @param the Optional parameter type + */ +public final class TraversableOptional implements Traversable { + private final Optional delegate; + + @SuppressWarnings("unchecked") + private TraversableOptional(Optional delegate) { + this.delegate = (Optional) delegate; + } + + /** + * Unwrap the underlying {@link Optional}. + * + * @return the wrapped Optional + */ + public Optional unwrap() { + return delegate; + } + + @Override + public TraversableOptional fmap(Function fn) { + return new TraversableOptional<>(delegate.map(fn)); + } + + @Override + public Applicative, App> traverse( + Function> fn, + Function, ? extends Applicative, App>> pure) { + return fmap(fn).delegate.map(app -> app.fmap(Optional::of).fmap(TraversableOptional::wrap)) + .orElseGet(() -> pure.apply(TraversableOptional.empty()).fmap(x -> (TraversableOptional) x)); + } + + @Override + public boolean equals(Object other) { + return other instanceof TraversableOptional + && Objects.equals(delegate, ((TraversableOptional) other).delegate); + } + + @Override + public int hashCode() { + return Objects.hash(delegate); + } + + /** + * Wrap an {@link Optional} in a TraversableOptional. + * + * @param optional the Optional + * @param the Optional parameter type + * @return the Optional wrapped in a TraversableOptional + */ + public static TraversableOptional wrap(Optional optional) { + return new TraversableOptional<>(optional); + } + + /** + * Construct an empty TraversableOptional by wrapping {@link Optional#empty()}. + * + * @param the optional parameter type + * @return a TraversableOptional wrapping Optional.empty() + */ + public static TraversableOptional empty() { + return TraversableOptional.wrap(Optional.empty()); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/traversable/Traversables.java b/src/main/java/com/jnape/palatable/lambda/traversable/Traversables.java new file mode 100644 index 000000000..54a6b0903 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/traversable/Traversables.java @@ -0,0 +1,34 @@ +package com.jnape.palatable.lambda.traversable; + +import java.util.Optional; + +/** + * Static factory methods for adapting core JDK types to {@link Traversable}. + */ +public final class Traversables { + + private Traversables() { + } + + /** + * Wrap an {@link Iterable} in a {@link Traversable}. + * + * @param as the Iterable + * @param the Iterable element type + * @return a Traversable wrapper around as + */ + static TraversableIterable traversable(Iterable as) { + return TraversableIterable.wrap(as); + } + + /** + * Wrap an {@link Optional} in a {@link Traversable}. + * + * @param opt the Optional + * @param the Optional type + * @return a Traversable wrapper around opt + */ + static TraversableOptional traversable(Optional opt) { + return TraversableOptional.wrap(opt); + } +} diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/SequenceTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/SequenceTest.java index 5f573c9d0..f5a8825d5 100644 --- a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/SequenceTest.java +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/SequenceTest.java @@ -5,12 +5,16 @@ import com.jnape.palatable.lambda.functor.builtin.Identity; import org.junit.Test; +import java.util.Optional; import java.util.function.Function; import static com.jnape.palatable.lambda.adt.Either.right; import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; import static com.jnape.palatable.lambda.functions.builtin.fn2.Sequence.sequence; +import static java.util.Arrays.asList; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; +import static testsupport.matchers.IterableMatcher.iterates; public class SequenceTest { @@ -36,4 +40,17 @@ public void composition() { assertEquals(sequence(traversable.fmap(x -> new Compose<>(x.fmap(id()))), x -> new Compose<>(new Identity<>(right(x)))), new Compose<>(sequence(traversable, Identity::new).fmap(x -> sequence(x, Either::right)).fmap(id()))); } + + @Test + public void iterableSpecialization() { + assertThat(sequence(asList(right(1), right(2)), Either::right) + .orThrow(l -> new AssertionError("Expected a right value, but was a left value of <" + l + ">")), + iterates(1, 2)); + } + + @Test + public void optionalSpecialization() { + assertEquals(right(Optional.of(1)), + sequence(Optional.of(right(1)), Either::right)); + } } \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/traversable/TraversablesTest.java b/src/test/java/com/jnape/palatable/lambda/traversable/TraversablesTest.java new file mode 100644 index 000000000..e11e7e12c --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/traversable/TraversablesTest.java @@ -0,0 +1,24 @@ +package com.jnape.palatable.lambda.traversable; + +import org.junit.Test; + +import java.util.Optional; + +import static com.jnape.palatable.lambda.traversable.Traversables.traversable; +import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; +import static org.junit.Assert.assertEquals; + +public class TraversablesTest { + + @Test + public void staticFactoryMethods() { + Iterable ints = asList(1, 2, 3); + assertEquals(TraversableIterable.wrap(ints), traversable(ints)); + assertEquals(TraversableIterable.empty(), traversable(emptyList())); + + Optional optString = Optional.of("foo"); + assertEquals(TraversableOptional.wrap(optString), traversable(optString)); + assertEquals(TraversableOptional.empty(), traversable(Optional.empty())); + } +} \ No newline at end of file diff --git a/src/test/java/spike/TraversableIterableTest.java b/src/test/java/spike/TraversableIterableTest.java new file mode 100644 index 000000000..1f179eb62 --- /dev/null +++ b/src/test/java/spike/TraversableIterableTest.java @@ -0,0 +1,21 @@ +package spike; + +import com.jnape.palatable.lambda.traversable.TraversableIterable; +import com.jnape.palatable.traitor.annotations.TestTraits; +import com.jnape.palatable.traitor.framework.Subjects; +import com.jnape.palatable.traitor.runners.Traits; +import org.junit.runner.RunWith; +import testsupport.traits.FunctorLaws; +import testsupport.traits.TraversableLaws; + +import static com.jnape.palatable.traitor.framework.Subjects.subjects; +import static java.util.Arrays.asList; + +@RunWith(Traits.class) +public class TraversableIterableTest { + + @TestTraits({FunctorLaws.class, TraversableLaws.class}) + public Subjects> testSubject() { + return subjects(TraversableIterable.empty(), TraversableIterable.wrap(asList(1, 2, 3))); + } +} \ No newline at end of file diff --git a/src/test/java/spike/TraversableOptionalTest.java b/src/test/java/spike/TraversableOptionalTest.java new file mode 100644 index 000000000..85717ff43 --- /dev/null +++ b/src/test/java/spike/TraversableOptionalTest.java @@ -0,0 +1,22 @@ +package spike; + +import com.jnape.palatable.lambda.traversable.TraversableOptional; +import com.jnape.palatable.traitor.annotations.TestTraits; +import com.jnape.palatable.traitor.framework.Subjects; +import com.jnape.palatable.traitor.runners.Traits; +import org.junit.runner.RunWith; +import testsupport.traits.FunctorLaws; +import testsupport.traits.TraversableLaws; + +import java.util.Optional; + +import static com.jnape.palatable.traitor.framework.Subjects.subjects; + +@RunWith(Traits.class) +public class TraversableOptionalTest { + + @TestTraits({FunctorLaws.class, TraversableLaws.class}) + public Subjects> testSubject() { + return subjects(TraversableOptional.empty(), TraversableOptional.wrap(Optional.of(1))); + } +} \ No newline at end of file From c6ef16768236a1a71b7a6bfbd70f72a7df36276e Mon Sep 17 00:00:00 2001 From: jnape Date: Mon, 29 May 2017 14:47:51 -0500 Subject: [PATCH 19/24] Fixing up javadocs --- src/main/java/com/jnape/palatable/lambda/adt/Either.java | 4 ++-- .../palatable/lambda/functions/builtin/fn2/GroupBy.java | 2 +- .../com/jnape/palatable/lambda/functor/Applicative.java | 2 +- src/main/java/com/jnape/palatable/lambda/lens/Lens.java | 1 + .../jnape/palatable/lambda/traversable/Traversable.java | 9 ++++----- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/main/java/com/jnape/palatable/lambda/adt/Either.java b/src/main/java/com/jnape/palatable/lambda/adt/Either.java index c666b58f0..f69fac64d 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/Either.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/Either.java @@ -53,8 +53,8 @@ public final R recover(Function recoveryFn) { } /** - * Inverse of recover. If this is a right value, apply the wrapped value to forfeitFn and return it; otherwise, - * return the wrapped left value; + * Inverse of recover. If this is a right value, apply the wrapped value to forfeitFn and return it; + * otherwise, return the wrapped left value. * * @param forfeitFn a function from R to L * @return either the wrapped value (if left) or the result of the right value applied to forfeitFn diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/GroupBy.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/GroupBy.java index 5759c0e3a..11dc659bc 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/GroupBy.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/GroupBy.java @@ -12,7 +12,7 @@ import static com.jnape.palatable.lambda.functions.builtin.fn3.FoldLeft.foldLeft; /** - * Given an Iterable<V> vs and a key function V -> K f, fold + * Given an Iterable<V> vs and a key function V -> K f, fold * vs into a Map<K, List<V>> by applying f to each element of * vs, retaining values that map to the same key in a list, in the order they were iterated in. * diff --git a/src/main/java/com/jnape/palatable/lambda/functor/Applicative.java b/src/main/java/com/jnape/palatable/lambda/functor/Applicative.java index 8f0f5b855..f345cdc2d 100644 --- a/src/main/java/com/jnape/palatable/lambda/functor/Applicative.java +++ b/src/main/java/com/jnape/palatable/lambda/functor/Applicative.java @@ -15,7 +15,7 @@ *

  • identity: v.zip(pureId).equals(v)
  • *
  • composition: w.zip(v.zip(u.zip(pureCompose))).equals((w.zip(v)).zip(u))
  • *
  • homomorphism: pureX.zip(pureF).equals(pureFx)
  • - *
  • interchange: pureY.zip(u).equals(u.zip(pure(f -> f.apply(y))))
  • + *
  • interchange: pureY.zip(u).equals(u.zip(pure(f -> f.apply(y))))
  • * * As with Functor, Applicative instances that do not satisfy all of the functor laws, as well * as the above applicative laws, are not well-behaved and often break down in surprising ways. diff --git a/src/main/java/com/jnape/palatable/lambda/lens/Lens.java b/src/main/java/com/jnape/palatable/lambda/lens/Lens.java index 9dadb582b..bfdc5cdf8 100644 --- a/src/main/java/com/jnape/palatable/lambda/lens/Lens.java +++ b/src/main/java/com/jnape/palatable/lambda/lens/Lens.java @@ -144,6 +144,7 @@ , FB extends Functor> FT apply * Although the Java type system does not allow enforceability, the functor instance FT should be the same as FB, * only differentiating in their parameters. * + * @param the common functor type of FT and FB * @param the type of the lifted T * @param the type of the lifted B * @return the lens, "fixed" to the functor diff --git a/src/main/java/com/jnape/palatable/lambda/traversable/Traversable.java b/src/main/java/com/jnape/palatable/lambda/traversable/Traversable.java index 6d45f64fd..4e41513be 100644 --- a/src/main/java/com/jnape/palatable/lambda/traversable/Traversable.java +++ b/src/main/java/com/jnape/palatable/lambda/traversable/Traversable.java @@ -15,13 +15,12 @@ *
      *
    • naturality: t.apply(trav.traverse(f, pure).<Object>fmap(id()).coerce()) * .equals(trav.traverse(t.compose(f), pure2).<Object>fmap(id()).coerce())
    • - *
    • identity: trav.traverse(Identity::new, x -> new Identity<>(x)).equals(new + *
    • identity: trav.traverse(Identity::new, x -> new Identity<>(x)).equals(new * Identity<>(trav)
    • - *
    • composition: trav.traverse(f.andThen(x -> x.fmap(g)).andThen(Compose::new), x -> + *
    • composition: trav.traverse(f.andThen(x -> x.fmap(g)).andThen(Compose::new), x -> * new Compose<>(new Identity<>(new Identity<>(x)))).equals(new Compose<Identity, Identity, - * Traversable<Object, Trav>>(trav.traverse(f, x -> new Identity<>(x)).fmap(t -> - * t.traverse(g, x -> new Identity<>(x)))))
    • - *

      + * Traversable<Object, Trav>>(trav.traverse(f, x -> new Identity<>(x)).fmap(t -> + * t.traverse(g, x -> new Identity<>(x))))) *

    *

    * For more information, read about From 3f09cb4231652342d35551adf5ca5e30289eb264 Mon Sep 17 00:00:00 2001 From: jnape Date: Mon, 29 May 2017 18:28:12 -0500 Subject: [PATCH 20/24] Failing compilation in the presence of warnings --- pom.xml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pom.xml b/pom.xml index 8b8fdc383..6900b6c13 100644 --- a/pom.xml +++ b/pom.xml @@ -98,6 +98,10 @@ 1.8 1.8 + + -Xlint:all + -Werror + From b93cd07b9589fd8c739dff96ad3354c10720275b Mon Sep 17 00:00:00 2001 From: John Napier Date: Thu, 1 Jun 2017 18:34:31 -0500 Subject: [PATCH 21/24] Fixing typo in README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 31c2fa53e..346df8ee3 100644 --- a/README.md +++ b/README.md @@ -132,7 +132,7 @@ And have fun with 3s: ```Java Iterable> multiplesOf3InGroupsOf3 = - take(10, inGroupsOf(3, unfoldr(x -> Optional.of(tuple(x * 3, x + 1)), 1))); + take(3, inGroupsOf(3, unfoldr(x -> Optional.of(tuple(x * 3, x + 1)), 1))); //-> [[3, 6, 9], [12, 15, 18], [21, 24, 27]] ``` From 5ca5a9fcc67ef39688205dab75a2952263a484e3 Mon Sep 17 00:00:00 2001 From: jnape Date: Fri, 2 Jun 2017 14:25:44 -0400 Subject: [PATCH 22/24] Lens is a profunctor via mapS and mapT --- .../com/jnape/palatable/lambda/functor/Profunctor.java | 2 ++ src/main/java/com/jnape/palatable/lambda/lens/Lens.java | 8 +++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/jnape/palatable/lambda/functor/Profunctor.java b/src/main/java/com/jnape/palatable/lambda/functor/Profunctor.java index 968c16fd7..d20045079 100644 --- a/src/main/java/com/jnape/palatable/lambda/functor/Profunctor.java +++ b/src/main/java/com/jnape/palatable/lambda/functor/Profunctor.java @@ -1,6 +1,7 @@ package com.jnape.palatable.lambda.functor; import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.lens.Lens; import java.util.function.Function; @@ -18,6 +19,7 @@ * @see Functor * @see Bifunctor * @see Fn1 + * @see Lens */ @FunctionalInterface public interface Profunctor { diff --git a/src/main/java/com/jnape/palatable/lambda/lens/Lens.java b/src/main/java/com/jnape/palatable/lambda/lens/Lens.java index bfdc5cdf8..dca9a2542 100644 --- a/src/main/java/com/jnape/palatable/lambda/lens/Lens.java +++ b/src/main/java/com/jnape/palatable/lambda/lens/Lens.java @@ -3,6 +3,7 @@ import com.jnape.palatable.lambda.functions.Fn2; import com.jnape.palatable.lambda.functor.Applicative; import com.jnape.palatable.lambda.functor.Functor; +import com.jnape.palatable.lambda.functor.Profunctor; import java.util.function.BiFunction; import java.util.function.Function; @@ -133,7 +134,7 @@ * @param the type of the "smaller" update value */ @FunctionalInterface -public interface Lens extends Applicative> { +public interface Lens extends Applicative>, Profunctor> { , FB extends Functor> FT apply( Function fn, S s); @@ -179,6 +180,11 @@ default Lens discardR(Applicative> appB) { return Applicative.super.discardR(appB).coerce(); } + @Override + default Lens diMap(Function lFn, Function rFn) { + return mapS(lFn).mapT(rFn); + } + /** * Contravariantly map S to R, yielding a new lens. * From 891e750ec85037c0657c42cf89f0ebe45b0b8eb1 Mon Sep 17 00:00:00 2001 From: jnape Date: Fri, 2 Jun 2017 13:33:43 -0400 Subject: [PATCH 23/24] Adding treatment for Semigroup, Monoid, Functor, Applicative, and Traversable to README --- README.md | 118 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 115 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 346df8ee3..5fcb5e56b 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,13 @@ Functional patterns for Java 8 - [Background](#background) - [Installation](#installation) - [Examples](#examples) + - [Semigroups](#semigroups) + - [Monoids](#monoids) + - [Functors](#functors) + - [Bifunctors](#bifunctors) + - [Profunctors](#profunctors) + - [Applicatives](#applicatives) + - [Traversables](#traversables) - [ADTs](#adts) - [HLists](#hlists) - [Tuples](#tuples) @@ -48,14 +55,14 @@ Add the following dependency to your: com.jnape.palatable lambda - 1.5.6 + 1.6.0 ``` `build.gradle` ([Gradle](https://docs.gradle.org/current/userguide/dependency_management.html)): ```gradle -compile group: 'com.jnape.palatable', name: 'lambda', version: '1.5.6' +compile group: 'com.jnape.palatable', name: 'lambda', version: '1.6.0' ``` Examples @@ -138,10 +145,115 @@ Iterable> multiplesOf3InGroupsOf3 = Check out the [tests](https://github.com/palatable/lambda/tree/master/src/test/java/com/jnape/palatable/lambda/functions/builtin) or [javadoc](http://palatable.github.io/lambda/javadoc/) for more examples. +Semigroups +---- + +[Semigroups](https://en.wikipedia.org/wiki/Semigroup) are supported via `Semigroup`, a subtype of `Fn2`, and add left and right folds over an `Iterable`. + +Lambda ships some default logical semigroups for lambda types and core JDK types. Common examples are: + +- `AddAll` for concatenating two `Collection`s +- `Collapse` for collapsing two `Tuple2`s together +- `Merge` for merging two `Either`s using left-biasing semantics + +Check out the [semigroup](https://palatable.github.io/lambda/javadoc/com/jnape/palatable/lambda/semigroup/builtin/package-summary.html) package for more examples. + +Monoids +---- + +[Monoids](https://en.wikipedia.org/wiki/Monoid) are supported via `Monoid`, a subtype of `Semigroup` with an `A #identity()` method, and add left and right reduces over an `Iterable`. + +Some commonly used lambda monoid implementations include: + +- `Present` for merging together two `Optional`s +- `Join` for joining two `String`s +- `And` for logical conjunction of two `Boolean`s +- `Or` for logical disjunction of two `Boolean`s + +Additionally, instances of `Monoid` can be trivially synthesized from instances of `Semigroup` via the `Monoid#monoid` static factory method, taking the `Semigroup` and the identity element `A` or a supplier of the identity element `Supplier`. + +Check out the [monoid](https://palatable.github.io/lambda/javadoc/com/jnape/palatable/lambda/monoid/builtin/package-summary.html) package for more examples. + +Functors +---- + +Functors are implemented via the `Functor` interface, and are sub-typed by every function type that lambda exports, as well as many of the [ADTs](#adts). + +Examples of functors include: + +- `Fn*`, `Semigroup`, and `Monoid` +- `SingletonHList` and `Tuple*` +- `Choice*` +- `Either` +- `Const`, `Identity`, and `Compose` +- `Lens` + +Implementing `Functor` is as simple as providing a definition for the covariant mapping function `#fmap` (ideally satisfying the [two laws](https://hackage.haskell.org/package/base-4.9.1.0/docs/Data-Functor.html)). + +### Bifunctors + +Bifunctors -- functors that support two parameters that can be covariantly mapped over -- are implemented via the `Bifunctor` interface. + +Examples of bifunctors include: + +- `Tuple*` +- `Choice*` +- `Either` +- `Const` + +Implementing `Bifunctor` requires implementing *either* `biMapL` and `biMapR` *or* `biMap`. As with `Functor`, there are a [few laws](https://hackage.haskell.org/package/base-4.9.1.0/docs/Data-Bifunctor.html) that well-behaved instances of `Bifunctor` should adhere to. + +### Profunctors + +Profunctors -- functors that support one parameter that can be mapped over contravariantly, and a second parameter that can be mapped over covariantly -- are implemented via the `Profunctor` interface. + + Examples of profunctors include: + + - `Fn*` + - `Lens` + +Implementing `Profunctor` requires implementing *either* `diMapL` and `diMapR` *or* `diMap`. As with `Functor` and `Bifunctor`, there are [some laws](https://hackage.haskell.org/package/profunctors-5.2/docs/Data-Profunctor.html) that well behaved instances of `Profunctor` should adhere to. + +### Applicatives + +Applicative functors -- functors that can be applied together with a 2-arity or higher function -- are implemented via the `Applicative` interface. + +Examples of applicative functors include: + +- `Fn*`, `Semigroup`, and `Monoid` +- `SingletonHList` and `Tuple*` +- `Choice*` +- `Either` +- `Const`, `Identity`, and `Compose` +- `Lens` + +In addition to implementing `fmap` from `Functor`, implementing an applicative functor involves providing two methods: `pure`, a method that lifts a value into the functor; and `zip`, a method that applies a lifted function to a lifted value, returning a new lifted value. As usual, there are [some laws](https://hackage.haskell.org/package/base-4.9.1.0/docs/Control-Applicative.html) that should be adhered to. + +For example, imagine we have an `Fn2` we'll call `join` that simply joins two strings. If we wanted to join the `String`s wrapped inside two `Either` instances, without `Applicative`, we would at best only be able to `fmap` one while `fmap`ping the other, resulting in an `Either>`. + +With `Applicative`, however, we can `fmap(join)` the first `Either`, resulting in an `Either>`, and then `zip` the second `Either`, producing another `Either`. In this way, `Applicative` can be thought of as facilitating `fmap`ping a functor with a multi-arity function. + +### Traversables + +Traversable functors -- functors that can be "traversed from left to right" -- are implemented via the `Traversable` interface. + +Examples of traversable functors include: + +- `SingletonHList` and `Tuple*` +- `Choice*` +- `Either` +- `Const` and `Identity` +- `TraversableIterable` for wrapping `Iterable` in an instance of `Traversable` +- `TraversableOptional` for wrapping `Optional` in an instance of `Traversable` + +In addition to implementing `fmap` from `Functor`, implementing a traversable functor involves providing an implementation of `traverse`. + +As always, there are [some laws](https://hackage.haskell.org/package/base-4.9.1.0/docs/Data-Traversable.html) that should be observed. + ADTs ---- -In addition to the functions above, lambda also supports a few first-class [algebraic data types](https://www.wikiwand.com/en/Algebraic_data_type). +Lambda also supports a few first-class [algebraic data types](https://www.wikiwand.com/en/Algebraic_data_type). ### Heterogeneous Lists (HLists) From c9f5cc1a5e176f936adaa0e693bae9bd20fdd8b9 Mon Sep 17 00:00:00 2001 From: jnape Date: Sun, 4 Jun 2017 16:48:06 -0400 Subject: [PATCH 24/24] [maven-release-plugin] prepare release lambda-1.6.0 --- pom.xml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/pom.xml b/pom.xml index 6900b6c13..24aebdc36 100644 --- a/pom.xml +++ b/pom.xml @@ -1,6 +1,5 @@ - + 4.0.0 @@ -10,7 +9,7 @@ lambda - 1.5.7-SNAPSHOT + 1.6.0 jar Lambda