diff --git a/README.md b/README.md
index 824187980..4d7cb8af1 100644
--- a/README.md
+++ b/README.md
@@ -14,7 +14,8 @@ Functional patterns for Java 8
- [HLists](#hlists)
- [Tuples](#tuples)
- [HMaps](#hmaps)
- - [Either](#either)
+ - [CoProducts](#coproducts)
+ - [Either](#either)
- [Lenses](#lenses)
- [Notes](#notes)
- [License](#license)
@@ -27,7 +28,7 @@ Lambda was born out of a desire to use some of the same canonical functions (e.g
Some things a user of lambda most likely values:
- Lazy evaluation
-- Immutablility by design
+- Immutability by design
- Composition
- Higher-level abstractions
- Parametric polymorphism
@@ -47,14 +48,14 @@ Add the following dependency to your:
com.jnape.palatable
lambda
- 1.5.2
+ 1.5.4
```
`build.gradle` ([Gradle](https://docs.gradle.org/current/userguide/dependency_management.html)):
```gradle
-compile group: 'com.jnape.palatable', name: 'lambda', version: '1.5.2'
+compile group: 'com.jnape.palatable', name: 'lambda', version: '1.5.4'
```
@@ -234,18 +235,47 @@ Optional intValue = hmap.get(intKey); // Optional[1]
Optional anotherIntValue = hmap.get(anotherIntKey); // Optional.empty
```
+### CoProducts
+
+`CoProduct`s generalize unions of disparate types in a single consolidated type.
+
+```Java
+CoProduct3 string = CoProduct3.a("string");
+CoProduct3 integer = CoProduct3.b(1);
+CoProduct3 character = CoProduct3.c('a');
+```
+
+Rather than supporting explicit value unwrapping, which would necessarily jeopardize type safety, `CoProduct`s support a `match` method that takes one function per possible value type and maps it to a final common result type:
+
+```Java
+CoProduct3 string = CoProduct3.a("string");
+CoProduct3 integer = CoProduct3.b(1);
+CoProduct3 character = CoProduct3.c('a');
+
+Integer result = string.match(String::length, identity(), Character::charCount); // 6
+```
+
+Additionally, because a `CoProduct2` guarantees a subset of a `CoProduct3 `, the `diverge` method exists between `CoProduct` types of single magnitude differences to make it easy to use a more convergent `CoProduct` where a more divergent `CoProduct` is expected:
+
+```Java
+CoProduct2 coProduct2 = CoProduct2.a("string");
+CoProduct3 coProduct3 = coProduct2.diverge(); // still just the coProduct2 value, adapted to the coProduct3 shape
+```
+
+There are `CoProduct` specializations for type unions of up to 5 different types: `CoProduct2` through `CoProduct5`, respectively.
+
### Either
-Binary tagged unions are represented as `Either`s, which resolve to one of two possible values: a `Left` value wrapping an `L`, or a `Right` value wrapping an `R` (typically an exceptional value or a successful value, respectively).
+`Either` represents a specialized `CoProduct2`, which resolve to one of two possible values: a left value wrapping an `L`, or a right value wrapping an `R` (typically an exceptional value or a successful value, respectively).
-Rather than supporting explicit value unwrapping, `Either` supports many useful comprehensions to help facilitate type-safe interactions. For example, `Either#match` is used to resolve an `Either` to a different type.
+As with `CoProduct2`, rather than supporting explicit value unwrapping, `Either` supports many useful comprehensions to help facilitate type-safe interactions:
```Java
Either right = Either.right(1);
Either left = Either.left("Head fell off");
-Boolean successful = right.match(l -> false, r -> true);
-//-> true
+Integer result = right.orElse(-1);
+//-> 1
List values = left.match(l -> Collections.emptyList(), Collections::singletonList);
//-> []
diff --git a/pom.xml b/pom.xml
index eb7ceb3bd..be94bb8ae 100644
--- a/pom.xml
+++ b/pom.xml
@@ -9,7 +9,7 @@
lambda
- 1.5.3
+ 1.5.4
jar
Lambda
diff --git a/src/main/java/com/jnape/palatable/lambda/adt/hmap/HMap.java b/src/main/java/com/jnape/palatable/lambda/adt/hmap/HMap.java
index f9073da12..1fc5ad770 100644
--- a/src/main/java/com/jnape/palatable/lambda/adt/hmap/HMap.java
+++ b/src/main/java/com/jnape/palatable/lambda/adt/hmap/HMap.java
@@ -125,6 +125,16 @@ public Iterable values() {
return map(Tuple2::_2, this);
}
+ /**
+ * Return a standard {@link Map} view of the current snapshot of this {@link HMap}. Note that updates to either the
+ * {@link Map} view or to the original {@link HMap} do not propagate to the other.
+ *
+ * @return the map view
+ */
+ public Map toMap() {
+ return new HashMap<>(table);
+ }
+
@Override
public Iterator> iterator() {
return map(Tuple2::fromEntry, table.entrySet()).iterator();
diff --git a/src/main/java/com/jnape/palatable/lambda/functions/Fn1.java b/src/main/java/com/jnape/palatable/lambda/functions/Fn1.java
index 3443ac535..ccb0eafe3 100644
--- a/src/main/java/com/jnape/palatable/lambda/functions/Fn1.java
+++ b/src/main/java/com/jnape/palatable/lambda/functions/Fn1.java
@@ -113,4 +113,17 @@ default Fn1 compose(Function super Z, ? extends A> before) {
default Fn1 andThen(Function super B, ? extends C> after) {
return a -> after.apply(apply(a));
}
+
+ /**
+ * Static factory method for wrapping a {@link Function} in an {@link Fn1}. Useful for avoid explicit casting when
+ * using method references as {@link Fn1}s.
+ *
+ * @param function the function to adapt
+ * @param the input argument type
+ * @param the output type
+ * @return the Fn1
+ */
+ static Fn1 adapt(Function function) {
+ return function::apply;
+ }
}
diff --git a/src/main/java/com/jnape/palatable/lambda/functions/Fn2.java b/src/main/java/com/jnape/palatable/lambda/functions/Fn2.java
index 4078bfa77..f3286a822 100644
--- a/src/main/java/com/jnape/palatable/lambda/functions/Fn2.java
+++ b/src/main/java/com/jnape/palatable/lambda/functions/Fn2.java
@@ -65,4 +65,18 @@ default Fn1, C> uncurry() {
default BiFunction toBiFunction() {
return this::apply;
}
+
+ /**
+ * Static factory method for wrapping a {@link BiFunction} in an {@link Fn2}. Useful for avoid explicit casting when
+ * using method references as {@link Fn2}s.
+ *
+ * @param biFunction the biFunction to adapt
+ * @param the first input argument type
+ * @param the second input argument type
+ * @param the output type
+ * @return the Fn2
+ */
+ static Fn2 adapt(BiFunction biFunction) {
+ return biFunction::apply;
+ }
}
diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Head.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Head.java
new file mode 100644
index 000000000..9cd2c6ab7
--- /dev/null
+++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Head.java
@@ -0,0 +1,34 @@
+package com.jnape.palatable.lambda.functions.builtin.fn1;
+
+import com.jnape.palatable.lambda.functions.Fn1;
+
+import java.util.Iterator;
+import java.util.Optional;
+
+/**
+ * Retrieve the head element of an Iterable, wrapped in an Optional. If the
+ * Iterable is empty, the result is Optional.empty().
+ *
+ * @param the Iterable element type
+ */
+public final class Head implements Fn1, Optional> {
+
+ private Head() {
+ }
+
+ @Override
+ public Optional apply(Iterable as) {
+ Iterator iterator = as.iterator();
+ return iterator.hasNext()
+ ? Optional.of(iterator.next())
+ : Optional.empty();
+ }
+
+ public static Head head() {
+ return new Head<>();
+ }
+
+ public static Optional head(Iterable as) {
+ return Head. head().apply(as);
+ }
+}
diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Tail.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Tail.java
new file mode 100644
index 000000000..87f8dd57f
--- /dev/null
+++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Tail.java
@@ -0,0 +1,35 @@
+package com.jnape.palatable.lambda.functions.builtin.fn1;
+
+import com.jnape.palatable.lambda.functions.Fn1;
+
+import java.util.Iterator;
+
+/**
+ * Returns the tail of an Iterable; the is, an Iterable of all the elements except for the
+ * head element. If the input Iterable is empty, the result is also an empty Iterable;
+ *
+ * @param the Iterable element type
+ */
+public final class Tail implements Fn1, Iterable> {
+
+ private Tail() {
+ }
+
+ @Override
+ public Iterable apply(Iterable as) {
+ return () -> {
+ Iterator iterator = as.iterator();
+ if (iterator.hasNext())
+ iterator.next();
+ return iterator;
+ };
+ }
+
+ public static Tail tail() {
+ return new Tail<>();
+ }
+
+ public static Iterable tail(Iterable as) {
+ return Tail. tail().apply(as);
+ }
+}
diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/All.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/All.java
index c0a4f2cc7..879aca6a4 100644
--- a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/All.java
+++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/All.java
@@ -1,7 +1,7 @@
package com.jnape.palatable.lambda.functions.builtin.fn2;
import com.jnape.palatable.lambda.functions.Fn1;
-import com.jnape.palatable.lambda.functions.Fn2;
+import com.jnape.palatable.lambda.functions.specialized.BiPredicate;
import java.util.function.Function;
@@ -13,7 +13,7 @@
* @param The input Iterable element type
* @see Any
*/
-public final class All implements Fn2, Iterable, Boolean> {
+public final class All implements BiPredicate, Iterable> {
private All() {
}
diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Any.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Any.java
index 716217884..93debeaa7 100644
--- a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Any.java
+++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Any.java
@@ -2,6 +2,7 @@
import com.jnape.palatable.lambda.functions.Fn1;
import com.jnape.palatable.lambda.functions.Fn2;
+import com.jnape.palatable.lambda.functions.specialized.BiPredicate;
import java.util.function.Function;
@@ -13,7 +14,7 @@
* @param The input Iterable element type
* @see All
*/
-public final class Any implements Fn2, Iterable, Boolean> {
+public final class Any implements BiPredicate, Iterable> {
private Any() {
}
diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Eq.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Eq.java
new file mode 100644
index 000000000..7511a6490
--- /dev/null
+++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Eq.java
@@ -0,0 +1,32 @@
+package com.jnape.palatable.lambda.functions.builtin.fn2;
+
+import com.jnape.palatable.lambda.functions.Fn1;
+import com.jnape.palatable.lambda.functions.specialized.BiPredicate;
+
+/**
+ * Type-safe equality in function form; uses {@link Object#equals}, not ==.
+ *
+ * @param the type to compare for equality
+ */
+public final class Eq implements BiPredicate {
+
+ private Eq() {
+ }
+
+ @Override
+ public Boolean apply(A x, A y) {
+ return x == null ? y == null : x.equals(y);
+ }
+
+ public static Eq eq() {
+ return new Eq<>();
+ }
+
+ public static Fn1 eq(A x) {
+ return Eq. eq().apply(x);
+ }
+
+ public static Boolean eq(A x, A y) {
+ return eq(x).apply(y);
+ }
+}
diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Find.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Find.java
new file mode 100644
index 000000000..88c9a33c1
--- /dev/null
+++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Find.java
@@ -0,0 +1,42 @@
+package com.jnape.palatable.lambda.functions.builtin.fn2;
+
+import com.jnape.palatable.lambda.functions.Fn1;
+import com.jnape.palatable.lambda.functions.Fn2;
+import com.jnape.palatable.lambda.functions.specialized.Predicate;
+
+import java.util.Optional;
+import java.util.function.Function;
+
+import static com.jnape.palatable.lambda.functions.builtin.fn1.Head.head;
+import static com.jnape.palatable.lambda.functions.builtin.fn2.DropWhile.dropWhile;
+
+/**
+ * Iterate the elements in an Iterable, applying a predicate to each one, returning the first element that
+ * matches the predicate, wrapped in an Optional. If no elements match the predicate, the result is
+ * Optional.empty(). This function short-circuits, and so is safe to use on potentially infinite
+ * Iterables that guarantee to have an eventually matching element.
+ *
+ * @param the Iterable element type
+ */
+public final class Find implements Fn2, Iterable, Optional > {
+
+ private Find() {
+ }
+
+ @Override
+ public Optional apply(Function super A, Boolean> predicate, Iterable as) {
+ return head(dropWhile(((Predicate ) predicate::apply).negate(), as));
+ }
+
+ public static Find find() {
+ return new Find<>();
+ }
+
+ public static Fn1, Optional> find(Function super A, Boolean> predicate) {
+ return Find. find().apply(predicate);
+ }
+
+ public static Optional find(Function super A, Boolean> predicate, Iterable as) {
+ return Find. find(predicate).apply(as);
+ }
+}
diff --git a/src/main/java/com/jnape/palatable/lambda/functions/specialized/BiPredicate.java b/src/main/java/com/jnape/palatable/lambda/functions/specialized/BiPredicate.java
new file mode 100644
index 000000000..71d427e69
--- /dev/null
+++ b/src/main/java/com/jnape/palatable/lambda/functions/specialized/BiPredicate.java
@@ -0,0 +1,82 @@
+package com.jnape.palatable.lambda.functions.specialized;
+
+import com.jnape.palatable.lambda.adt.hlist.Tuple2;
+import com.jnape.palatable.lambda.functions.Fn2;
+
+/**
+ * A specialized {@link Fn2} that returns a Boolean when fully applied,
+ * or a {@link Predicate} when partially applied.
+ *
+ * @param the first argument type
+ * @param the second argument type
+ */
+@FunctionalInterface
+public interface BiPredicate extends Fn2 , java.util.function.BiPredicate {
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ default boolean test(A a, B b) {
+ return apply(a, b);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ default Predicate apply(A a) {
+ return Fn2.super.apply(a)::apply;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ default BiPredicate flip() {
+ return (BiPredicate) Fn2.super.flip();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ default Predicate> uncurry() {
+ return Fn2.super.uncurry()::apply;
+ }
+
+ /**
+ * Override of {@link java.util.function.BiPredicate#and(java.util.function.BiPredicate)}, returning an instance of
+ * BiPredicate for compatibility. Left-to-right composition.
+ *
+ * @param other the biPredicate to test if this one succeeds
+ * @return a biPredicate representing the conjunction of this biPredicate and other
+ */
+ @Override
+ default BiPredicate and(java.util.function.BiPredicate super A, ? super B> other) {
+ return (a, b) -> apply(a, b) && other.test(a, b);
+ }
+
+ /**
+ * Override of {@link java.util.function.BiPredicate#or(java.util.function.BiPredicate)}, returning an instance of
+ * BiPredicate for compatibility. Left-to-right composition.
+ *
+ * @param other the biPredicate to test if this one fails
+ * @return a biPredicate representing the disjunction of this biPredicate and other
+ */
+ @Override
+ default BiPredicate or(java.util.function.BiPredicate super A, ? super B> other) {
+ return (a, b) -> apply(a, b) || other.test(a, b);
+ }
+
+ /**
+ * Override of {@link java.util.function.BiPredicate#negate()}, returning an instance of BiPredicate
+ * for compatibility.
+ *
+ * @return the negation of this biPredicate
+ */
+ @Override
+ default BiPredicate negate() {
+ return (a, b) -> !apply(a, b);
+ }
+}
diff --git a/src/main/java/com/jnape/palatable/lambda/monoid/Monoid.java b/src/main/java/com/jnape/palatable/lambda/monoid/Monoid.java
index 434dafe0f..d392414da 100644
--- a/src/main/java/com/jnape/palatable/lambda/monoid/Monoid.java
+++ b/src/main/java/com/jnape/palatable/lambda/monoid/Monoid.java
@@ -1,9 +1,14 @@
package com.jnape.palatable.lambda.monoid;
+import com.jnape.palatable.lambda.functions.builtin.fn2.Map;
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;
+import java.util.function.Function;
+
+import static com.jnape.palatable.lambda.functions.builtin.fn2.Map.map;
+
/**
* A {@link Monoid} is the pairing of a {@link Semigroup} with an identity element.
*
@@ -24,6 +29,7 @@ public interface Monoid extends Semigroup {
*
* @param as the elements to reduce
* @return the reduction, or {@link Monoid#identity} if empty
+ * @see ReduceLeft
*/
default A reduceLeft(Iterable as) {
return ReduceLeft.reduceLeft(toBiFunction(), as).orElse(identity());
@@ -35,11 +41,28 @@ default A reduceLeft(Iterable as) {
*
* @param as an Iterable of elements in this monoid
* @return the reduction, or {@link Monoid#identity} if empty
+ * @see ReduceRight
*/
default A reduceRight(Iterable as) {
return ReduceRight.reduceRight(toBiFunction(), as).orElse(identity());
}
+ /**
+ * Homomorphism combined with catamorphism. Convert an Iterable<B> to an
+ * Iterable<A> (that is, an Iterable of elements this monoid is formed over), then
+ * reduce the result from left to right. Under algebraic data types, this is isomorphic to a flatMap.
+ *
+ * @param fn the mapping function from A to B
+ * @param bs the Iterable of Bs
+ * @param the input Iterable element type
+ * @return the folded result under this Monoid
+ * @see Map
+ * @see Monoid#reduceLeft(Iterable)
+ */
+ default A foldMap(Function super B, ? extends A> fn, Iterable bs) {
+ return reduceLeft(map(fn, bs));
+ }
+
/**
* Promote a {@link Semigroup} to a {@link Monoid} by supplying an identity element.
*
diff --git a/src/main/java/com/jnape/palatable/lambda/semigroup/Semigroup.java b/src/main/java/com/jnape/palatable/lambda/semigroup/Semigroup.java
index 2fb0330e3..a680612e0 100644
--- a/src/main/java/com/jnape/palatable/lambda/semigroup/Semigroup.java
+++ b/src/main/java/com/jnape/palatable/lambda/semigroup/Semigroup.java
@@ -20,6 +20,7 @@ public interface Semigroup extends Fn2 {
* @param a the starting accumulator
* @param as the elements to fold over
* @return the folded result
+ * @see FoldLeft
*/
default A foldLeft(A a, Iterable as) {
return FoldLeft.foldLeft(toBiFunction(), a, as);
@@ -32,6 +33,7 @@ default A foldLeft(A a, Iterable as) {
* @param a the starting accumulator
* @param as the elements to fold over
* @return the folded result
+ * @see FoldRight
*/
default A foldRight(A a, Iterable as) {
return FoldRight.foldRight(toBiFunction(), a, as);
diff --git a/src/test/java/com/jnape/palatable/lambda/adt/hmap/HMapTest.java b/src/test/java/com/jnape/palatable/lambda/adt/hmap/HMapTest.java
index 2edcb1d17..649c6b6fd 100644
--- a/src/test/java/com/jnape/palatable/lambda/adt/hmap/HMapTest.java
+++ b/src/test/java/com/jnape/palatable/lambda/adt/hmap/HMapTest.java
@@ -2,6 +2,7 @@
import org.junit.Test;
+import java.util.HashMap;
import java.util.NoSuchElementException;
import java.util.Optional;
@@ -128,6 +129,18 @@ public void demandForAbsentKey() {
emptyHMap().demand(typeSafeKey());
}
+ @Test
+ public void toMap() {
+ TypeSafeKey stringKey = typeSafeKey();
+ TypeSafeKey intKey = typeSafeKey();
+
+ assertEquals(new HashMap() {{
+ put(stringKey, "string");
+ put(intKey, 1);
+ }}, hMap(stringKey, "string",
+ intKey, 1).toMap());
+ }
+
@Test
public void iteratesKVPairsAsTuples() {
TypeSafeKey stringKey = typeSafeKey();
diff --git a/src/test/java/com/jnape/palatable/lambda/functions/Fn1Test.java b/src/test/java/com/jnape/palatable/lambda/functions/Fn1Test.java
index 2bdfca588..26baebef4 100644
--- a/src/test/java/com/jnape/palatable/lambda/functions/Fn1Test.java
+++ b/src/test/java/com/jnape/palatable/lambda/functions/Fn1Test.java
@@ -3,6 +3,8 @@
import org.hamcrest.MatcherAssert;
import org.junit.Test;
+import java.util.function.Function;
+
import static org.hamcrest.core.Is.is;
import static org.junit.Assert.assertEquals;
@@ -32,4 +34,10 @@ public void thenIsJustAnAliasForFmap() {
MatcherAssert.assertThat(add2.then(toString).apply(2), is(toString.apply(add2.apply(2))));
}
+
+ @Test
+ public void adapt() {
+ Function parseInt = Integer::parseInt;
+ assertEquals((Integer) 1, Fn1.adapt(parseInt).apply("1"));
+ }
}
diff --git a/src/test/java/com/jnape/palatable/lambda/functions/Fn2Test.java b/src/test/java/com/jnape/palatable/lambda/functions/Fn2Test.java
index e12b9e3e3..6a845f11a 100644
--- a/src/test/java/com/jnape/palatable/lambda/functions/Fn2Test.java
+++ b/src/test/java/com/jnape/palatable/lambda/functions/Fn2Test.java
@@ -34,4 +34,10 @@ public void toBiFunction() {
BiFunction biFunction = CHECK_LENGTH.toBiFunction();
assertEquals(true, biFunction.apply("abc", 3));
}
+
+ @Test
+ public void adapt() {
+ BiFunction format = String::format;
+ assertEquals("foo bar", Fn2.adapt(format).apply("foo %s", "bar"));
+ }
}
diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/HeadTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/HeadTest.java
new file mode 100644
index 000000000..6c09d03b1
--- /dev/null
+++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/HeadTest.java
@@ -0,0 +1,26 @@
+package com.jnape.palatable.lambda.functions.builtin.fn1;
+
+import com.jnape.palatable.traitor.runners.Traits;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Optional;
+
+import static com.jnape.palatable.lambda.functions.builtin.fn1.Head.head;
+import static java.util.Arrays.asList;
+import static java.util.Collections.emptyList;
+import static org.junit.Assert.assertEquals;
+
+@RunWith(Traits.class)
+public class HeadTest {
+
+ @Test
+ public void returnsTheHeadOfNonEmptyIterable() {
+ assertEquals(Optional.of(1), head(asList(1, 2, 3)));
+ }
+
+ @Test
+ public void isEmptyForEmptyIterable() {
+ assertEquals(Optional.empty(), head(emptyList()));
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/TailTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/TailTest.java
new file mode 100644
index 000000000..055cd9c9b
--- /dev/null
+++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/TailTest.java
@@ -0,0 +1,36 @@
+package com.jnape.palatable.lambda.functions.builtin.fn1;
+
+import com.jnape.palatable.lambda.functions.Fn1;
+import com.jnape.palatable.traitor.annotations.TestTraits;
+import com.jnape.palatable.traitor.runners.Traits;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import testsupport.traits.EmptyIterableSupport;
+import testsupport.traits.FiniteIteration;
+import testsupport.traits.ImmutableIteration;
+import testsupport.traits.Laziness;
+
+import static com.jnape.palatable.lambda.functions.builtin.fn1.Tail.tail;
+import static java.util.Arrays.asList;
+import static java.util.Collections.emptyList;
+import static org.junit.Assert.assertThat;
+import static testsupport.matchers.IterableMatcher.iterates;
+
+@RunWith(Traits.class)
+public class TailTest {
+
+ @TestTraits({Laziness.class, EmptyIterableSupport.class, FiniteIteration.class, ImmutableIteration.class})
+ public Fn1 extends Iterable, ?> createTraitsTestSubject() {
+ return tail();
+ }
+
+ @Test
+ public void skipsFirstElementOfNonEmptyList() {
+ assertThat(tail(asList(1, 2, 3)), iterates(2, 3));
+ }
+
+ @Test
+ public void isEmptyIfEmptyIterable() {
+ assertThat(tail(emptyList()), iterates());
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/EqTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/EqTest.java
new file mode 100644
index 000000000..76df4f672
--- /dev/null
+++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/EqTest.java
@@ -0,0 +1,22 @@
+package com.jnape.palatable.lambda.functions.builtin.fn2;
+
+import org.junit.Test;
+
+import static com.jnape.palatable.lambda.functions.builtin.fn2.Eq.eq;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+public class EqTest {
+
+ @Test
+ public void testsValueEquality() {
+ Eq stringEq = eq();
+
+ assertTrue(stringEq.apply("a", "a"));
+ assertFalse(stringEq.apply("a", "b"));
+ assertFalse(stringEq.apply("b", "a"));
+ assertTrue(stringEq.apply(null, null));
+ assertFalse(stringEq.apply("a", null));
+ assertFalse(stringEq.apply(null, "a"));
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/FindTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/FindTest.java
new file mode 100644
index 000000000..585a01793
--- /dev/null
+++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/FindTest.java
@@ -0,0 +1,32 @@
+package com.jnape.palatable.lambda.functions.builtin.fn2;
+
+import org.junit.Test;
+
+import java.util.Optional;
+
+import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly;
+import static com.jnape.palatable.lambda.functions.builtin.fn2.Find.find;
+import static com.jnape.palatable.lambda.functions.builtin.fn2.Iterate.iterate;
+import static java.util.Arrays.asList;
+import static org.junit.Assert.assertEquals;
+
+public class FindTest {
+
+ @Test
+ public void findsFirstElementMatchingPredicate() {
+ assertEquals(Optional.of("three"),
+ find(s -> s.length() > 3, asList("one", "two", "three", "four")));
+ }
+
+ @Test
+ public void isEmptyIfNoElementsMatchPredicate() {
+ assertEquals(Optional.empty(),
+ find(s -> s.length() > 5, asList("one", "two", "three", "four")));
+ }
+
+ @Test
+ public void shortCircuitsOnMatch() {
+ assertEquals(Optional.of(0),
+ find(constantly(true), iterate(x -> x + 1, 0)));
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/com/jnape/palatable/lambda/functions/specialized/BiPredicateTest.java b/src/test/java/com/jnape/palatable/lambda/functions/specialized/BiPredicateTest.java
new file mode 100644
index 000000000..e8dd3c690
--- /dev/null
+++ b/src/test/java/com/jnape/palatable/lambda/functions/specialized/BiPredicateTest.java
@@ -0,0 +1,53 @@
+package com.jnape.palatable.lambda.functions.specialized;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+public class BiPredicateTest {
+
+ @Test
+ public void jufBiPredicateTest() {
+ BiPredicate equals = String::equals;
+
+ assertTrue(equals.test("abc", "abc"));
+ assertFalse(equals.test("abc", ""));
+ assertFalse(equals.test("", "abc"));
+ }
+
+ @Test
+ public void jufBiPredicateAnd() {
+ BiPredicate bothOdd = (x, y) -> x % 2 == 1 && y % 2 == 1;
+ BiPredicate greaterThan = (x, y) -> x.compareTo(y) > 0;
+
+ BiPredicate conjunction = bothOdd.and(greaterThan);
+
+ assertTrue(conjunction.test(3, 1));
+ assertFalse(conjunction.test(3, 2));
+ assertFalse(conjunction.test(3, 5));
+ assertFalse(conjunction.test(4, 1));
+ }
+
+ @Test
+ public void jufBiPredicateOr() {
+ BiPredicate bothOdd = (x, y) -> x % 2 == 1 && y % 2 == 1;
+ BiPredicate greaterThan = (x, y) -> x.compareTo(y) > 0;
+
+ BiPredicate disjunction = bothOdd.or(greaterThan);
+
+ assertTrue(disjunction.test(3, 2));
+ assertTrue(disjunction.test(1, 3));
+ assertFalse(disjunction.test(1, 2));
+ }
+
+ @Test
+ public void jufBiPredicateNegate() {
+ BiPredicate equals = String::equals;
+
+ assertTrue(equals.test("a", "a"));
+ assertFalse(equals.test("b", "a"));
+ assertFalse(equals.negate().test("a", "a"));
+ assertTrue(equals.negate().test("b", "a"));
+ }
+}
\ 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
index e3e38c037..8b14f3dfa 100644
--- a/src/test/java/com/jnape/palatable/lambda/monoid/MonoidTest.java
+++ b/src/test/java/com/jnape/palatable/lambda/monoid/MonoidTest.java
@@ -2,6 +2,9 @@
import org.junit.Test;
+import java.util.List;
+import java.util.Optional;
+
import static com.jnape.palatable.lambda.monoid.Monoid.monoid;
import static java.util.Arrays.asList;
import static org.junit.Assert.assertEquals;
@@ -19,4 +22,11 @@ public void reduceRight() {
Monoid sum = monoid((x, y) -> x + y, 0);
assertEquals((Integer) 6, sum.reduceRight(asList(1, 2, 3)));
}
+
+ @Test
+ public void foldMap() {
+ Monoid sum = monoid((x, y) -> x + y, 0);
+ List> optionalInts = asList(Optional.of(1), Optional.of(2), Optional.empty(), Optional.of(3), Optional.empty());
+ assertEquals((Integer) 6, sum.foldMap(optX -> optX.orElse(0), optionalInts));
+ }
}
\ No newline at end of file