result type
+ * @return the result of applying the appropriate morphism from whichever type is represented by this coproduct to R
+ */
+ R match(Function super A, ? extends R> aFn, Function super B, ? extends R> bFn);
+
+ /**
+ * Diverge this coproduct by introducing another possible type that it could represent.
+ *
+ * It's important to understand that this does not alter the essential value represented by this coproduct: if the
+ * value was an A before divergence, it is still an A; likewise with B.
+ *
+ * The purpose of this operation is to allow the use of a more convergent coproduct with a more divergent one; that
+ * is, if a CoProduct3<String, Integer, Boolean> is expected, a CoProduct2<String,
+ * Integer> should suffice.
+ *
+ * Generally, we use inheritance to make this a non-issue; however, with coproducts of differing magnitudes, we
+ * cannot guarantee variance compatibility in one direction conveniently at construction time, and in the other
+ * direction, at all. A {@link CoProduct2} could not be a {@link CoProduct3} without specifying all type parameters
+ * that are possible for a {@link CoProduct3} - more specifically, the third possible type - which is not
+ * necessarily known at construction time, or even useful if never used in the context of a {@link CoProduct3}. The
+ * inverse inheritance relationship - {@link CoProduct3} < {@link CoProduct2} - is inherently unsound, as a
+ * {@link CoProduct3} cannot correctly implement {@link CoProduct2#match}, given that the third type C
+ * is always possible.
+ *
+ * For this reason, there is a diverge method supported between all CoProduct types of
+ * single magnitude difference.
+ *
+ * @param the additional possible type of this coproduct
+ * @return a Coproduct3<A, B, C>
+ */
+ default CoProduct3 diverge() {
+ return match(CoProduct3::a, CoProduct3::b);
+ }
+
+ @Override
+ default CoProduct2 fmap(Function super B, ? extends C> fn) {
+ return biMapR(fn);
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ default CoProduct2 biMapL(Function super A, ? extends C> fn) {
+ return (CoProduct2) Bifunctor.super.biMapL(fn);
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ default CoProduct2 biMapR(Function super B, ? extends C> fn) {
+ return (CoProduct2) Bifunctor.super.biMapR(fn);
+ }
+
+ @Override
+ default CoProduct2 biMap(Function super A, ? extends C> lFn,
+ Function super B, ? extends D> rFn) {
+ return match(a -> a(lFn.apply(a)), b -> b(rFn.apply(b)));
+ }
+
+ /**
+ * Static factory method for wrapping a value of type A in a {@link CoProduct2}.
+ *
+ * @param a the value
+ * @param a type parameter representing the first possible type of this coproduct
+ * @param a type parameter representing the second possible type of this coproduct
+ * @return the wrapped value as a CoProduct2<A, B>
+ */
+ static CoProduct2 a(A a) {
+ class _A implements CoProduct2 {
+
+ private final A a;
+
+ private _A(A a) {
+ this.a = a;
+ }
+
+ @Override
+ public R match(Function super A, ? extends R> aFn, Function super B, ? extends R> bFn) {
+ return aFn.apply(a);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other instanceof _A
+ && Objects.equals(a, ((_A) other).a);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(a);
+ }
+
+ @Override
+ public String toString() {
+ return "CoProduct2{" +
+ "a=" + a +
+ '}';
+ }
+ }
+
+ return new _A(a);
+ }
+
+ /**
+ * Static factory method for wrapping a value of type B in a {@link CoProduct2}.
+ *
+ * @param b the value
+ * @param a type parameter representing the first possible type of this coproduct
+ * @param a type parameter representing the second possible type of this coproduct
+ * @return the wrapped value as a CoProduct2<A, B>
+ */
+ static CoProduct2 b(B b) {
+ class _B implements CoProduct2 {
+
+ private final B b;
+
+ private _B(B b) {
+ this.b = b;
+ }
+
+ @Override
+ public R match(Function super A, ? extends R> aFn, Function super B, ? extends R> bFn) {
+ return bFn.apply(b);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other instanceof _B
+ && Objects.equals(b, ((_B) other).b);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(b);
+ }
+
+ @Override
+ public String toString() {
+ return "CoProduct2{" +
+ "b=" + b +
+ '}';
+ }
+ }
+ return new _B(b);
+ }
+}
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
new file mode 100644
index 000000000..2dc66cede
--- /dev/null
+++ b/src/main/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct3.java
@@ -0,0 +1,205 @@
+package com.jnape.palatable.lambda.adt.coproduct;
+
+import com.jnape.palatable.lambda.functor.Bifunctor;
+import com.jnape.palatable.lambda.functor.Functor;
+
+import java.util.Objects;
+import java.util.function.Function;
+
+/**
+ * A generalization of the coproduct of three types A, B, and C.
+ *
+ * @param a type parameter representing the first possible type of this coproduct
+ * @param a type parameter representing the second possible type of this coproduct
+ * @param a type parameter representing the third possible type of this coproduct
+ * @see CoProduct2
+ */
+@FunctionalInterface
+public interface CoProduct3 extends Functor, Bifunctor {
+
+ /**
+ * Type-safe convergence requiring a match against all potential types.
+ *
+ * @param aFn morphism A -> R
+ * @param bFn morphism B -> R
+ * @param cFn morphism C -> R
+ * @param result type
+ * @return the result of applying the appropriate morphism from whichever type is represented by this coproduct to R
+ * @see CoProduct2#match(Function, Function)
+ */
+ R match(Function super A, ? extends R> aFn, Function super B, ? extends R> bFn,
+ Function super C, ? extends R> cFn);
+
+
+ /**
+ * Diverge this coproduct by introducing another possible type that it could represent.
+ *
+ * @param the additional possible type of this coproduct
+ * @return a Coproduct4<A, B, C, D>
+ * @see CoProduct2#diverge()
+ */
+ default CoProduct4 diverge() {
+ return match(CoProduct4::a, CoProduct4::b, CoProduct4::c);
+ }
+
+ @Override
+ default CoProduct3 fmap(Function super C, ? extends D> fn) {
+ return biMapR(fn);
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ default CoProduct3 biMapL(Function super B, ? extends D> fn) {
+ return (CoProduct3) Bifunctor.super.biMapL(fn);
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ default CoProduct3 biMapR(Function super C, ? extends D> fn) {
+ return (CoProduct3) Bifunctor.super.biMapR(fn);
+ }
+
+ @Override
+ default CoProduct3 biMap(Function super B, ? extends D> lFn,
+ Function super C, ? extends E> rFn) {
+ return match(CoProduct3::a, b -> b(lFn.apply(b)), c -> c(rFn.apply(c)));
+ }
+
+ /**
+ * Static factory method for wrapping a value of type A in a {@link CoProduct3}.
+ *
+ * @param a the value
+ * @param a type parameter representing the first possible type of this coproduct
+ * @param a type parameter representing the second possible type of this coproduct
+ * @param a type parameter representing the third possible type of this coproduct
+ * @return the wrapped value as a CoProduct3<A, B, C>
+ */
+ static CoProduct3 a(A a) {
+ class _A implements CoProduct3 {
+
+ private final A a;
+
+ private _A(A a) {
+ this.a = a;
+ }
+
+ @Override
+ public R match(Function super A, ? extends R> aFn, Function super B, ? extends R> bFn,
+ Function super C, ? extends R> cFn) {
+ return aFn.apply(a);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other instanceof _A
+ && Objects.equals(a, ((_A) other).a);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(a);
+ }
+
+ @Override
+ public String toString() {
+ return "CoProduct3{" +
+ "a=" + a +
+ '}';
+ }
+ }
+
+ return new _A(a);
+ }
+
+ /**
+ * Static factory method for wrapping a value of type A in a {@link CoProduct3}.
+ *
+ * @param b the value
+ * @param a type parameter representing the first possible type of this coproduct
+ * @param a type parameter representing the second possible type of this coproduct
+ * @param a type parameter representing the third possible type of this coproduct
+ * @return the wrapped value as a CoProduct3<A, B, C>
+ */
+ static CoProduct3 b(B b) {
+ class _B implements CoProduct3 {
+
+ private final B b;
+
+ private _B(B b) {
+ this.b = b;
+ }
+
+ @Override
+ public R match(Function super A, ? extends R> aFn, Function super B, ? extends R> bFn,
+ Function super C, ? extends R> cFn) {
+ return bFn.apply(b);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other instanceof _B
+ && Objects.equals(b, ((_B) other).b);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(b);
+ }
+
+ @Override
+ public String toString() {
+ return "CoProduct3{" +
+ "b=" + b +
+ '}';
+ }
+ }
+
+ return new _B(b);
+ }
+
+ /**
+ * Static factory method for wrapping a value of type A in a {@link CoProduct3}.
+ *
+ * @param c the value
+ * @param a type parameter representing the first possible type of this coproduct
+ * @param a type parameter representing the second possible type of this coproduct
+ * @param a type parameter representing the third possible type of this coproduct
+ * @return the wrapped value as a CoProduct3<A, B, C>
+ */
+ static CoProduct3 c(C c) {
+ class _C implements CoProduct3 {
+
+ private final C c;
+
+ private _C(C c) {
+ this.c = c;
+ }
+
+ @Override
+ public R match(Function super A, ? extends R> aFn, Function super B, ? extends R> bFn,
+ Function super C, ? extends R> cFn) {
+ return cFn.apply(c);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other instanceof _C
+ && Objects.equals(c, ((_C) other).c);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(c);
+ }
+
+ @Override
+ public String toString() {
+ return "CoProduct3{" +
+ "c=" + c +
+ '}';
+ }
+ }
+
+ return new _C(c);
+ }
+}
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
new file mode 100644
index 000000000..59f8f0043
--- /dev/null
+++ b/src/main/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct4.java
@@ -0,0 +1,256 @@
+package com.jnape.palatable.lambda.adt.coproduct;
+
+import com.jnape.palatable.lambda.functor.Bifunctor;
+import com.jnape.palatable.lambda.functor.Functor;
+
+import java.util.Objects;
+import java.util.function.Function;
+
+/**
+ * A generalization of the coproduct of four types A, B, C, and D.
+ *
+ * @param a type parameter representing the first possible type of this coproduct
+ * @param a type parameter representing the second possible type of this coproduct
+ * @param a type parameter representing the third possible type of this coproduct
+ * @param a type parameter representing the fourth possible type of this coproduct
+ * @see CoProduct2
+ */
+@FunctionalInterface
+public interface CoProduct4 extends Functor, Bifunctor {
+
+ /**
+ * Type-safe convergence requiring a match against all potential types.
+ *
+ * @param aFn morphism A -> R
+ * @param bFn morphism B -> R
+ * @param cFn morphism C -> R
+ * @param dFn morphism D -> R
+ * @param result type
+ * @return the result of applying the appropriate morphism from whichever type is represented by this coproduct to R
+ * @see CoProduct2#match(Function, Function)
+ */
+ R match(Function super A, ? extends R> aFn,
+ Function super B, ? extends R> bFn,
+ Function super C, ? extends R> cFn,
+ Function super D, ? extends R> dFn);
+
+ /**
+ * Diverge this coproduct by introducing another possible type that it could represent.
+ *
+ * @param the additional possible type of this coproduct
+ * @return a Coproduct5<A, B, C, D, E>
+ * @see CoProduct2#diverge()
+ */
+ default CoProduct5 diverge() {
+ return match(CoProduct5::a, CoProduct5::b, CoProduct5::c, CoProduct5::d);
+ }
+
+ @Override
+ default CoProduct4 fmap(Function super D, ? extends E> fn) {
+ return biMapR(fn);
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ default CoProduct4 biMapL(Function super C, ? extends E> fn) {
+ return (CoProduct4) Bifunctor.super.biMapL(fn);
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ default CoProduct4 biMapR(Function super D, ? extends E> fn) {
+ return (CoProduct4) Bifunctor.super.biMapR(fn);
+ }
+
+ @Override
+ default CoProduct4 biMap(Function super C, ? extends E> lFn,
+ Function super D, ? extends F> rFn) {
+ return match(CoProduct4::a, CoProduct4::b, c -> c(lFn.apply(c)), d -> d(rFn.apply(d)));
+ }
+
+ /**
+ * Static factory method for wrapping a value of type A in a {@link CoProduct4}.
+ *
+ * @param a the value
+ * @param a type parameter representing the first possible type of this coproduct
+ * @param a type parameter representing the second possible type of this coproduct
+ * @param a type parameter representing the third possible type of this coproduct
+ * @param a type parameter representing the fourth possible type of this coproduct
+ * @return the wrapped value as a CoProduct4<A, B, C, D>
+ */
+ static CoProduct4 a(A a) {
+ class _A implements CoProduct4 {
+
+ private final A a;
+
+ private _A(A a) {
+ this.a = a;
+ }
+
+ @Override
+ public R match(Function super A, ? extends R> aFn, Function super B, ? extends R> bFn,
+ Function super C, ? extends R> cFn, Function super D, ? extends R> dFn) {
+ return aFn.apply(a);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other instanceof _A
+ && Objects.equals(a, ((_A) other).a);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(a);
+ }
+
+ @Override
+ public String toString() {
+ return "CoProduct4{" +
+ "a=" + a +
+ '}';
+ }
+ }
+
+ return new _A(a);
+ }
+
+ /**
+ * Static factory method for wrapping a value of type B in a {@link CoProduct4}.
+ *
+ * @param b the value
+ * @param a type parameter representing the first possible type of this coproduct
+ * @param a type parameter representing the second possible type of this coproduct
+ * @param a type parameter representing the third possible type of this coproduct
+ * @param a type parameter representing the fourth possible type of this coproduct
+ * @return the wrapped value as a CoProduct4<A, B, C, D>
+ */
+ static CoProduct4 b(B b) {
+ class _B implements CoProduct4 {
+
+ private final B b;
+
+ private _B(B b) {
+ this.b = b;
+ }
+
+ @Override
+ public R match(Function super A, ? extends R> aFn, Function super B, ? extends R> bFn,
+ Function super C, ? extends R> cFn, Function super D, ? extends R> dFn) {
+ return bFn.apply(b);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other instanceof _B
+ && Objects.equals(b, ((_B) other).b);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(b);
+ }
+
+ @Override
+ public String toString() {
+ return "CoProduct4{" +
+ "b=" + b +
+ '}';
+ }
+ }
+ return new _B(b);
+ }
+
+ /**
+ * Static factory method for wrapping a value of type C in a {@link CoProduct4}.
+ *
+ * @param c the value
+ * @param a type parameter representing the first possible type of this coproduct
+ * @param a type parameter representing the second possible type of this coproduct
+ * @param a type parameter representing the third possible type of this coproduct
+ * @param a type parameter representing the fourth possible type of this coproduct
+ * @return the wrapped value as a CoProduct4<A, B, C, D>
+ */
+ static CoProduct4 c(C c) {
+ class _C implements CoProduct4 {
+
+ private final C c;
+
+ private _C(C c) {
+ this.c = c;
+ }
+
+ @Override
+ public R match(Function super A, ? extends R> aFn, Function super B, ? extends R> bFn,
+ Function super C, ? extends R> cFn, Function super D, ? extends R> dFn) {
+ return cFn.apply(c);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other instanceof _C
+ && Objects.equals(c, ((_C) other).c);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(c);
+ }
+
+ @Override
+ public String toString() {
+ return "CoProduct4{" +
+ "c=" + c +
+ '}';
+ }
+ }
+ return new _C(c);
+ }
+
+ /**
+ * Static factory method for wrapping a value of type D in a {@link CoProduct4}.
+ *
+ * @param d the value
+ * @param a type parameter representing the first possible type of this coproduct
+ * @param a type parameter representing the second possible type of this coproduct
+ * @param a type parameter representing the third possible type of this coproduct
+ * @param a type parameter representing the fourth possible type of this coproduct
+ * @return the wrapped value as a CoProduct4<A, B, C, D>
+ */
+ static CoProduct4 d(D d) {
+ class _D implements CoProduct4 {
+
+ private final D d;
+
+ private _D(D d) {
+ this.d = d;
+ }
+
+ @Override
+ public R match(Function super A, ? extends R> aFn, Function super B, ? extends R> bFn,
+ Function super C, ? extends R> cFn, Function super D, ? extends R> dFn) {
+ return dFn.apply(d);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other instanceof _D
+ && Objects.equals(d, ((_D) other).d);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(d);
+ }
+
+ @Override
+ public String toString() {
+ return "CoProduct4{" +
+ "d=" + d +
+ '}';
+ }
+ }
+
+ return new _D(d);
+ }
+}
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
new file mode 100644
index 000000000..2594f16df
--- /dev/null
+++ b/src/main/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct5.java
@@ -0,0 +1,308 @@
+package com.jnape.palatable.lambda.adt.coproduct;
+
+import com.jnape.palatable.lambda.functor.Bifunctor;
+import com.jnape.palatable.lambda.functor.Functor;
+
+import java.util.Objects;
+import java.util.function.Function;
+
+/**
+ * A generalization of the coproduct of five types A, B, C, D, and
+ * E.
+ *
+ * @param a type parameter representing the first possible type of this coproduct
+ * @param a type parameter representing the second possible type of this coproduct
+ * @param a type parameter representing the third possible type of this coproduct
+ * @param a type parameter representing the fourth possible type of this coproduct
+ * @param a type parameter representing the fifth possible type of this coproduct
+ * @see CoProduct2
+ */
+@FunctionalInterface
+public interface CoProduct5 extends Functor, Bifunctor {
+
+ /**
+ * Type-safe convergence requiring a match against all potential types.
+ *
+ * @param aFn morphism A -> R
+ * @param bFn morphism B -> R
+ * @param cFn morphism C -> R
+ * @param dFn morphism D -> R
+ * @param eFn morphism E -> R
+ * @param result type
+ * @return the result of applying the appropriate morphism from whichever type is represented by this coproduct to R
+ * @see CoProduct2#match(Function, Function)
+ */
+ R match(Function super A, ? extends R> aFn,
+ Function super B, ? extends R> bFn,
+ Function super C, ? extends R> cFn,
+ Function super D, ? extends R> dFn,
+ Function super E, ? extends R> eFn);
+
+ @Override
+ default CoProduct5 fmap(Function super E, ? extends F> fn) {
+ return biMapR(fn);
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ default CoProduct5 biMapL(Function super D, ? extends F> fn) {
+ return (CoProduct5) Bifunctor.super.biMapL(fn);
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ default CoProduct5 biMapR(Function super E, ? extends F> fn) {
+ return (CoProduct5) Bifunctor.super.biMapR(fn);
+ }
+
+ @Override
+ default CoProduct5 biMap(Function super D, ? extends F> lFn,
+ Function super E, ? extends G> rFn) {
+ return match(CoProduct5::a, CoProduct5::b, CoProduct5::c, d -> d(lFn.apply(d)), e -> e(rFn.apply(e)));
+ }
+
+ /**
+ * Static factory method for wrapping a value of type A in a {@link CoProduct5}.
+ *
+ * @param a the value
+ * @param a type parameter representing the first possible type of this coproduct
+ * @param a type parameter representing the second possible type of this coproduct
+ * @param a type parameter representing the third possible type of this coproduct
+ * @param a type parameter representing the fourth possible type of this coproduct
+ * @param a type parameter representing the fifth possible type of this coproduct
+ * @return the wrapped value as a CoProduct5<A, B, C, D, E>
+ */
+ static CoProduct5 a(A a) {
+ class _A implements CoProduct5 {
+
+ private final A a;
+
+ private _A(A a) {
+ this.a = a;
+ }
+
+ @Override
+ public R match(Function super A, ? extends R> aFn, Function super B, ? extends R> bFn,
+ Function super C, ? extends R> cFn, Function super D, ? extends R> dFn,
+ Function super E, ? extends R> eFn) {
+ return aFn.apply(a);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other instanceof _A
+ && Objects.equals(a, ((_A) other).a);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(a);
+ }
+
+ @Override
+ public String toString() {
+ return "CoProduct5{" +
+ "a=" + a +
+ '}';
+ }
+ }
+
+ return new _A(a);
+ }
+
+ /**
+ * Static factory method for wrapping a value of type B in a {@link CoProduct5}.
+ *
+ * @param b the value
+ * @param a type parameter representing the first possible type of this coproduct
+ * @param a type parameter representing the second possible type of this coproduct
+ * @param a type parameter representing the third possible type of this coproduct
+ * @param a type parameter representing the fourth possible type of this coproduct
+ * @param a type parameter representing the fifth possible type of this coproduct
+ * @return the wrapped value as a CoProduct5<A, B, C, D, E>
+ */
+ static CoProduct5 b(B b) {
+ class _B implements CoProduct5 {
+
+ private final B b;
+
+ private _B(B b) {
+ this.b = b;
+ }
+
+ @Override
+ public R match(Function super A, ? extends R> aFn, Function super B, ? extends R> bFn,
+ Function super C, ? extends R> cFn, Function super D, ? extends R> dFn,
+ Function super E, ? extends R> eFn) {
+ return bFn.apply(b);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other instanceof _B
+ && Objects.equals(b, ((_B) other).b);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(b);
+ }
+
+ @Override
+ public String toString() {
+ return "CoProduct5{" +
+ "b=" + b +
+ '}';
+ }
+ }
+
+ return new _B(b);
+ }
+
+ /**
+ * Static factory method for wrapping a value of type C in a {@link CoProduct5}.
+ *
+ * @param c the value
+ * @param a type parameter representing the first possible type of this coproduct
+ * @param a type parameter representing the second possible type of this coproduct
+ * @param a type parameter representing the third possible type of this coproduct
+ * @param a type parameter representing the fourth possible type of this coproduct
+ * @param a type parameter representing the fifth possible type of this coproduct
+ * @return the wrapped value as a CoProduct5<A, B, C, D, E>
+ */
+ static CoProduct5 c(C c) {
+ class _C implements CoProduct5 {
+
+ private final C c;
+
+ private _C(C c) {
+ this.c = c;
+ }
+
+ @Override
+ public R match(Function super A, ? extends R> aFn, Function super B, ? extends R> bFn,
+ Function super C, ? extends R> cFn, Function super D, ? extends R> dFn,
+ Function super E, ? extends R> eFn) {
+ return cFn.apply(c);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other instanceof _C
+ && Objects.equals(c, ((_C) other).c);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(c);
+ }
+
+ @Override
+ public String toString() {
+ return "CoProduct5{" +
+ "c=" + c +
+ '}';
+ }
+ }
+
+ return new _C(c);
+ }
+
+ /**
+ * Static factory method for wrapping a value of type D in a {@link CoProduct5}.
+ *
+ * @param d the value
+ * @param a type parameter representing the first possible type of this coproduct
+ * @param a type parameter representing the second possible type of this coproduct
+ * @param a type parameter representing the third possible type of this coproduct
+ * @param a type parameter representing the fourth possible type of this coproduct
+ * @param a type parameter representing the fifth possible type of this coproduct
+ * @return the wrapped value as a CoProduct5<A, B, C, D, E>
+ */
+ static CoProduct5 d(D d) {
+ class _D implements CoProduct5 {
+
+ private final D d;
+
+ private _D(D d) {
+ this.d = d;
+ }
+
+ @Override
+ public R match(Function super A, ? extends R> aFn, Function super B, ? extends R> bFn,
+ Function super C, ? extends R> cFn, Function super D, ? extends R> dFn,
+ Function super E, ? extends R> eFn) {
+ return dFn.apply(d);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other instanceof _D
+ && Objects.equals(d, ((_D) other).d);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(d);
+ }
+
+ @Override
+ public String toString() {
+ return "CoProduct5{" +
+ "d=" + d +
+ '}';
+ }
+ }
+
+ return new _D(d);
+ }
+
+ /**
+ * Static factory method for wrapping a value of type E in a {@link CoProduct5}.
+ *
+ * @param e the value
+ * @param a type parameter representing the first possible type of this coproduct
+ * @param a type parameter representing the second possible type of this coproduct
+ * @param a type parameter representing the third possible type of this coproduct
+ * @param a type parameter representing the fourth possible type of this coproduct
+ * @param a type parameter representing the fifth possible type of this coproduct
+ * @return the wrapped value as a CoProduct5<A, B, C, D, E>
+ */
+ static CoProduct5 e(E e) {
+ class _E implements CoProduct5 {
+
+ private final E e;
+
+ private _E(E e) {
+ this.e = e;
+ }
+
+ @Override
+ public R match(Function super A, ? extends R> aFn, Function super B, ? extends R> bFn,
+ Function super C, ? extends R> cFn, Function super D, ? extends R> dFn,
+ Function super E, ? extends R> eFn) {
+ return eFn.apply(e);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other instanceof _E
+ && Objects.equals(e, ((_E) other).e);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(e);
+ }
+
+ @Override
+ public String toString() {
+ return "CoProduct5{" +
+ "e=" + e +
+ '}';
+ }
+ }
+
+ return new _E(e);
+ }
+}
diff --git a/src/main/java/com/jnape/palatable/lambda/adt/hlist/Index.java b/src/main/java/com/jnape/palatable/lambda/adt/hlist/Index.java
index 701cae229..12f1eeebd 100644
--- a/src/main/java/com/jnape/palatable/lambda/adt/hlist/Index.java
+++ b/src/main/java/com/jnape/palatable/lambda/adt/hlist/Index.java
@@ -38,6 +38,7 @@ private Index() {
*
* @param newElement the new value
* @param hList the HList
+ * @param the inferred tail type of the HList
* @return the updated HList
*/
public abstract L set(Target newElement, L hList);
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
new file mode 100644
index 000000000..57056430b
--- /dev/null
+++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Partition.java
@@ -0,0 +1,61 @@
+package com.jnape.palatable.lambda.functions.builtin.fn2;
+
+import com.jnape.palatable.lambda.adt.Either;
+import com.jnape.palatable.lambda.adt.hlist.Tuple2;
+import com.jnape.palatable.lambda.functions.Fn1;
+import com.jnape.palatable.lambda.functions.Fn2;
+
+import java.util.Optional;
+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.
+ *
+ * @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
+ */
+public final class Partition implements Fn2>, Iterable, Tuple2, Iterable>> {
+
+ private Partition() {
+ }
+
+ @Override
+ public Tuple2, Iterable> apply(Function super A, ? extends Either> function,
+ Iterable as) {
+ Iterable> eithers = map(function, as);
+
+ Iterable lefts = unwrapRight(map(Either::invert, eithers));
+ Iterable rights = unwrapRight(map(id(), eithers));
+
+ return tuple(lefts, rights);
+ }
+
+ private Iterable unwrapRight(Iterable> eithers) {
+ return map(Optional::get, filter(Optional::isPresent, map(Either::toOptional, eithers)));
+ }
+
+ public static Partition partition() {
+ return new Partition<>();
+ }
+
+ public static Fn1, Tuple2, Iterable>> partition(
+ Function super A, ? extends Either> function) {
+ return Partition.partition().apply(function);
+ }
+
+ public static Tuple2, Iterable> partition(
+ Function super A, ? extends Either> function,
+ Iterable as) {
+ return Partition.partition(function).apply(as);
+ }
+}
diff --git a/src/main/java/com/jnape/palatable/lambda/functions/specialized/checked/CheckedFn1.java b/src/main/java/com/jnape/palatable/lambda/functions/specialized/checked/CheckedFn1.java
index 15c17a796..f91b83aa4 100644
--- a/src/main/java/com/jnape/palatable/lambda/functions/specialized/checked/CheckedFn1.java
+++ b/src/main/java/com/jnape/palatable/lambda/functions/specialized/checked/CheckedFn1.java
@@ -13,7 +13,7 @@
* @see Fn1
*/
@FunctionalInterface
-public interface CheckedFn1 extends Fn1 {
+public interface CheckedFn1 extends Fn1 {
@Override
default B apply(A a) {
@@ -29,7 +29,7 @@ default B apply(A a) {
*
* @param a the argument
* @return the result of the function application
- * @throws Exception any exception thrown by the function application
+ * @throws T any Throwable thrown by the function application
*/
- B checkedApply(A a) throws Exception;
+ B checkedApply(A a) throws T;
}
diff --git a/src/main/java/com/jnape/palatable/lambda/functor/Bifunctor.java b/src/main/java/com/jnape/palatable/lambda/functor/Bifunctor.java
index 496c97021..6ab64dd96 100644
--- a/src/main/java/com/jnape/palatable/lambda/functor/Bifunctor.java
+++ b/src/main/java/com/jnape/palatable/lambda/functor/Bifunctor.java
@@ -49,7 +49,7 @@ default Bifunctor biMapR(Function super B, ? extends C> fn) {
* @param the new right parameter type
* @param lFn the left parameter mapping function
* @param rFn the right parameter mapping function
- * @return a bifunctor over Z (the new left parameter type) and C (the new right parameter type)
+ * @return a bifunctor over C (the new left parameter type) and D (the new right parameter type)
*/
Bifunctor biMap(Function super A, ? extends C> lFn, Function super B, ? extends D> rFn);
}
diff --git a/src/main/java/com/jnape/palatable/lambda/monoid/Monoid.java b/src/main/java/com/jnape/palatable/lambda/monoid/Monoid.java
new file mode 100644
index 000000000..434dafe0f
--- /dev/null
+++ b/src/main/java/com/jnape/palatable/lambda/monoid/Monoid.java
@@ -0,0 +1,64 @@
+package com.jnape.palatable.lambda.monoid;
+
+import com.jnape.palatable.lambda.functions.builtin.fn2.ReduceLeft;
+import com.jnape.palatable.lambda.functions.builtin.fn2.ReduceRight;
+import com.jnape.palatable.lambda.semigroup.Semigroup;
+
+/**
+ * A {@link Monoid} is the pairing of a {@link Semigroup} with an identity element.
+ *
+ * @param the element type this Monoid is formed over
+ */
+public interface Monoid extends Semigroup {
+
+ /**
+ * The identity element of this monoid.
+ *
+ * @return the identity
+ */
+ A identity();
+
+ /**
+ * Catamorphism under this monoid using {@link ReduceLeft}, where the result is the reduction, or, if empty, the
+ * identity of this monoid.
+ *
+ * @param as the elements to reduce
+ * @return the reduction, or {@link Monoid#identity} if empty
+ */
+ default A reduceLeft(Iterable as) {
+ return ReduceLeft.reduceLeft(toBiFunction(), as).orElse(identity());
+ }
+
+ /**
+ * Catamorphism under this monoid using {@link ReduceRight}, where the result is the reduction, or, if empty, the
+ * identity of this monoid.
+ *
+ * @param as an Iterable of elements in this monoid
+ * @return the reduction, or {@link Monoid#identity} if empty
+ */
+ default A reduceRight(Iterable as) {
+ return ReduceRight.reduceRight(toBiFunction(), as).orElse(identity());
+ }
+
+ /**
+ * Promote a {@link Semigroup} to a {@link Monoid} by supplying an identity element.
+ *
+ * @param semigroup the semigroup
+ * @param identity the identity element
+ * @param the element type of this monoid
+ * @return the monoid
+ */
+ static Monoid monoid(Semigroup semigroup, A identity) {
+ return new Monoid() {
+ @Override
+ public A identity() {
+ return identity;
+ }
+
+ @Override
+ public A apply(A x, A y) {
+ return semigroup.apply(x, y);
+ }
+ };
+ }
+}
diff --git a/src/main/java/com/jnape/palatable/lambda/semigroup/Left.java b/src/main/java/com/jnape/palatable/lambda/semigroup/Left.java
new file mode 100644
index 000000000..329bbc723
--- /dev/null
+++ b/src/main/java/com/jnape/palatable/lambda/semigroup/Left.java
@@ -0,0 +1,35 @@
+package com.jnape.palatable.lambda.semigroup;
+
+import com.jnape.palatable.lambda.adt.Either;
+
+import static com.jnape.palatable.lambda.adt.Either.left;
+
+/**
+ * The {@link Semigroup} formed by {@link Either}<L,R> and a Semigroup over
+ * L. The application to two {@link Either} values is right-biased, such that for a given {@link
+ * Either} x and y:
+ *
+ * - if
x is a Right value, the result is x
+ * - if
y is a Right value, the result is y
+ * - if both
x and y are Left values, the result is the application of the x
+ * and y values in terms of the provided semigroup, wrapped in a Left
+ *
+ *
+ * @param the left parameter type
+ * @param the right parameter type
+ */
+public final class Left implements Semigroup> {
+
+ private final Semigroup semigroup;
+
+ public Left(Semigroup semigroup) {
+ this.semigroup = semigroup;
+ }
+
+ @Override
+ public Either apply(Either x, Either y) {
+ return x.flatMap(xL -> y.flatMap(yL -> left(semigroup.apply(xL, yL)),
+ Either::right),
+ Either::right);
+ }
+}
diff --git a/src/main/java/com/jnape/palatable/lambda/semigroup/Present.java b/src/main/java/com/jnape/palatable/lambda/semigroup/Present.java
new file mode 100644
index 000000000..5f234ca42
--- /dev/null
+++ b/src/main/java/com/jnape/palatable/lambda/semigroup/Present.java
@@ -0,0 +1,37 @@
+package com.jnape.palatable.lambda.semigroup;
+
+import com.jnape.palatable.lambda.monoid.Monoid;
+
+import java.util.Optional;
+
+/**
+ * The {@link Semigroup} formed by {@link Optional}<A> and a Semigroup over
+ * A. The application to two {@link Optional} values is empty-biased, such that for a given {@link
+ * Optional} x and y:
+ *
+ * - if
x is Optional.empty() value, the result is x
+ * - if
y is Optional.empty() value, the result is y
+ * - if both
x and y are present, the result is the application of the x and y values in
+ * terms of the provided semigroup, wrapped in a present Optional
+ *
+ *
+ * @param the Optional value parameter type
+ */
+public final class Present implements Monoid> {
+
+ private Semigroup semigroup;
+
+ public Present(Semigroup semigroup) {
+ this.semigroup = semigroup;
+ }
+
+ @Override
+ public Optional identity() {
+ return Optional.empty();
+ }
+
+ @Override
+ public Optional apply(Optional optX, Optional optY) {
+ return optX.flatMap(x -> optY.flatMap(y -> Optional.of(semigroup.apply(x, y))));
+ }
+}
diff --git a/src/main/java/com/jnape/palatable/lambda/semigroup/Right.java b/src/main/java/com/jnape/palatable/lambda/semigroup/Right.java
new file mode 100644
index 000000000..cdeeff324
--- /dev/null
+++ b/src/main/java/com/jnape/palatable/lambda/semigroup/Right.java
@@ -0,0 +1,33 @@
+package com.jnape.palatable.lambda.semigroup;
+
+import com.jnape.palatable.lambda.adt.Either;
+
+import static com.jnape.palatable.lambda.adt.Either.right;
+
+/**
+ * The {@link Semigroup} formed by {@link Either}<L,R> and a Semigroup over
+ * R. The application to two {@link Either} values is left-biased, such that for a given {@link
+ * Either} x and y:
+ *
+ * - if
x is a Left value, the result is x
+ * - if
y is a Left value, the result is y
+ * - if both
x and y are right values, the result is the application of the x
+ * and y values in terms of the provided semigroup, wrapped in a Right
+ *
+ *
+ * @param the left parameter type
+ * @param the right parameter type
+ */
+public final class Right implements Semigroup> {
+
+ private final Semigroup semigroup;
+
+ public Right(Semigroup semigroup) {
+ this.semigroup = semigroup;
+ }
+
+ @Override
+ public Either apply(Either eitherX, Either eitherY) {
+ return eitherX.flatMap(xR -> eitherY.flatMap(yR -> right(semigroup.apply(xR, yR))));
+ }
+}
diff --git a/src/main/java/com/jnape/palatable/lambda/semigroup/Semigroup.java b/src/main/java/com/jnape/palatable/lambda/semigroup/Semigroup.java
new file mode 100644
index 000000000..2fb0330e3
--- /dev/null
+++ b/src/main/java/com/jnape/palatable/lambda/semigroup/Semigroup.java
@@ -0,0 +1,39 @@
+package com.jnape.palatable.lambda.semigroup;
+
+import com.jnape.palatable.lambda.functions.Fn2;
+import com.jnape.palatable.lambda.functions.builtin.fn3.FoldLeft;
+import com.jnape.palatable.lambda.functions.builtin.fn3.FoldRight;
+
+/**
+ * A Semigroup is a closed, associative category. As closure can be implied by the type signature, and
+ * associativity is not enforceable, this is simply represented as a binary operator.
+ *
+ * @param The element type this Semigroup is formed over
+ */
+@FunctionalInterface
+public interface Semigroup extends Fn2 {
+
+ /**
+ * Catamorphism under this semigroup using {@link FoldLeft}, where the binary operator is this semigroup, and the
+ * starting accumulator is provided.
+ *
+ * @param a the starting accumulator
+ * @param as the elements to fold over
+ * @return the folded result
+ */
+ default A foldLeft(A a, Iterable as) {
+ return FoldLeft.foldLeft(toBiFunction(), a, as);
+ }
+
+ /**
+ * Catamorphism under this semigroup using {@link FoldRight}, where the binary operator is this semigroup, and the
+ * starting accumulator is provided.
+ *
+ * @param a the starting accumulator
+ * @param as the elements to fold over
+ * @return the folded result
+ */
+ default A foldRight(A a, Iterable as) {
+ return FoldRight.foldRight(toBiFunction(), a, as);
+ }
+}
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 4e2911dc7..ff5a36684 100644
--- a/src/test/java/com/jnape/palatable/lambda/adt/EitherTest.java
+++ b/src/test/java/com/jnape/palatable/lambda/adt/EitherTest.java
@@ -90,6 +90,15 @@ 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");
@@ -133,7 +142,7 @@ public void fromOptionalMapsOptionalToEither() {
}
@Test
- public void functorialProperties() {
+ public void functorProperties() {
Either left = left("foo");
Either right = right(1);
@@ -142,7 +151,7 @@ public void functorialProperties() {
}
@Test
- public void biFunctorialProperties() {
+ public void bifunctorProperties() {
Either left = left("foo");
Either right = right(1);
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
new file mode 100644
index 000000000..f056ae9f7
--- /dev/null
+++ b/src/test/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct2Test.java
@@ -0,0 +1,45 @@
+package com.jnape.palatable.lambda.adt.coproduct;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import static com.jnape.palatable.lambda.adt.coproduct.CoProduct2.a;
+import static com.jnape.palatable.lambda.adt.coproduct.CoProduct2.b;
+import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id;
+import static org.junit.Assert.assertEquals;
+
+public class CoProduct2Test {
+
+ private CoProduct2 a;
+ private CoProduct2 b;
+
+ @Before
+ public void setUp() {
+ a = a(1);
+ b = b(true);
+ }
+
+ @Test
+ public void match() {
+ assertEquals(1, a.match(id(), id()));
+ assertEquals(true, b.match(id(), id()));
+ }
+
+ @Test
+ public void diverge() {
+ assertEquals(CoProduct3.a(1), a.diverge());
+ assertEquals(CoProduct3.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));
+ 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/coproduct/CoProduct3Test.java b/src/test/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct3Test.java
new file mode 100644
index 000000000..799b641e5
--- /dev/null
+++ b/src/test/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct3Test.java
@@ -0,0 +1,52 @@
+package com.jnape.palatable.lambda.adt.coproduct;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import static com.jnape.palatable.lambda.adt.coproduct.CoProduct3.a;
+import static com.jnape.palatable.lambda.adt.coproduct.CoProduct3.b;
+import static com.jnape.palatable.lambda.adt.coproduct.CoProduct3.c;
+import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id;
+import static org.junit.Assert.assertEquals;
+
+public class CoProduct3Test {
+
+ private CoProduct3 a;
+ private CoProduct3 b;
+ private CoProduct3 c;
+
+ @Before
+ public void setUp() throws Exception {
+ a = a(1);
+ b = b("two");
+ c = c(true);
+ }
+
+ @Test
+ public void match() {
+ assertEquals(1, a.match(id(), id(), id()));
+ assertEquals("two", b.match(id(), id(), id()));
+ assertEquals(true, c.match(id(), id(), id()));
+ }
+
+ @Test
+ public void diverge() {
+ assertEquals(CoProduct4.a(1), a.diverge());
+ assertEquals(CoProduct4.b("two"), b.diverge());
+ assertEquals(CoProduct4.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));
+ 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/coproduct/CoProduct4Test.java b/src/test/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct4Test.java
new file mode 100644
index 000000000..31ac2dd01
--- /dev/null
+++ b/src/test/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct4Test.java
@@ -0,0 +1,59 @@
+package com.jnape.palatable.lambda.adt.coproduct;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import static com.jnape.palatable.lambda.adt.coproduct.CoProduct4.a;
+import static com.jnape.palatable.lambda.adt.coproduct.CoProduct4.b;
+import static com.jnape.palatable.lambda.adt.coproduct.CoProduct4.c;
+import static com.jnape.palatable.lambda.adt.coproduct.CoProduct4.d;
+import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id;
+import static org.junit.Assert.assertEquals;
+
+public class CoProduct4Test {
+
+ private CoProduct4