diff --git a/README.md b/README.md index b44dcd4ef..824187980 100644 --- a/README.md +++ b/README.md @@ -47,14 +47,14 @@ Add the following dependency to your: com.jnape.palatable lambda - 1.5.1 + 1.5.2 ``` `build.gradle` ([Gradle](https://docs.gradle.org/current/userguide/dependency_management.html)): ```gradle -compile group: 'com.jnape.palatable', name: 'lambda', version: '1.5.1' +compile group: 'com.jnape.palatable', name: 'lambda', version: '1.5.2' ``` diff --git a/pom.xml b/pom.xml index 336e797a2..eb7ceb3bd 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ lambda - 1.5.2 + 1.5.3 jar Lambda diff --git a/src/main/java/com/jnape/palatable/lambda/adt/Either.java b/src/main/java/com/jnape/palatable/lambda/adt/Either.java index 81cddcc26..4c6c70d57 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/Either.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/Either.java @@ -1,8 +1,8 @@ package com.jnape.palatable.lambda.adt; +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.Bifunctor; -import com.jnape.palatable.lambda.functor.Functor; import java.util.Objects; import java.util.Optional; @@ -23,7 +23,7 @@ * @param The left parameter type * @param The right parameter type */ -public abstract class Either implements Functor, Bifunctor { +public abstract class Either implements CoProduct2 { private Either() { } @@ -61,16 +61,16 @@ public final L forfeit(Function forfeitFn) { } /** - * Return the wrapped value if this is a right; otherwise, map the wrapped left value to an E and throw + * Return the wrapped value if this is a right; otherwise, map the wrapped left value to a T and throw * it. * - * @param throwableFn a function from L to E - * @param the left parameter type (the throwable exception type) + * @param throwableFn a function from L to T + * @param the left parameter type (the throwable type) * @return the wrapped value if this is a right - * @throws E the result of applying the wrapped left value to throwableFn, if this is a left + * @throws T the result of applying the wrapped left value to throwableFn, if this is a left */ - public final R orThrow(Function throwableFn) throws E { - return match(l -> { + public final R orThrow(Function throwableFn) throws T { + return match((CheckedFn1) l -> { throw throwableFn.apply(l); }, id()); } @@ -121,6 +121,16 @@ 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); + } + /** * Given two binary operators over L and R, merge multiple Either<L, R>s into a single * Either<L, R>. Note that merge biases towards left values; that is, if any left @@ -190,13 +200,13 @@ public final Either fmap(Function fn) { @Override @SuppressWarnings("unchecked") public final Either biMapL(Function fn) { - return (Either) Bifunctor.super.biMapL(fn); + return (Either) CoProduct2.super.biMapL(fn); } @Override @SuppressWarnings("unchecked") public final Either biMapR(Function fn) { - return (Either) Bifunctor.super.biMapR(fn); + return (Either) CoProduct2.super.biMapR(fn); } @Override 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 new file mode 100644 index 000000000..c01d50773 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct2.java @@ -0,0 +1,170 @@ +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 two types A and B. Coproducts represent the disjoint + * union of two or more distinct types, and provides an interface for specifying morphisms from those types to a common + * result type. + *

+ * Learn more about Coproducts. + * + * @param a type parameter representing the first possible type of this coproduct + * @param a type parameter representing the second possible type of this coproduct + */ +@FunctionalInterface +public interface CoProduct2 extends Functor, Bifunctor { + + /** + * Type-safe convergence requiring a match against all potential types. + * + * @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 + */ + R match(Function aFn, Function 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 fn) { + return biMapR(fn); + } + + @Override + @SuppressWarnings("unchecked") + default CoProduct2 biMapL(Function fn) { + return (CoProduct2) Bifunctor.super.biMapL(fn); + } + + @Override + @SuppressWarnings("unchecked") + default CoProduct2 biMapR(Function fn) { + return (CoProduct2) Bifunctor.super.biMapR(fn); + } + + @Override + default CoProduct2 biMap(Function lFn, + Function 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 aFn, Function 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 aFn, Function 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 aFn, Function bFn, + Function 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 fn) { + return biMapR(fn); + } + + @Override + @SuppressWarnings("unchecked") + default CoProduct3 biMapL(Function fn) { + return (CoProduct3) Bifunctor.super.biMapL(fn); + } + + @Override + @SuppressWarnings("unchecked") + default CoProduct3 biMapR(Function fn) { + return (CoProduct3) Bifunctor.super.biMapR(fn); + } + + @Override + default CoProduct3 biMap(Function lFn, + Function 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 aFn, Function bFn, + Function 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 aFn, Function bFn, + Function 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 aFn, Function bFn, + Function 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 aFn, + Function bFn, + Function cFn, + Function 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 fn) { + return biMapR(fn); + } + + @Override + @SuppressWarnings("unchecked") + default CoProduct4 biMapL(Function fn) { + return (CoProduct4) Bifunctor.super.biMapL(fn); + } + + @Override + @SuppressWarnings("unchecked") + default CoProduct4 biMapR(Function fn) { + return (CoProduct4) Bifunctor.super.biMapR(fn); + } + + @Override + default CoProduct4 biMap(Function lFn, + Function 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 aFn, Function bFn, + Function cFn, Function 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 aFn, Function bFn, + Function cFn, Function 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 aFn, Function bFn, + Function cFn, Function 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 aFn, Function bFn, + Function cFn, Function 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 aFn, + Function bFn, + Function cFn, + Function dFn, + Function eFn); + + @Override + default CoProduct5 fmap(Function fn) { + return biMapR(fn); + } + + @Override + @SuppressWarnings("unchecked") + default CoProduct5 biMapL(Function fn) { + return (CoProduct5) Bifunctor.super.biMapL(fn); + } + + @Override + @SuppressWarnings("unchecked") + default CoProduct5 biMapR(Function fn) { + return (CoProduct5) Bifunctor.super.biMapR(fn); + } + + @Override + default CoProduct5 biMap(Function lFn, + Function 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 aFn, Function bFn, + Function cFn, Function dFn, + Function 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 aFn, Function bFn, + Function cFn, Function dFn, + Function 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 aFn, Function bFn, + Function cFn, Function dFn, + Function 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 aFn, Function bFn, + Function cFn, Function dFn, + Function 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 aFn, Function bFn, + Function cFn, Function dFn, + Function 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> 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> function) { + return Partition.partition().apply(function); + } + + public static Tuple2, Iterable> partition( + Function> 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 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 lFn, Function 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: + *

+ * + * @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 a; + private CoProduct4 b; + private CoProduct4 c; + private CoProduct4 d; + + @Before + public void setUp() throws Exception { + a = a(1); + b = b("two"); + c = c(true); + d = d(4D); + } + + @Test + public void match() { + assertEquals(1, a.match(id(), id(), id(), id())); + assertEquals("two", b.match(id(), id(), id(), id())); + assertEquals(true, c.match(id(), id(), id(), id())); + assertEquals(4D, d.match(id(), id(), id(), id())); + } + + @Test + public void diverge() { + assertEquals(CoProduct5.a(1), a.diverge()); + assertEquals(CoProduct5.b("two"), b.diverge()); + assertEquals(CoProduct5.c(true), c.diverge()); + assertEquals(CoProduct5.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)); + 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/coproduct/CoProduct5Test.java b/src/test/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct5Test.java new file mode 100644 index 000000000..6adb56d29 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct5Test.java @@ -0,0 +1,57 @@ +package com.jnape.palatable.lambda.adt.coproduct; + +import org.junit.Before; +import org.junit.Test; + +import static com.jnape.palatable.lambda.adt.coproduct.CoProduct5.a; +import static com.jnape.palatable.lambda.adt.coproduct.CoProduct5.b; +import static com.jnape.palatable.lambda.adt.coproduct.CoProduct5.c; +import static com.jnape.palatable.lambda.adt.coproduct.CoProduct5.d; +import static com.jnape.palatable.lambda.adt.coproduct.CoProduct5.e; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; +import static org.junit.Assert.assertEquals; + +public class CoProduct5Test { + + private CoProduct5 a; + private CoProduct5 b; + private CoProduct5 c; + private CoProduct5 d; + private CoProduct5 e; + + @Before + public void setUp() { + a = a(1); + b = b("two"); + c = c(true); + d = d(4D); + e = e('z'); + } + + @Test + public void match() { + assertEquals(1, a.match(id(), id(), id(), id(), id())); + assertEquals("two", b.match(id(), id(), id(), id(), id())); + assertEquals(true, c.match(id(), id(), id(), id(), id())); + assertEquals(4D, d.match(id(), id(), id(), id(), id())); + assertEquals('z', e.match(id(), id(), id(), id(), id())); + } + + @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)); + 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/functions/builtin/fn2/PartitionTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/PartitionTest.java new file mode 100644 index 000000000..3f0fe8b84 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/PartitionTest.java @@ -0,0 +1,51 @@ +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.traitor.annotations.TestTraits; +import com.jnape.palatable.traitor.runners.Traits; +import org.junit.Test; +import org.junit.runner.RunWith; +import testsupport.traits.EmptyIterableSupport; +import testsupport.traits.FiniteIteration; +import testsupport.traits.ImmutableIteration; +import testsupport.traits.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.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 java.util.Arrays.asList; +import static org.junit.Assert.assertThat; +import static testsupport.matchers.IterableMatcher.iterates; + +@RunWith(Traits.class) +public class PartitionTest { + + @TestTraits({Laziness.class, EmptyIterableSupport.class, FiniteIteration.class, ImmutableIteration.class}) + public Fn1 createTraitsLeftTestSubject() { + return partition(constantly(left(1))).andThen(Tuple2::_1); + } + + @Test + public void partitionsIterableIntoLeftsAndRights() { + Iterable strings = asList("one", "two", "three", "four", "five"); + Tuple2, Iterable> partition = partition(s -> s.length() % 2 == 1 ? left(s) : right(s.length()), strings); + + assertThat(partition._1(), iterates("one", "two", "three")); + assertThat(partition._2(), iterates(4, 4)); + } + + @Test + public void infiniteListSupport() { + Iterable> eithers = cycle(left("left"), right(1)); + Tuple2, Iterable> partition = partition(id(), eithers); + + assertThat(take(3, partition._1()), iterates("left", "left", "left")); + assertThat(take(3, partition._2()), iterates(1, 1, 1)); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/monoid/MonoidTest.java b/src/test/java/com/jnape/palatable/lambda/monoid/MonoidTest.java new file mode 100644 index 000000000..e3e38c037 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/monoid/MonoidTest.java @@ -0,0 +1,22 @@ +package com.jnape.palatable.lambda.monoid; + +import org.junit.Test; + +import static com.jnape.palatable.lambda.monoid.Monoid.monoid; +import static java.util.Arrays.asList; +import static org.junit.Assert.assertEquals; + +public class MonoidTest { + + @Test + public void reduceLeft() { + Monoid sum = monoid((x, y) -> x + y, 0); + assertEquals((Integer) 6, sum.reduceLeft(asList(1, 2, 3))); + } + + @Test + public void reduceRight() { + Monoid sum = monoid((x, y) -> x + y, 0); + assertEquals((Integer) 6, sum.reduceRight(asList(1, 2, 3))); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/semigroup/LeftTest.java b/src/test/java/com/jnape/palatable/lambda/semigroup/LeftTest.java new file mode 100644 index 000000000..0d2d65e75 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/semigroup/LeftTest.java @@ -0,0 +1,23 @@ +package com.jnape.palatable.lambda.semigroup; + +import org.junit.Test; + +import static com.jnape.palatable.lambda.adt.Either.left; +import static com.jnape.palatable.lambda.adt.Either.right; +import static org.junit.Assert.assertEquals; + +public class LeftTest { + private static final Semigroup ADDITION = (x, y) -> x + y; + + @Test + public void appliesTwoLeftValues() { + assertEquals(left(3), new Left<>(ADDITION).apply(left(1), left(2))); + } + + @Test + public void rightValueSubvertsSemigroup() { + assertEquals(right("foo"), new Left<>(ADDITION).apply(right("foo"), left(1))); + assertEquals(right("bar"), new Left<>(ADDITION).apply(left(1), right("bar"))); + assertEquals(right("foo"), new Left<>(ADDITION).apply(right("foo"), right("bar"))); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/semigroup/PresentTest.java b/src/test/java/com/jnape/palatable/lambda/semigroup/PresentTest.java new file mode 100644 index 000000000..0f1858b99 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/semigroup/PresentTest.java @@ -0,0 +1,24 @@ +package com.jnape.palatable.lambda.semigroup; + +import org.junit.Test; + +import java.util.Optional; + +import static org.junit.Assert.assertEquals; + +public class PresentTest { + + private static final Semigroup ADDITION = (x, y) -> x + y; + + @Test + public void appliesTwoPresentValues() { + assertEquals(Optional.of(3), new Present<>(ADDITION).apply(Optional.of(1), Optional.of(2))); + } + + @Test + public void emptyValueSubvertsSemigroup() { + assertEquals(Optional.empty(), new Present<>(ADDITION).apply(Optional.empty(), Optional.of(1))); + assertEquals(Optional.empty(), new Present<>(ADDITION).apply(Optional.of(1), Optional.empty())); + assertEquals(Optional.empty(), new Present<>(ADDITION).apply(Optional.empty(), Optional.empty())); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/semigroup/RightTest.java b/src/test/java/com/jnape/palatable/lambda/semigroup/RightTest.java new file mode 100644 index 000000000..d31ca130c --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/semigroup/RightTest.java @@ -0,0 +1,23 @@ +package com.jnape.palatable.lambda.semigroup; + +import org.junit.Test; + +import static com.jnape.palatable.lambda.adt.Either.left; +import static com.jnape.palatable.lambda.adt.Either.right; +import static org.junit.Assert.assertEquals; + +public class RightTest { + private static final Semigroup ADDITION = (x, y) -> x + y; + + @Test + public void appliesTwoRightValues() { + assertEquals(right(3), new Right<>(ADDITION).apply(right(1), right(2))); + } + + @Test + public void leftValueSubvertsSemigroup() { + assertEquals(left("foo"), new Right<>(ADDITION).apply(left("foo"), right(1))); + assertEquals(left("bar"), new Right<>(ADDITION).apply(right(1), left("bar"))); + assertEquals(left("foo"), new Right<>(ADDITION).apply(left("foo"), left("bar"))); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/semigroup/SemigroupTest.java b/src/test/java/com/jnape/palatable/lambda/semigroup/SemigroupTest.java new file mode 100644 index 000000000..ec20057bc --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/semigroup/SemigroupTest.java @@ -0,0 +1,21 @@ +package com.jnape.palatable.lambda.semigroup; + +import org.junit.Test; + +import static java.util.Arrays.asList; +import static org.junit.Assert.assertEquals; + +public class SemigroupTest { + + @Test + public void foldLeft() { + Semigroup sum = (x, y) -> x + y; + assertEquals((Integer) 6, sum.foldLeft(0, asList(1, 2, 3))); + } + + @Test + public void foldRight() { + Semigroup sum = (x, y) -> x + y; + assertEquals((Integer) 6, sum.foldRight(0, asList(1, 2, 3))); + } +} \ No newline at end of file