From 0306a6e0d7b488b4ee5c2cc884d00217fd1796f6 Mon Sep 17 00:00:00 2001 From: jnape Date: Sun, 6 Nov 2016 22:28:58 -0600 Subject: [PATCH 01/11] [maven-release-plugin] prepare for next development iteration --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index eb7ceb3bd..0c1b9970e 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ lambda - 1.5.3 + 1.5.4-SNAPSHOT jar Lambda From 993c3221bf60774ff1de698f22a9f8796034b5d1 Mon Sep 17 00:00:00 2001 From: John Napier Date: Sat, 12 Nov 2016 00:27:34 -0600 Subject: [PATCH 02/11] Updating README Adding CoProduct examples and bumping README version info --- README.md | 44 +++++++++++++++++++++++++++++++++++++------- 1 file changed, 37 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 824187980..49a870132 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) @@ -47,14 +48,14 @@ Add the following dependency to your: com.jnape.palatable lambda - 1.5.2 + 1.5.3 ``` `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.3' ``` @@ -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); //-> [] From 25a7e0fe735bc28072f11a5d8797372bab111867 Mon Sep 17 00:00:00 2001 From: jnape Date: Thu, 10 Nov 2016 00:04:23 -0600 Subject: [PATCH 03/11] adding adapt methods for Fn1 and Fn2 from Function and BiFunction, respectively --- .../com/jnape/palatable/lambda/functions/Fn1.java | 13 +++++++++++++ .../com/jnape/palatable/lambda/functions/Fn2.java | 14 ++++++++++++++ .../jnape/palatable/lambda/functions/Fn1Test.java | 8 ++++++++ .../jnape/palatable/lambda/functions/Fn2Test.java | 6 ++++++ 4 files changed, 41 insertions(+) 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/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")); + } } From f9b10be694d77e2afb54cb741715a167cc9cd70f Mon Sep 17 00:00:00 2001 From: jnape Date: Fri, 11 Nov 2016 00:36:37 -0600 Subject: [PATCH 04/11] adding eq --- .../lambda/functions/builtin/fn2/Eq.java | 32 +++++++++++++++++++ .../lambda/functions/builtin/fn2/EqTest.java | 22 +++++++++++++ 2 files changed, 54 insertions(+) create mode 100644 src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Eq.java create mode 100644 src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/EqTest.java 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..95a15388a --- /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.Fn2; + +/** + * Type-safe equality in function form; uses {@link Object#equals}, not ==. + * + * @param the type to compare for equality + */ +public final class Eq implements Fn2 { + + 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/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 From d62119da9d5b0085708a173d949a0ae7096b33e7 Mon Sep 17 00:00:00 2001 From: jnape Date: Wed, 16 Nov 2016 22:30:47 -0600 Subject: [PATCH 05/11] adding BiPredicate --- .../functions/specialized/BiPredicate.java | 82 +++++++++++++++++++ .../specialized/BiPredicateTest.java | 53 ++++++++++++ 2 files changed, 135 insertions(+) create mode 100644 src/main/java/com/jnape/palatable/lambda/functions/specialized/BiPredicate.java create mode 100644 src/test/java/com/jnape/palatable/lambda/functions/specialized/BiPredicateTest.java 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/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 From b783c73e288f2351380718a10a89aac36baff5dc Mon Sep 17 00:00:00 2001 From: jnape Date: Sat, 19 Nov 2016 13:09:55 -0600 Subject: [PATCH 06/11] adding head() and find() --- .../lambda/functions/builtin/fn1/Head.java | 34 +++++++++++++++ .../lambda/functions/builtin/fn2/Find.java | 42 +++++++++++++++++++ .../functions/builtin/fn1/HeadTest.java | 26 ++++++++++++ .../functions/builtin/fn2/FindTest.java | 32 ++++++++++++++ 4 files changed, 134 insertions(+) create mode 100644 src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Head.java create mode 100644 src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Find.java create mode 100644 src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/HeadTest.java create mode 100644 src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/FindTest.java 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/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/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/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 From de00ae94aa2a94606dc70ceaf8b9a06c206235bc Mon Sep 17 00:00:00 2001 From: jnape Date: Sat, 19 Nov 2016 13:22:47 -0600 Subject: [PATCH 07/11] adding tail() --- .../lambda/functions/builtin/fn1/Tail.java | 35 ++++++++++++++++++ .../functions/builtin/fn1/TailTest.java | 36 +++++++++++++++++++ 2 files changed, 71 insertions(+) create mode 100644 src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Tail.java create mode 100644 src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/TailTest.java 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/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 From 99339b37763187e76935fd057bbb4ce3725209d1 Mon Sep 17 00:00:00 2001 From: jnape Date: Sat, 19 Nov 2016 13:52:54 -0600 Subject: [PATCH 08/11] Adding Monoid#foldMap; adding @see refs in javadocs for Monoid and Semigroup --- .../jnape/palatable/lambda/monoid/Monoid.java | 23 +++++++++++++++++++ .../palatable/lambda/semigroup/Semigroup.java | 2 ++ .../palatable/lambda/monoid/MonoidTest.java | 10 ++++++++ 3 files changed, 35 insertions(+) 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/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 From 42d8c9747026201e9d21afa0ac8cf3895a5c59f1 Mon Sep 17 00:00:00 2001 From: jnape Date: Sun, 27 Nov 2016 12:58:36 -0600 Subject: [PATCH 09/11] Updating README for next release; Eq, Any, and All are now BiPredicates --- README.md | 6 +++--- .../jnape/palatable/lambda/functions/builtin/fn2/All.java | 4 ++-- .../jnape/palatable/lambda/functions/builtin/fn2/Any.java | 3 ++- .../jnape/palatable/lambda/functions/builtin/fn2/Eq.java | 4 ++-- 4 files changed, 9 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 49a870132..4d7cb8af1 100644 --- a/README.md +++ b/README.md @@ -28,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 @@ -48,14 +48,14 @@ Add the following dependency to your: com.jnape.palatable lambda - 1.5.3 + 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.3' +compile group: 'com.jnape.palatable', name: 'lambda', version: '1.5.4' ``` 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 index 95a15388a..7511a6490 100644 --- 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 @@ -1,14 +1,14 @@ 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; /** * Type-safe equality in function form; uses {@link Object#equals}, not ==. * * @param the type to compare for equality */ -public final class Eq implements Fn2 { +public final class Eq implements BiPredicate { private Eq() { } From 699104092c20e21d6e139f6c211f2348276c31a2 Mon Sep 17 00:00:00 2001 From: jnape Date: Sun, 27 Nov 2016 13:19:52 -0600 Subject: [PATCH 10/11] adding HMap#toMap --- .../com/jnape/palatable/lambda/adt/hmap/HMap.java | 10 ++++++++++ .../jnape/palatable/lambda/adt/hmap/HMapTest.java | 13 +++++++++++++ 2 files changed, 23 insertions(+) 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/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(); From 61d1b6ad439004d0e652aebc70462fb230821940 Mon Sep 17 00:00:00 2001 From: jnape Date: Sun, 27 Nov 2016 14:23:44 -0600 Subject: [PATCH 11/11] [maven-release-plugin] prepare release lambda-1.5.4 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 0c1b9970e..be94bb8ae 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ lambda - 1.5.4-SNAPSHOT + 1.5.4 jar Lambda