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 before) { default Fn1 andThen(Function 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 predicate, Iterable as) { + return head(dropWhile(((Predicate) predicate::apply).negate(), as)); + } + + public static Find find() { + return new Find<>(); + } + + public static Fn1, Optional> find(Function predicate) { + return Find.find().apply(predicate); + } + + public static Optional find(Function 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 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 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 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 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