From 8566a28fbcb0117f08d118bbd07231212bad72cf Mon Sep 17 00:00:00 2001 From: jnape Date: Wed, 27 Sep 2017 21:32:39 -0500 Subject: [PATCH 01/32] [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 a58b8340f..226409c5c 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ lambda - 1.6.3 + 1.6.4-SNAPSHOT jar Lambda From de7d76021021ffc0c334dceddd66208ad76bf6f7 Mon Sep 17 00:00:00 2001 From: jnape Date: Wed, 27 Sep 2017 21:37:30 -0500 Subject: [PATCH 02/32] Updating README and CHANGELOG to reflect v1.6.3 --- CHANGELOG.md | 5 ++++- README.md | 4 ++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 26b2fe0a8..202affb9b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/). ## [Unreleased] + +## [1.6.3] - 2017-09-27 ### Fixed - `ConcatenatingIterator` bug where deeply nested `xs` skip elements @@ -160,7 +162,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/). - `Monadic/Dyadic/TriadicFunction`, `Predicate`, `Tuple2`, `Tuple3` - `Functor`, `BiFunctor`, `ProFunctor` -[Unreleased]: https://github.com/palatable/lambda/compare/lambda-1.6.2...HEAD +[Unreleased]: https://github.com/palatable/lambda/compare/lambda-1.6.3...HEAD +[1.6.3]: https://github.com/palatable/lambda/compare/lambda-1.6.2...lambda-1.6.3 [1.6.2]: https://github.com/palatable/lambda/compare/lambda-1.6.1...lambda-1.6.2 [1.6.1]: https://github.com/palatable/lambda/compare/lambda-1.6.0...lambda-1.6.1 [1.6.0]: https://github.com/palatable/lambda/compare/lambda-1.5.6...lambda-1.6.0 diff --git a/README.md b/README.md index e34a5ab7f..480f6ecc0 100644 --- a/README.md +++ b/README.md @@ -55,14 +55,14 @@ Add the following dependency to your: com.jnape.palatable lambda - 1.6.2 + 1.6.3 ``` `build.gradle` ([Gradle](https://docs.gradle.org/current/userguide/dependency_management.html)): ```gradle -compile group: 'com.jnape.palatable', name: 'lambda', version: '1.6.2' +compile group: 'com.jnape.palatable', name: 'lambda', version: '1.6.3' ``` Examples From c3caf018b071799b1a6639accfa608da4a4b9f73 Mon Sep 17 00:00:00 2001 From: jnape Date: Sun, 1 Oct 2017 15:33:00 -0500 Subject: [PATCH 03/32] Lazier implementation of Tail, using Drop --- .../palatable/lambda/functions/builtin/fn1/Tail.java | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) 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 index e155134fc..8b954fe07 100644 --- 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 @@ -2,7 +2,7 @@ import com.jnape.palatable.lambda.functions.Fn1; -import java.util.Iterator; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Drop.drop; /** * Returns the tail of an Iterable; the is, an Iterable of all the elements except for the @@ -19,12 +19,7 @@ private Tail() { @Override public Iterable apply(Iterable as) { - return () -> { - Iterator iterator = as.iterator(); - if (iterator.hasNext()) - iterator.next(); - return iterator; - }; + return drop(1, as); } @SuppressWarnings("unchecked") From 8ed4a61a70f16f69157e0022a17e28571bba9478 Mon Sep 17 00:00:00 2001 From: jnape Date: Sun, 1 Oct 2017 15:42:52 -0500 Subject: [PATCH 04/32] Force, for forcing iteration of an Iterable --- CHANGELOG.md | 2 ++ .../lambda/functions/builtin/fn1/Force.java | 33 +++++++++++++++++++ .../functions/builtin/fn1/ForceTest.java | 29 ++++++++++++++++ 3 files changed, 64 insertions(+) create mode 100644 src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Force.java create mode 100644 src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/ForceTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 202affb9b..758d81237 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/). ## [Unreleased] +### Added +- `Force`, for forcing iteration of an `Iterable` to perform any side-effects ## [1.6.3] - 2017-09-27 ### Fixed diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Force.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Force.java new file mode 100644 index 000000000..a97832a5e --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Force.java @@ -0,0 +1,33 @@ +package com.jnape.palatable.lambda.functions.builtin.fn1; + +import com.jnape.palatable.lambda.functions.Fn1; + +/** + * Force a full iteration of an {@link Iterable}, presumably to perform any side-effects contained therein. Returns the + * {@link Iterable} back. + * + * @param the Iterable element type + */ +public final class Force implements Fn1, Iterable> { + + private static final Force INSTANCE = new Force(); + + private Force() { + } + + @Override + @SuppressWarnings("unchecked") + public Iterable apply(Iterable as) { + as.forEach(__ -> {}); + return as; + } + + @SuppressWarnings("unchecked") + public static Force force() { + return INSTANCE; + } + + public static Iterable force(Iterable as) { + return Force.force().apply(as); + } +} diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/ForceTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/ForceTest.java new file mode 100644 index 000000000..394d85ddd --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/ForceTest.java @@ -0,0 +1,29 @@ +package com.jnape.palatable.lambda.functions.builtin.fn1; + +import org.junit.Test; + +import java.util.concurrent.atomic.AtomicInteger; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Force.force; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Map.map; +import static java.util.Arrays.asList; +import static org.junit.Assert.assertEquals; + +public class ForceTest { + + @Test + public void performsAnySideEffects() { + AtomicInteger counter = new AtomicInteger(); + Iterable ints = map(x -> { + counter.incrementAndGet(); + return x; + }, asList(1, 2, 3)); + + assertEquals(0, counter.get()); + + force(ints); + force(ints); + + assertEquals(6, counter.get()); + } +} \ No newline at end of file From d941dabc478967550f4787feb55f5c1692019ca0 Mon Sep 17 00:00:00 2001 From: jnape Date: Sun, 1 Oct 2017 16:53:25 -0500 Subject: [PATCH 05/32] Removing members deprecated in previous release --- CHANGELOG.md | 7 ++++- .../jnape/palatable/lambda/functions/Fn1.java | 29 ------------------- .../jnape/palatable/lambda/functions/Fn2.java | 16 ---------- 3 files changed, 6 insertions(+), 46 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 758d81237..df8e2df0f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,12 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/). ## [Unreleased] ### Added -- `Force`, for forcing iteration of an `Iterable` to perform any side-effects +- `Force`, for forcing iteration of an `Iterable` to perform any side-effects + +### Removed +- `Fn1#then(Function)`, deprecated in previous release +- `Fn1#adapt(Function function)`, deprecated in previous release +- `Fn2#adapt(BiFunction biFunction)`, deprecated in previous release ## [1.6.3] - 2017-09-27 ### Fixed 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 fb1b7f27e..dcc62f5d4 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/Fn1.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/Fn1.java @@ -26,20 +26,6 @@ public interface Fn1 extends Applicative>, Profunctorg.then(f).apply(x) is equivalent to - * f.apply(g.apply(x)). - * - * @param f the function to invoke with this function's return value - * @param the return type of the next function to invoke - * @return a function representing the composition of this function and f - * @deprecated in favor of {@link Fn1#andThen(Function)} - */ - @Deprecated - default Fn1 then(Function f) { - return fmap(f); - } - /** * Also left-to-right composition (sadly). * @@ -196,21 +182,6 @@ 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 - * @deprecated in favor of {@link Fn1#fn1(Function)} - */ - @Deprecated - static Fn1 adapt(Function function) { - return function::apply; - } - /** * 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. 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 3855e6caa..792309b09 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/Fn2.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/Fn2.java @@ -78,22 +78,6 @@ 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 - * @deprecated in favor of {@link Fn2#fn2(BiFunction)} - */ - @Deprecated - static Fn2 adapt(BiFunction biFunction) { - return biFunction::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. From 2e0645c8f920078d5be430a6ec589b34699c4327 Mon Sep 17 00:00:00 2001 From: jnape Date: Tue, 3 Oct 2017 20:43:12 -0500 Subject: [PATCH 06/32] Adding snoc, for lazily appending elements to the end of Iterables --- CHANGELOG.md | 1 + .../lambda/functions/builtin/fn2/Snoc.java | 37 ++++++++++++ .../lambda/iterators/SnocIterator.java | 48 +++++++++++++++ .../functions/builtin/fn2/SnocTest.java | 38 ++++++++++++ .../lambda/iterators/SnocIteratorTest.java | 58 +++++++++++++++++++ 5 files changed, 182 insertions(+) create mode 100644 src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Snoc.java create mode 100644 src/main/java/com/jnape/palatable/lambda/iterators/SnocIterator.java create mode 100644 src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/SnocTest.java create mode 100644 src/test/java/com/jnape/palatable/lambda/iterators/SnocIteratorTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md index df8e2df0f..0301f815d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/). ## [Unreleased] ### Added - `Force`, for forcing iteration of an `Iterable` to perform any side-effects +- `Snoc`, for lazily appending an element to the end of an `Iterable` ### Removed - `Fn1#then(Function)`, deprecated in previous release diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Snoc.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Snoc.java new file mode 100644 index 000000000..361cede00 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Snoc.java @@ -0,0 +1,37 @@ +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.iterators.SnocIterator; + +/** + * Opposite of {@link Cons}: lazily append an element to the end of the given {@link Iterable}. + *

+ * Note that obtaining both laziness and stack-safety is particularly tricky here, and requires an initial eager + * deforestation of O(k) traversals where k is the number of contiguously nested + * {@link Snoc}s. + * + * @param the Iterable element type + */ +public final class Snoc implements Fn2, Iterable> { + + private static final Snoc INSTANCE = new Snoc(); + + @Override + public Iterable apply(A a, Iterable as) { + return () -> new SnocIterator<>(a, as); + } + + @SuppressWarnings("unchecked") + public static Snoc snoc() { + return INSTANCE; + } + + public static Fn1, Iterable> snoc(A a) { + return Snoc.snoc().apply(a); + } + + public static Iterable snoc(A a, Iterable as) { + return snoc(a).apply(as); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/iterators/SnocIterator.java b/src/main/java/com/jnape/palatable/lambda/iterators/SnocIterator.java new file mode 100644 index 000000000..2506bd658 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/iterators/SnocIterator.java @@ -0,0 +1,48 @@ +package com.jnape.palatable.lambda.iterators; + +import java.util.Collections; +import java.util.Iterator; +import java.util.NoSuchElementException; +import java.util.function.Supplier; + +import static com.jnape.palatable.lambda.functions.builtin.fn2.Cons.cons; + +public final class SnocIterator implements Iterator { + + private final Supplier> initsSupplier; + private final A last; + private Iterator inits; + private Iterator lasts; + + public SnocIterator(A last, Iterable inits) { + this.last = last; + initsSupplier = inits::iterator; + } + + @Override + public boolean hasNext() { + if (inits == null) + queueAndDeforest(); + + return inits.hasNext() || lasts.hasNext(); + } + + @Override + public A next() { + if (!hasNext()) + throw new NoSuchElementException(); + + return inits.hasNext() ? inits.next() : lasts.next(); + } + + private void queueAndDeforest() { + Iterable lastConses = Collections::emptyIterator; + inits = this; + while (inits instanceof SnocIterator) { + SnocIterator it = (SnocIterator) inits; + lastConses = cons(it.last, lastConses); + inits = it.initsSupplier.get(); + } + lasts = lastConses.iterator(); + } +} diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/SnocTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/SnocTest.java new file mode 100644 index 000000000..8615a5dd8 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/SnocTest.java @@ -0,0 +1,38 @@ +package com.jnape.palatable.lambda.functions.builtin.fn2; + +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.InfiniteIterableSupport; +import testsupport.traits.Laziness; + +import java.util.Collections; + +import static com.jnape.palatable.lambda.functions.builtin.fn2.Snoc.snoc; +import static java.util.Arrays.asList; +import static org.junit.Assert.assertThat; +import static testsupport.matchers.IterableMatcher.iterates; + +@RunWith(Traits.class) +public class SnocTest { + + @TestTraits({Laziness.class, EmptyIterableSupport.class, ImmutableIteration.class, FiniteIteration.class, InfiniteIterableSupport.class}) + public Fn1, Iterable> testSubject() { + return snoc(1); + } + + @Test + public void appendToEmptyIterable() { + assertThat(snoc(1, Collections::emptyIterator), iterates(1)); + } + + @Test + public void appendToNonEmptyIterable() { + assertThat(snoc(4, asList(1, 2, 3)), iterates(1, 2, 3, 4)); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/iterators/SnocIteratorTest.java b/src/test/java/com/jnape/palatable/lambda/iterators/SnocIteratorTest.java new file mode 100644 index 000000000..6ccbe3fc3 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/iterators/SnocIteratorTest.java @@ -0,0 +1,58 @@ +package com.jnape.palatable.lambda.iterators; + +import org.junit.Before; +import org.junit.Test; + +import java.util.Collections; +import java.util.Optional; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Last.last; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Iterate.iterate; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Take.take; +import static com.jnape.palatable.lambda.functions.builtin.fn3.FoldLeft.foldLeft; +import static java.util.Arrays.asList; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class SnocIteratorTest { + + private SnocIterator iterator; + + @Before + public void setUp() { + iterator = new SnocIterator<>(3, asList(1, 2)); + } + + @Test + public void hasNextIfInitNotYetIterated() { + assertTrue(iterator.hasNext()); + assertEquals((Integer) 1, iterator.next()); + } + + @Test + public void hasNextIfNoInitButLastNotYetIterated() { + iterator.next(); + iterator.next(); + assertTrue(iterator.hasNext()); + assertEquals((Integer) 3, iterator.next()); + } + + @Test + public void doesNotHaveNextIfLastIterated() { + iterator.next(); + iterator.next(); + iterator.next(); + assertFalse(iterator.hasNext()); + } + + @Test + public void stackSafety() { + int stackBlowingNumber = 100000; + Iterable ints = foldLeft((xs, x) -> () -> new SnocIterator<>(x, xs), + Collections::emptyIterator, + take(stackBlowingNumber, iterate(x -> x + 1, 1))); + + assertEquals(Optional.of(stackBlowingNumber), last(ints)); + } +} \ No newline at end of file From 32b257e943919fed651735fd9d4922a384e2791a Mon Sep 17 00:00:00 2001 From: jnape Date: Sat, 7 Oct 2017 14:56:12 -0500 Subject: [PATCH 07/32] making xor constructor private --- .../java/com/jnape/palatable/lambda/monoid/builtin/Xor.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/java/com/jnape/palatable/lambda/monoid/builtin/Xor.java b/src/main/java/com/jnape/palatable/lambda/monoid/builtin/Xor.java index 9341b05d3..5ebdb9b0e 100644 --- a/src/main/java/com/jnape/palatable/lambda/monoid/builtin/Xor.java +++ b/src/main/java/com/jnape/palatable/lambda/monoid/builtin/Xor.java @@ -14,6 +14,9 @@ public final class Xor implements Monoid { private static final Xor INSTANCE = new Xor(); + private Xor() { + } + @Override public Boolean identity() { return false; From 484402552aeea63c9995c5b72f9ddd8808ac5ed6 Mon Sep 17 00:00:00 2001 From: jnape Date: Sat, 7 Oct 2017 16:12:15 -0500 Subject: [PATCH 08/32] more efficient implementation of Force --- .../jnape/palatable/lambda/functions/builtin/fn1/Force.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Force.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Force.java index a97832a5e..511b90b4e 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Force.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Force.java @@ -16,9 +16,10 @@ private Force() { } @Override - @SuppressWarnings("unchecked") + @SuppressWarnings("StatementWithEmptyBody") public Iterable apply(Iterable as) { - as.forEach(__ -> {}); + for (A ignored : as) { + } return as; } From 5f8a73b6d79a16f2bc83f48512a4159c8077ebce Mon Sep 17 00:00:00 2001 From: jnape Date: Thu, 5 Oct 2017 20:47:22 -0500 Subject: [PATCH 09/32] Coalesce to fold through eithers and collect all pertinent results --- CHANGELOG.md | 2 + .../functions/builtin/fn1/Coalesce.java | 51 +++++++++++++++++++ .../palatable/lambda/monoid/builtin/And.java | 13 ++++- .../palatable/lambda/monoid/builtin/Or.java | 13 ++++- .../palatable/lambda/monoid/builtin/Xor.java | 21 ++++++-- .../functions/builtin/fn1/CoalesceTest.java | 38 ++++++++++++++ .../iterators/GroupingIteratorTest.java | 2 +- .../testsupport/matchers/IterableMatcher.java | 22 ++++---- .../testsupport/matchers/LeftMatcher.java | 42 +++++++++++++++ .../testsupport/matchers/RightMatcher.java | 42 +++++++++++++++ 10 files changed, 229 insertions(+), 17 deletions(-) create mode 100644 src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Coalesce.java create mode 100644 src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/CoalesceTest.java create mode 100644 src/test/java/testsupport/matchers/LeftMatcher.java create mode 100644 src/test/java/testsupport/matchers/RightMatcher.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 0301f815d..74a5cf980 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/). ### Added - `Force`, for forcing iteration of an `Iterable` to perform any side-effects - `Snoc`, for lazily appending an element to the end of an `Iterable` +- `Coalesce`, for folding an `Iterable>` into an `Either, Iterable>` +- `And`, `Or`, and `Xor` all gain `BiPredicate` properties ### Removed - `Fn1#then(Function)`, deprecated in previous release diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Coalesce.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Coalesce.java new file mode 100644 index 000000000..e97fe056f --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Coalesce.java @@ -0,0 +1,51 @@ +package com.jnape.palatable.lambda.functions.builtin.fn1; + +import com.jnape.palatable.lambda.adt.Either; +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.builtin.fn2.Snoc; +import com.jnape.palatable.lambda.monoid.builtin.Merge; + +import java.util.Collections; + +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.fn3.FoldLeft.foldLeft; + +/** + * Fold an {@link Iterable}<{@link Either}<L, R>> into an {@link Either}<{@link + * Iterable}<L>, {@link Iterable}<R>>, preserving all results of the side that's returned. That + * is, if the result is a left, it will contain all left values; if it is a right, it will + * contain all right values. + *

+ * It may be useful to think of this as a more efficient version of {@link Merge}<{@link Iterable}<L>, + * {@link Iterable}<R>>. + * + * @param the left parameter type + * @param the right parameter type + */ +public final class Coalesce implements Fn1>, Either, Iterable>> { + + private static final Coalesce INSTANCE = new Coalesce(); + + private Coalesce() { + } + + @Override + public Either, Iterable> apply(Iterable> eithers) { + return foldLeft((acc, e) -> acc + .biMapL(ls -> e.match(Snoc.snoc().flip().apply(ls), constantly(ls))) + .flatMap(rs -> e.biMap(Collections::singletonList, + Snoc.snoc().flip().apply(rs))), + right(Collections::emptyIterator), + eithers); + } + + @SuppressWarnings("unchecked") + public static Coalesce coalesce() { + return INSTANCE; + } + + public static Either, Iterable> coalesce(Iterable> eithers) { + return Coalesce.coalesce().apply(eithers); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/monoid/builtin/And.java b/src/main/java/com/jnape/palatable/lambda/monoid/builtin/And.java index cd10613df..3b7610510 100644 --- a/src/main/java/com/jnape/palatable/lambda/monoid/builtin/And.java +++ b/src/main/java/com/jnape/palatable/lambda/monoid/builtin/And.java @@ -1,6 +1,7 @@ package com.jnape.palatable.lambda.monoid.builtin; import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.specialized.BiPredicate; import com.jnape.palatable.lambda.monoid.Monoid; /** @@ -9,7 +10,7 @@ * @see Or * @see Monoid */ -public class And implements Monoid { +public class And implements Monoid, BiPredicate { private static final And INSTANCE = new And(); @@ -26,6 +27,16 @@ public Boolean apply(Boolean x, Boolean y) { return x && y; } + @Override + public boolean test(Boolean x, Boolean y) { + return apply(x, y); + } + + @Override + public And flip() { + return this; + } + public static And and() { return INSTANCE; } diff --git a/src/main/java/com/jnape/palatable/lambda/monoid/builtin/Or.java b/src/main/java/com/jnape/palatable/lambda/monoid/builtin/Or.java index 55d1611d5..e4bc56b8c 100644 --- a/src/main/java/com/jnape/palatable/lambda/monoid/builtin/Or.java +++ b/src/main/java/com/jnape/palatable/lambda/monoid/builtin/Or.java @@ -1,6 +1,7 @@ package com.jnape.palatable.lambda.monoid.builtin; import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.specialized.BiPredicate; import com.jnape.palatable.lambda.monoid.Monoid; /** @@ -9,7 +10,7 @@ * @see And * @see Monoid */ -public class Or implements Monoid { +public class Or implements Monoid, BiPredicate { private static final Or INSTANCE = new Or(); @@ -26,6 +27,16 @@ public Boolean apply(Boolean x, Boolean y) { return x || y; } + @Override + public boolean test(Boolean x, Boolean y) { + return apply(x, y); + } + + @Override + public Or flip() { + return this; + } + public static Or or() { return INSTANCE; } diff --git a/src/main/java/com/jnape/palatable/lambda/monoid/builtin/Xor.java b/src/main/java/com/jnape/palatable/lambda/monoid/builtin/Xor.java index 5ebdb9b0e..a2fe7abaf 100644 --- a/src/main/java/com/jnape/palatable/lambda/monoid/builtin/Xor.java +++ b/src/main/java/com/jnape/palatable/lambda/monoid/builtin/Xor.java @@ -1,16 +1,19 @@ package com.jnape.palatable.lambda.monoid.builtin; import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.specialized.BiPredicate; import com.jnape.palatable.lambda.monoid.Monoid; /** - * Logical exclusive-or. Note that this implementation behaves as a cascade of binary exclusive-or operations, as is the - * only possible monoidal behavior when applied to an unknown number of inputs. + * Logical exclusive-or. Equivalent to logical ^. + *

+ * Note that this implementation behaves as a cascade of binary exclusive-or operations, as is the only possible + * monoidal behavior when applied to an unknown number of inputs. * * @see Or * @see And */ -public final class Xor implements Monoid { +public class Xor implements Monoid, BiPredicate { private static final Xor INSTANCE = new Xor(); @@ -24,7 +27,17 @@ public Boolean identity() { @Override public Boolean apply(Boolean x, Boolean y) { - return x ? !y : y; + return x ^ y; + } + + @Override + public boolean test(Boolean x, Boolean y) { + return apply(x, y); + } + + @Override + public Xor flip() { + return this; } public static Xor xor() { diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/CoalesceTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/CoalesceTest.java new file mode 100644 index 000000000..68db636c1 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/CoalesceTest.java @@ -0,0 +1,38 @@ +package com.jnape.palatable.lambda.functions.builtin.fn1; + +import org.junit.Test; + +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.Coalesce.coalesce; +import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; +import static org.junit.Assert.assertThat; +import static testsupport.matchers.IterableMatcher.isEmpty; +import static testsupport.matchers.IterableMatcher.iterates; +import static testsupport.matchers.LeftMatcher.isLeftThat; +import static testsupport.matchers.RightMatcher.isRightThat; + +public class CoalesceTest { + + @Test + public void empty() { + assertThat(coalesce(emptyList()), isRightThat(isEmpty())); + } + + @Test + public void allRights() { + assertThat(coalesce(asList(right(1), right(2), right(3))), isRightThat(iterates(1, 2, 3))); + } + + @Test + public void allLefts() { + assertThat(coalesce(asList(left("foo"), left("bar"), left("baz"))), isLeftThat(iterates("foo", "bar", "baz"))); + } + + @Test + public void someRightsAndLefts() { + assertThat(coalesce(asList(right(1), left("foo"), right(2), left("bar"), right(3), left("baz"))), + isLeftThat(iterates("foo", "bar", "baz"))); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/iterators/GroupingIteratorTest.java b/src/test/java/com/jnape/palatable/lambda/iterators/GroupingIteratorTest.java index bd8cb3bac..98084dea3 100644 --- a/src/test/java/com/jnape/palatable/lambda/iterators/GroupingIteratorTest.java +++ b/src/test/java/com/jnape/palatable/lambda/iterators/GroupingIteratorTest.java @@ -18,7 +18,7 @@ public class GroupingIteratorTest { @Mock private Iterator as; - private GroupingIterator groupingIterator; + private GroupingIterator groupingIterator; @Before public void setUp() { diff --git a/src/test/java/testsupport/matchers/IterableMatcher.java b/src/test/java/testsupport/matchers/IterableMatcher.java index 8b81d6d7d..5313e70af 100644 --- a/src/test/java/testsupport/matchers/IterableMatcher.java +++ b/src/test/java/testsupport/matchers/IterableMatcher.java @@ -9,11 +9,11 @@ import static java.util.Arrays.asList; import static org.apache.commons.lang3.builder.EqualsBuilder.reflectionEquals; -public class IterableMatcher extends BaseMatcher { +public class IterableMatcher extends BaseMatcher> { - private final Iterable expected; + private final Iterable expected; - public IterableMatcher(Iterable expected) { + private IterableMatcher(Iterable expected) { this.expected = expected; } @@ -29,9 +29,11 @@ public void describeTo(Description description) { @Override public void describeMismatch(Object item, Description description) { - if (item instanceof Iterable) - description.appendText("was <").appendText(stringify((Iterable) item)).appendText(">"); - else + if (item instanceof Iterable) { + if (description.toString().endsWith("but: ")) + description.appendText("was "); + description.appendText("<").appendText(stringify((Iterable) item)).appendText(">"); + } else super.describeMismatch(item, description); } @@ -69,11 +71,11 @@ private String stringify(Iterable iterable) { } @SafeVarargs - public static IterableMatcher iterates(Element... elements) { - return new IterableMatcher(asList(elements)); + public static IterableMatcher iterates(E... es) { + return new IterableMatcher<>(asList(es)); } - public static IterableMatcher isEmpty() { - return new IterableMatcher(new ArrayList()); + public static IterableMatcher isEmpty() { + return new IterableMatcher<>(new ArrayList<>()); } } diff --git a/src/test/java/testsupport/matchers/LeftMatcher.java b/src/test/java/testsupport/matchers/LeftMatcher.java new file mode 100644 index 000000000..7982965a8 --- /dev/null +++ b/src/test/java/testsupport/matchers/LeftMatcher.java @@ -0,0 +1,42 @@ +package testsupport.matchers; + +import com.jnape.palatable.lambda.adt.Either; +import org.hamcrest.Description; +import org.hamcrest.Matcher; +import org.hamcrest.TypeSafeMatcher; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; + +public final class LeftMatcher extends TypeSafeMatcher> { + + private final Matcher lMatcher; + + private LeftMatcher(Matcher lMatcher) { + this.lMatcher = lMatcher; + } + + @Override + protected boolean matchesSafely(Either actual) { + return actual.match(lMatcher::matches, constantly(false)); + } + + @Override + public void describeTo(Description description) { + description.appendText("Left value of "); + lMatcher.describeTo(description); + } + + @Override + protected void describeMismatchSafely(Either item, Description mismatchDescription) { + mismatchDescription.appendText("was "); + item.peek(l -> { + mismatchDescription.appendText("Left value of "); + lMatcher.describeMismatch(l, mismatchDescription); + }, + r -> mismatchDescription.appendValue(item)); + } + + public static LeftMatcher isLeftThat(Matcher lMatcher) { + return new LeftMatcher<>(lMatcher); + } +} diff --git a/src/test/java/testsupport/matchers/RightMatcher.java b/src/test/java/testsupport/matchers/RightMatcher.java new file mode 100644 index 000000000..08965bf2f --- /dev/null +++ b/src/test/java/testsupport/matchers/RightMatcher.java @@ -0,0 +1,42 @@ +package testsupport.matchers; + +import com.jnape.palatable.lambda.adt.Either; +import org.hamcrest.Description; +import org.hamcrest.Matcher; +import org.hamcrest.TypeSafeMatcher; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; + +public final class RightMatcher extends TypeSafeMatcher> { + + private final Matcher rMatcher; + + private RightMatcher(Matcher rMatcher) { + this.rMatcher = rMatcher; + } + + @Override + protected boolean matchesSafely(Either actual) { + return actual.match(constantly(false), rMatcher::matches); + } + + @Override + public void describeTo(Description description) { + description.appendText("Right value of "); + rMatcher.describeTo(description); + } + + @Override + protected void describeMismatchSafely(Either item, Description mismatchDescription) { + mismatchDescription.appendText("was "); + item.peek(l -> mismatchDescription.appendValue(item), + r -> { + mismatchDescription.appendText("Right value of "); + rMatcher.describeMismatch(r, mismatchDescription); + }); + } + + public static RightMatcher isRightThat(Matcher rMatcher) { + return new RightMatcher<>(rMatcher); + } +} From 920229033dcafe90ba164319d0107d372f16e537 Mon Sep 17 00:00:00 2001 From: jnape Date: Sun, 8 Oct 2017 14:23:27 -0500 Subject: [PATCH 10/32] Adding into, used for applying a destructured tuple to a bifunction --- .../lambda/functions/builtin/fn2/Into.java | 41 +++++++++++++++++++ .../functions/builtin/fn2/IntoTest.java | 15 +++++++ 2 files changed, 56 insertions(+) create mode 100644 src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Into.java create mode 100644 src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/IntoTest.java diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Into.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Into.java new file mode 100644 index 000000000..c3c8381ed --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Into.java @@ -0,0 +1,41 @@ +package com.jnape.palatable.lambda.functions.builtin.fn2; + +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.function.BiFunction; + +/** + * Given a {@link BiFunction}<A, B, C> and a {@link Tuple2}<A, B>, destructure the + * tuple and apply the slots as arguments to the function, returning the result. + * + * @param the first argument type + * @param the second argument type + * @param the result type + */ +public final class Into implements Fn2, Tuple2, C> { + + private static final Into INSTANCE = new Into(); + + private Into() { + } + + @Override + public C apply(BiFunction fn, Tuple2 tuple) { + return tuple.into(fn); + } + + @SuppressWarnings("unchecked") + public static Into into() { + return INSTANCE; + } + + public static Fn1, C> into(BiFunction fn) { + return Into.into().apply(fn); + } + + public static C into(BiFunction fn, Tuple2 tuple) { + return Into.into(fn).apply(tuple); + } +} diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/IntoTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/IntoTest.java new file mode 100644 index 000000000..dfcc7b064 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/IntoTest.java @@ -0,0 +1,15 @@ +package com.jnape.palatable.lambda.functions.builtin.fn2; + +import org.junit.Test; + +import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Into.into; +import static org.junit.Assert.assertEquals; + +public class IntoTest { + + @Test + public void appliesTupleToFunction() { + assertEquals((Integer) 3, into((a, b) -> a + b, tuple(1, 2))); + } +} \ No newline at end of file From 01aed8d9490a8d3e9afaf012c0e93f6badfa3acc Mon Sep 17 00:00:00 2001 From: jnape Date: Sat, 14 Oct 2017 17:38:55 -0500 Subject: [PATCH 11/32] LambdaOptional and LambdaIterable, adapting jdk types lambda interfaces --- CHANGELOG.md | 7 ++ .../palatable/lambda/traversable/Lambda.java | 34 +++++ .../lambda/traversable/LambdaIterable.java | 117 ++++++++++++++++++ .../lambda/traversable/LambdaOptional.java | 96 ++++++++++++++ .../lambda/traversable/Traversables.java | 7 ++ .../traversable/LambdaIterableTest.java | 36 ++++++ .../traversable/LambdaOptionalTest.java | 19 +++ .../lambda/traversable/TraversablesTest.java | 24 ---- 8 files changed, 316 insertions(+), 24 deletions(-) create mode 100644 src/main/java/com/jnape/palatable/lambda/traversable/Lambda.java create mode 100644 src/main/java/com/jnape/palatable/lambda/traversable/LambdaIterable.java create mode 100644 src/main/java/com/jnape/palatable/lambda/traversable/LambdaOptional.java create mode 100644 src/test/java/com/jnape/palatable/lambda/traversable/LambdaIterableTest.java create mode 100644 src/test/java/com/jnape/palatable/lambda/traversable/LambdaOptionalTest.java delete mode 100644 src/test/java/com/jnape/palatable/lambda/traversable/TraversablesTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 74a5cf980..f3aa6bd0f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,12 +9,19 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/). - `Snoc`, for lazily appending an element to the end of an `Iterable` - `Coalesce`, for folding an `Iterable>` into an `Either, Iterable>` - `And`, `Or`, and `Xor` all gain `BiPredicate` properties +- `LambdaOptional` and `LambdaIterable`, adapters for `Optional` and `Iterable` that support lambda types +- `Lambda`, providing static factory methods for `LambdaOptional` and `LambdaIterable` ### Removed - `Fn1#then(Function)`, deprecated in previous release - `Fn1#adapt(Function function)`, deprecated in previous release - `Fn2#adapt(BiFunction biFunction)`, deprecated in previous release +### Deprecated +- `Traversables` and all methods therein, in favor of new `Lambda` methods +- `TraversableOptional` in favor of `LambdaOptional` +- `TraversableIterable` in favor of `LambdaIterable` + ## [1.6.3] - 2017-09-27 ### Fixed - `ConcatenatingIterator` bug where deeply nested `xs` skip elements diff --git a/src/main/java/com/jnape/palatable/lambda/traversable/Lambda.java b/src/main/java/com/jnape/palatable/lambda/traversable/Lambda.java new file mode 100644 index 000000000..150bef15d --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/traversable/Lambda.java @@ -0,0 +1,34 @@ +package com.jnape.palatable.lambda.traversable; + +import java.util.Optional; + +/** + * Static factory methods for adapting core JDK types to Lambda interfaces. + */ +public final class Lambda { + + private Lambda() { + } + + /** + * Wrap an {@link Iterable} in a {@link LambdaIterable}. + * + * @param as the Iterable + * @param the Iterable element type + * @return a LambdaIterable + */ + public static LambdaIterable lambda(Iterable as) { + return LambdaIterable.wrap(as); + } + + /** + * Wrap an {@link Optional} in a {@link LambdaOptional}. + * + * @param opt the Optional + * @param the Optional type + * @return a LambdaOptional + */ + public static LambdaOptional lambda(Optional opt) { + return LambdaOptional.wrap(opt); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/traversable/LambdaIterable.java b/src/main/java/com/jnape/palatable/lambda/traversable/LambdaIterable.java new file mode 100644 index 000000000..3564732da --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/traversable/LambdaIterable.java @@ -0,0 +1,117 @@ +package com.jnape.palatable.lambda.traversable; + +import com.jnape.palatable.lambda.functor.Applicative; + +import java.util.Iterator; +import java.util.Objects; +import java.util.function.Function; + +import static com.jnape.palatable.lambda.functions.builtin.fn2.CartesianProduct.cartesianProduct; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Cons.cons; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Into.into; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Map.map; +import static com.jnape.palatable.lambda.functions.builtin.fn3.FoldRight.foldRight; +import static java.util.Collections.emptyList; +import static java.util.Collections.singleton; + +/** + * Wrap an {@link Iterable} in a {@link Traversable} such that {@link Traversable#traverse(Function, Function)} applies + * its computation against each element of the wrapped {@link Iterable}. Returns the result of pure if the + * wrapped {@link Iterable} is empty. + * + * @param the Iterable element type + */ +public final class LambdaIterable implements Applicative, Traversable { + private final Iterable as; + + @SuppressWarnings("unchecked") + private LambdaIterable(Iterable as) { + this.as = (Iterable) as; + } + + /** + * Unwrap the underlying {@link Iterable}. + * + * @return the wrapped Iterable + */ + public Iterable unwrap() { + return as; + } + + @Override + public LambdaIterable fmap(Function fn) { + return wrap(map(fn, as)); + } + + @Override + public LambdaIterable pure(B b) { + return wrap(singleton(b)); + } + + @Override + @SuppressWarnings("Convert2MethodRef") + public LambdaIterable zip(Applicative, LambdaIterable> appFn) { + return wrap(map(into((f, x) -> f.apply(x)), + cartesianProduct(appFn.>>coerce().unwrap(), as))); + } + + @Override + public LambdaIterable discardL(Applicative appB) { + return Applicative.super.discardL(appB).coerce(); + } + + @Override + public LambdaIterable discardR(Applicative appB) { + return Applicative.super.discardR(appB).coerce(); + } + + @Override + public Applicative, App> traverse( + Function> fn, + Function, ? extends Applicative, App>> pure) { + return foldRight((a, appTrav) -> appTrav.zip(fn.apply(a).fmap(b -> bs -> LambdaIterable.wrap(cons(b, bs.unwrap())))), + pure.apply(LambdaIterable.empty()).fmap(ti -> (LambdaIterable) ti), + as); + } + + @Override + public boolean equals(Object other) { + if (other instanceof LambdaIterable) { + Iterator xs = as.iterator(); + Iterator ys = ((LambdaIterable) other).as.iterator(); + + while (xs.hasNext() && ys.hasNext()) + if (!Objects.equals(xs.next(), ys.next())) + return false; + + return xs.hasNext() == ys.hasNext(); + } + return false; + } + + @Override + public int hashCode() { + return Objects.hash(as); + } + + /** + * Wrap an {@link Iterable} in a TraversableIterable. + * + * @param as the Iterable + * @param the Iterable element type + * @return the Iterable wrapped in a TraversableIterable + */ + public static LambdaIterable wrap(Iterable as) { + return new LambdaIterable<>(as); + } + + /** + * Construct an empty TraversableIterable by wrapping {@link java.util.Collections#emptyList()}. + * + * @param the Iterable element type + * @return a TraversableIterable wrapping Collections.emptyList() + */ + public static LambdaIterable empty() { + return wrap(emptyList()); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/traversable/LambdaOptional.java b/src/main/java/com/jnape/palatable/lambda/traversable/LambdaOptional.java new file mode 100644 index 000000000..3d38d5742 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/traversable/LambdaOptional.java @@ -0,0 +1,96 @@ +package com.jnape.palatable.lambda.traversable; + +import com.jnape.palatable.lambda.functor.Applicative; +import com.jnape.palatable.lambda.functor.Functor; + +import java.util.Objects; +import java.util.Optional; +import java.util.function.Function; + +/** + * Adapt an {@link Optional} to support generic lambda interfaces like {@link Functor}, {@link Applicative}, + * {@link Traversable}, etc. + * + * @param the Optional parameter type + */ +public final class LambdaOptional implements Applicative, Traversable { + private final Optional delegate; + + private LambdaOptional(Optional delegate) { + this.delegate = delegate; + } + + /** + * Unwrap the underlying {@link Optional}. + * + * @return the wrapped Optional + */ + public Optional unwrap() { + return delegate; + } + + @Override + public LambdaOptional fmap(Function fn) { + return new LambdaOptional<>(delegate.map(fn)); + } + + @Override + public LambdaOptional pure(B b) { + return wrap(Optional.of(b)); + } + + @Override + public LambdaOptional zip(Applicative, LambdaOptional> appFn) { + return wrap(appFn.>>coerce().unwrap().flatMap(delegate::map)); + } + + @Override + public LambdaOptional discardL(Applicative appB) { + return Applicative.super.discardL(appB).coerce(); + } + + @Override + public LambdaOptional discardR(Applicative appB) { + return Applicative.super.discardR(appB).coerce(); + } + + @Override + public Applicative, App> traverse( + Function> fn, + Function, ? extends Applicative, App>> pure) { + return fmap(fn).delegate.map(app -> app.fmap(Optional::of).fmap(LambdaOptional::wrap)) + .orElseGet(() -> pure.apply(LambdaOptional.empty()).fmap(x -> (LambdaOptional) x)); + } + + @Override + public boolean equals(Object other) { + return other instanceof LambdaOptional + && Objects.equals(delegate, ((LambdaOptional) other).delegate); + } + + @Override + public int hashCode() { + return Objects.hash(delegate); + } + + /** + * Wrap an {@link Optional} in a TraversableOptional. + * + * @param optional the Optional + * @param the Optional parameter type + * @return the Optional wrapped in a TraversableOptional + */ + public static LambdaOptional wrap(Optional optional) { + return new LambdaOptional<>(optional); + } + + /** + * Construct an empty TraversableOptional by wrapping {@link Optional#empty()}. + * + * @param the optional parameter type + * @return a TraversableOptional wrapping Optional.empty() + */ + public static LambdaOptional empty() { + return LambdaOptional.wrap(Optional.empty()); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/traversable/Traversables.java b/src/main/java/com/jnape/palatable/lambda/traversable/Traversables.java index 4fc1443c4..210f3e428 100644 --- a/src/main/java/com/jnape/palatable/lambda/traversable/Traversables.java +++ b/src/main/java/com/jnape/palatable/lambda/traversable/Traversables.java @@ -4,7 +4,10 @@ /** * Static factory methods for adapting core JDK types to {@link Traversable}. + * + * @deprecated in favor of {@link Lambda} methods */ +@Deprecated public final class Traversables { private Traversables() { @@ -16,7 +19,9 @@ private Traversables() { * @param as the Iterable * @param the Iterable element type * @return a Traversable wrapper around as + * @deprecated in favor of {@link Lambda#lambda(Iterable)} */ + @Deprecated public static TraversableIterable traversable(Iterable as) { return TraversableIterable.wrap(as); } @@ -27,7 +32,9 @@ public static TraversableIterable traversable(Iterable as) { * @param opt the Optional * @param the Optional type * @return a Traversable wrapper around opt + * @deprecated in favor of {@link Lambda#lambda(Optional)} */ + @Deprecated public static TraversableOptional traversable(Optional opt) { return TraversableOptional.wrap(opt); } diff --git a/src/test/java/com/jnape/palatable/lambda/traversable/LambdaIterableTest.java b/src/test/java/com/jnape/palatable/lambda/traversable/LambdaIterableTest.java new file mode 100644 index 000000000..b72308be1 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/traversable/LambdaIterableTest.java @@ -0,0 +1,36 @@ +package com.jnape.palatable.lambda.traversable; + +import com.jnape.palatable.traitor.annotations.TestTraits; +import com.jnape.palatable.traitor.framework.Subjects; +import com.jnape.palatable.traitor.runners.Traits; +import org.junit.Test; +import org.junit.runner.RunWith; +import testsupport.traits.ApplicativeLaws; +import testsupport.traits.FunctorLaws; +import testsupport.traits.TraversableLaws; + +import java.util.function.Function; + +import static com.jnape.palatable.lambda.functions.builtin.fn2.Replicate.replicate; +import static com.jnape.palatable.traitor.framework.Subjects.subjects; +import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; +import static java.util.Collections.singleton; +import static org.junit.Assert.assertThat; +import static testsupport.matchers.IterableMatcher.iterates; + +@RunWith(Traits.class) +public class LambdaIterableTest { + + @TestTraits({FunctorLaws.class, ApplicativeLaws.class, TraversableLaws.class}) + public Subjects> testSubject() { + return subjects(LambdaIterable.wrap(emptyList()), LambdaIterable.wrap(singleton(1)), LambdaIterable.wrap(replicate(100, 1))); + } + + @Test + public void zipAppliesCartesianProductOfFunctionsAndValues() { + LambdaIterable> fns = LambdaIterable.wrap(asList(x -> x + 1, x -> x - 1)); + LambdaIterable xs = LambdaIterable.wrap(asList(1, 2, 3)); + assertThat(xs.zip(fns).unwrap(), iterates(2, 3, 4, 0, 1, 2)); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/traversable/LambdaOptionalTest.java b/src/test/java/com/jnape/palatable/lambda/traversable/LambdaOptionalTest.java new file mode 100644 index 000000000..0ddfc63a4 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/traversable/LambdaOptionalTest.java @@ -0,0 +1,19 @@ +package com.jnape.palatable.lambda.traversable; + +import com.jnape.palatable.traitor.annotations.TestTraits; +import com.jnape.palatable.traitor.runners.Traits; +import org.junit.runner.RunWith; +import testsupport.traits.ApplicativeLaws; +import testsupport.traits.FunctorLaws; +import testsupport.traits.TraversableLaws; + +import java.util.Optional; + +@RunWith(Traits.class) +public class LambdaOptionalTest { + + @TestTraits({FunctorLaws.class, ApplicativeLaws.class, TraversableLaws.class}) + public LambdaOptional testSubject() { + return LambdaOptional.wrap(Optional.of(new Object())); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/traversable/TraversablesTest.java b/src/test/java/com/jnape/palatable/lambda/traversable/TraversablesTest.java deleted file mode 100644 index e11e7e12c..000000000 --- a/src/test/java/com/jnape/palatable/lambda/traversable/TraversablesTest.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.jnape.palatable.lambda.traversable; - -import org.junit.Test; - -import java.util.Optional; - -import static com.jnape.palatable.lambda.traversable.Traversables.traversable; -import static java.util.Arrays.asList; -import static java.util.Collections.emptyList; -import static org.junit.Assert.assertEquals; - -public class TraversablesTest { - - @Test - public void staticFactoryMethods() { - Iterable ints = asList(1, 2, 3); - assertEquals(TraversableIterable.wrap(ints), traversable(ints)); - assertEquals(TraversableIterable.empty(), traversable(emptyList())); - - Optional optString = Optional.of("foo"); - assertEquals(TraversableOptional.wrap(optString), traversable(optString)); - assertEquals(TraversableOptional.empty(), traversable(Optional.empty())); - } -} \ No newline at end of file From a5995c1b8d62794a74a33f65d69b2a3086280d58 Mon Sep 17 00:00:00 2001 From: jnape Date: Sat, 14 Oct 2017 17:49:54 -0500 Subject: [PATCH 12/32] obviate the applicative behavior for LambdaIterable in its javadoc --- .../palatable/lambda/traversable/LambdaIterable.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/main/java/com/jnape/palatable/lambda/traversable/LambdaIterable.java b/src/main/java/com/jnape/palatable/lambda/traversable/LambdaIterable.java index 3564732da..481103c58 100644 --- a/src/main/java/com/jnape/palatable/lambda/traversable/LambdaIterable.java +++ b/src/main/java/com/jnape/palatable/lambda/traversable/LambdaIterable.java @@ -48,6 +48,16 @@ public LambdaIterable pure(B b) { return wrap(singleton(b)); } + /** + * {@inheritDoc} + *

+ * In this case, calculate the cartesian product of applications of all functions in appFn to all + * values wrapped by this {@link LambdaIterable}. + * + * @param appFn the other applicative instance + * @param the new parameter type + * @return the zipped LambdaIterable + */ @Override @SuppressWarnings("Convert2MethodRef") public LambdaIterable zip(Applicative, LambdaIterable> appFn) { From 57214ffac08416c6ab1f8694df858fde97e4b529 Mon Sep 17 00:00:00 2001 From: jnape Date: Sun, 22 Oct 2017 14:11:54 -0500 Subject: [PATCH 13/32] Profunctor#diMap/L/R parameters allow variance --- CHANGELOG.md | 3 +++ .../com/jnape/palatable/lambda/functions/Fn1.java | 10 +++++----- .../lambda/functions/specialized/Predicate.java | 3 +-- .../jnape/palatable/lambda/functor/Profunctor.java | 6 +++--- .../java/com/jnape/palatable/lambda/lens/Lens.java | 14 ++++++++++++-- .../InvocationRecordingProfunctor.java | 3 ++- 6 files changed, 26 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f3aa6bd0f..3a232cac6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,9 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/). - `TraversableOptional` in favor of `LambdaOptional` - `TraversableIterable` in favor of `LambdaIterable` +### Changed +- `Profunctor#diMap/L/R` parameters allow variance + ## [1.6.3] - 2017-09-27 ### Fixed - `ConcatenatingIterator` bug where deeply nested `xs` skip elements 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 dcc62f5d4..677c3f6b4 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/Fn1.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/Fn1.java @@ -87,8 +87,8 @@ default Fn1 discardR(Applicative> appB) { * @return a new function from Z (the new argument type) to B (the same result) */ @Override - default Fn1 diMapL(Function fn) { - return (Fn1) Profunctor.super.diMapL(fn); + default Fn1 diMapL(Function fn) { + return (Fn1) Profunctor.super.diMapL(fn); } /** @@ -100,8 +100,8 @@ default Fn1 diMapL(Function fn) { * @return a new function from A (the same argument type) to C (the new result type) */ @Override - default Fn1 diMapR(Function fn) { - return (Fn1) Profunctor.super.diMapR(fn); + default Fn1 diMapR(Function fn) { + return (Fn1) Profunctor.super.diMapR(fn); } /** @@ -114,7 +114,7 @@ default Fn1 diMapR(Function fn) { * @return a new function from Z (the new argument type) to C (the new result type) */ @Override - default Fn1 diMap(Function lFn, Function rFn) { + default Fn1 diMap(Function lFn, Function rFn) { return lFn.andThen(this).andThen(rFn)::apply; } diff --git a/src/main/java/com/jnape/palatable/lambda/functions/specialized/Predicate.java b/src/main/java/com/jnape/palatable/lambda/functions/specialized/Predicate.java index 811fcf283..03eb6e0b8 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/specialized/Predicate.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/specialized/Predicate.java @@ -37,8 +37,7 @@ default Predicate compose(Function before) { * {@inheritDoc} */ @Override - @SuppressWarnings("unchecked") - default Predicate diMapL(Function fn) { + default Predicate diMapL(Function fn) { return Fn1.super.diMapL(fn)::apply; } diff --git a/src/main/java/com/jnape/palatable/lambda/functor/Profunctor.java b/src/main/java/com/jnape/palatable/lambda/functor/Profunctor.java index d20045079..95de29c54 100644 --- a/src/main/java/com/jnape/palatable/lambda/functor/Profunctor.java +++ b/src/main/java/com/jnape/palatable/lambda/functor/Profunctor.java @@ -31,7 +31,7 @@ public interface Profunctor { * @param fn the mapping function * @return a profunctor over Z (the new left parameter type) and C (the same right parameter type) */ - default Profunctor diMapL(Function fn) { + default Profunctor diMapL(Function fn) { return diMap(fn, id()); } @@ -43,7 +43,7 @@ default Profunctor diMapL(Function fn) { * @param fn the mapping function * @return a profunctor over A (the same left parameter type) and C (the new right parameter type) */ - default Profunctor diMapR(Function fn) { + default Profunctor diMapR(Function fn) { return diMap(id(), fn); } @@ -57,5 +57,5 @@ default Profunctor diMapR(Function fn) { * @param rFn the right parameter mapping function * @return a profunctor over Z (the new left parameter type) and C (the new right parameter type) */ - Profunctor diMap(Function lFn, Function rFn); + Profunctor diMap(Function lFn, Function rFn); } diff --git a/src/main/java/com/jnape/palatable/lambda/lens/Lens.java b/src/main/java/com/jnape/palatable/lambda/lens/Lens.java index dca9a2542..3ef19506c 100644 --- a/src/main/java/com/jnape/palatable/lambda/lens/Lens.java +++ b/src/main/java/com/jnape/palatable/lambda/lens/Lens.java @@ -181,8 +181,18 @@ default Lens discardR(Applicative> appB) { } @Override - default Lens diMap(Function lFn, Function rFn) { - return mapS(lFn).mapT(rFn); + default Lens diMapL(Function fn) { + return (Lens) Profunctor.super.diMapL(fn); + } + + @Override + default Lens diMapR(Function fn) { + return (Lens) Profunctor.super.diMapR(fn); + } + + @Override + default Lens diMap(Function lFn, Function rFn) { + return this.mapS(lFn).mapT(rFn); } /** diff --git a/src/test/java/testsupport/applicatives/InvocationRecordingProfunctor.java b/src/test/java/testsupport/applicatives/InvocationRecordingProfunctor.java index 1e7c690c4..4f440bb38 100644 --- a/src/test/java/testsupport/applicatives/InvocationRecordingProfunctor.java +++ b/src/test/java/testsupport/applicatives/InvocationRecordingProfunctor.java @@ -17,7 +17,8 @@ public InvocationRecordingProfunctor(AtomicReference leftFn, @Override @SuppressWarnings("unchecked") - public InvocationRecordingProfunctor diMap(Function lFn, Function rFn) { + public InvocationRecordingProfunctor diMap(Function lFn, + Function rFn) { leftFn.set(lFn); rightFn.set(rFn); return (InvocationRecordingProfunctor) this; From d8191ea5e96b43484ba4bb53fac72c35be78efb8 Mon Sep 17 00:00:00 2001 From: jnape Date: Sun, 22 Oct 2017 14:51:54 -0500 Subject: [PATCH 14/32] Contravariant arrives --- CHANGELOG.md | 2 ++ .../jnape/palatable/lambda/functions/Fn1.java | 5 +++ .../lambda/functor/Contravariant.java | 27 ++++++++++++++++ .../palatable/lambda/functor/Profunctor.java | 32 ++++++++++++------- .../com/jnape/palatable/lambda/lens/Lens.java | 6 ++++ 5 files changed, 60 insertions(+), 12 deletions(-) create mode 100644 src/main/java/com/jnape/palatable/lambda/functor/Contravariant.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 3a232cac6..4c6bf8e4c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/). - `And`, `Or`, and `Xor` all gain `BiPredicate` properties - `LambdaOptional` and `LambdaIterable`, adapters for `Optional` and `Iterable` that support lambda types - `Lambda`, providing static factory methods for `LambdaOptional` and `LambdaIterable` +- `Contravariant`, an interface representing functors that map contravariantly over their parameters +- `Profunctor` extends `Contravariant` ### Removed - `Fn1#then(Function)`, deprecated in previous release 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 677c3f6b4..12e84d9f6 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/Fn1.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/Fn1.java @@ -118,6 +118,11 @@ default Fn1 diMap(Function lFn, Function Fn1 contraMap(Function fn) { + return (Fn1) Profunctor.super.contraMap(fn); + } + /** * Override of {@link Function#compose(Function)}, returning an instance of Fn1 for compatibility. * Right-to-left composition. diff --git a/src/main/java/com/jnape/palatable/lambda/functor/Contravariant.java b/src/main/java/com/jnape/palatable/lambda/functor/Contravariant.java new file mode 100644 index 000000000..07d548d1a --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functor/Contravariant.java @@ -0,0 +1,27 @@ +package com.jnape.palatable.lambda.functor; + +import java.util.function.Function; + +/** + * The contravariant functor (or "co-functor"); that is, a functor that maps contravariantly (A <- B) + * over its parameter. + * Contravariant functors are not necessarily {@link Functor}s. + *

+ * For more information, read about Contravariant Functors. + * + * @param the type of the parameter + * @param the unification parameter + * @see Profunctor + */ +public interface Contravariant { + + /** + * Contravariantly map A <- B. + * + * @param fn the mapping function + * @param the new parameter type + * @return the mapped Contravariant functor instance + */ + Contravariant contraMap(Function fn); +} diff --git a/src/main/java/com/jnape/palatable/lambda/functor/Profunctor.java b/src/main/java/com/jnape/palatable/lambda/functor/Profunctor.java index 95de29c54..0dd9a1e67 100644 --- a/src/main/java/com/jnape/palatable/lambda/functor/Profunctor.java +++ b/src/main/java/com/jnape/palatable/lambda/functor/Profunctor.java @@ -8,8 +8,7 @@ import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; /** - * A dually-parametric functor that maps contravariantly over the left parameter and covariantly over the - * right. + * A dually-parametric functor that maps contravariantly over the left parameter and covariantly over the right. *

* For more information, read about Profunctors. * @@ -18,11 +17,24 @@ * @param The unification parameter * @see Functor * @see Bifunctor + * @see Contravariant * @see Fn1 * @see Lens */ @FunctionalInterface -public interface Profunctor { +public interface Profunctor extends Contravariant> { + + /** + * Dually map contravariantly over the left parameter and covariantly over the right parameter. This is isomorphic + * to diMapL(lFn).diMapR(rFn).¬ + * + * @param the new left parameter type + * @param the new right parameter type + * @param lFn the left parameter mapping function + * @param rFn the right parameter mapping function + * @return a profunctor over Z (the new left parameter type) and C (the new right parameter type) + */ + Profunctor diMap(Function lFn, Function rFn); /** * Contravariantly map over the left parameter. @@ -48,14 +60,10 @@ default Profunctor diMapR(Function fn) { } /** - * Dually map contravariantly over the left parameter and covariantly over the right parameter. This is isomorphic - * to diMapL(lFn).diMapR(rFn). - * - * @param the new left parameter type - * @param the new right parameter type - * @param lFn the left parameter mapping function - * @param rFn the right parameter mapping function - * @return a profunctor over Z (the new left parameter type) and C (the new right parameter type) + * @inheritDoc */ - Profunctor diMap(Function lFn, Function rFn); + @Override + default Profunctor contraMap(Function fn) { + return diMapL(fn); + } } diff --git a/src/main/java/com/jnape/palatable/lambda/lens/Lens.java b/src/main/java/com/jnape/palatable/lambda/lens/Lens.java index 3ef19506c..750ddcb68 100644 --- a/src/main/java/com/jnape/palatable/lambda/lens/Lens.java +++ b/src/main/java/com/jnape/palatable/lambda/lens/Lens.java @@ -4,6 +4,7 @@ import com.jnape.palatable.lambda.functor.Applicative; import com.jnape.palatable.lambda.functor.Functor; import com.jnape.palatable.lambda.functor.Profunctor; +import com.jnape.palatable.lambda.monad.Monad; import java.util.function.BiFunction; import java.util.function.Function; @@ -195,6 +196,11 @@ default Lens diMap(Function lFn, Func return this.mapS(lFn).mapT(rFn); } + @Override + default Lens contraMap(Function fn) { + return (Lens) Profunctor.super.contraMap(fn); + } + /** * Contravariantly map S to R, yielding a new lens. * From b17b1a55363b334e1563834ea3374a596fb3caf4 Mon Sep 17 00:00:00 2001 From: jnape Date: Sat, 21 Oct 2017 13:43:31 -0500 Subject: [PATCH 15/32] Swapping TraversableIterable/Optional for LambdaIterable/Optional --- .../functions/builtin/fn2/Sequence.java | 17 +++++++-------- .../lambda/traversable/LambdaOptional.java | 7 ++++--- .../traversable/TraversableIterable.java | 2 ++ .../traversable/TraversableOptional.java | 2 ++ .../traversable/TraversableIterableTest.java | 20 ------------------ .../traversable/TraversableOptionalTest.java | 21 ------------------- 6 files changed, 16 insertions(+), 53 deletions(-) delete mode 100644 src/test/java/com/jnape/palatable/lambda/traversable/TraversableIterableTest.java delete mode 100644 src/test/java/com/jnape/palatable/lambda/traversable/TraversableOptionalTest.java diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Sequence.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Sequence.java index 228536162..4df1d3360 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Sequence.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Sequence.java @@ -3,9 +3,9 @@ import com.jnape.palatable.lambda.functions.Fn1; import com.jnape.palatable.lambda.functions.Fn2; import com.jnape.palatable.lambda.functor.Applicative; +import com.jnape.palatable.lambda.traversable.LambdaIterable; +import com.jnape.palatable.lambda.traversable.LambdaOptional; import com.jnape.palatable.lambda.traversable.Traversable; -import com.jnape.palatable.lambda.traversable.TraversableIterable; -import com.jnape.palatable.lambda.traversable.TraversableOptional; import java.util.Optional; import java.util.function.Function; @@ -69,18 +69,17 @@ TravApp extends Traversable, Trav>> AppTrav sequen public static , App>, IterableApp extends Iterable>> Fn1, ? extends AppIterable>, AppIterable> sequence( IterableApp optionalApp) { return pure -> - (AppIterable) sequence(TraversableIterable.wrap(optionalApp), x -> pure.apply(((TraversableIterable) x).unwrap()) - .fmap(TraversableIterable::wrap)) - .fmap(TraversableIterable::unwrap); + (AppIterable) sequence(LambdaIterable.wrap(optionalApp), x -> pure.apply(((LambdaIterable) x).unwrap()) + .fmap(LambdaIterable::wrap)) + .fmap(LambdaIterable::unwrap); } @SuppressWarnings("unchecked") public static , App>, OptionalApp extends Optional>> Fn1, ? extends AppOptional>, AppOptional> sequence( OptionalApp optionalApp) { - return pure -> - (AppOptional) sequence(TraversableOptional.wrap(optionalApp), x -> pure.apply(((TraversableOptional) x).unwrap()) - .fmap(TraversableOptional::wrap)) - .fmap(TraversableOptional::unwrap); + return pure -> (AppOptional) sequence(LambdaOptional.wrap(optionalApp), x -> pure.apply(((LambdaOptional) x).unwrap()) + .fmap(LambdaOptional::wrap)) + .fmap(LambdaOptional::unwrap); } public static , App>, diff --git a/src/main/java/com/jnape/palatable/lambda/traversable/LambdaOptional.java b/src/main/java/com/jnape/palatable/lambda/traversable/LambdaOptional.java index 3d38d5742..40731e8da 100644 --- a/src/main/java/com/jnape/palatable/lambda/traversable/LambdaOptional.java +++ b/src/main/java/com/jnape/palatable/lambda/traversable/LambdaOptional.java @@ -16,8 +16,9 @@ public final class LambdaOptional implements Applicative, Traversable { private final Optional delegate; - private LambdaOptional(Optional delegate) { - this.delegate = delegate; + @SuppressWarnings("unchecked") + private LambdaOptional(Optional delegate) { + this.delegate = (Optional) delegate; } /** @@ -80,7 +81,7 @@ public int hashCode() { * @param the Optional parameter type * @return the Optional wrapped in a TraversableOptional */ - public static LambdaOptional wrap(Optional optional) { + public static LambdaOptional wrap(Optional optional) { return new LambdaOptional<>(optional); } diff --git a/src/main/java/com/jnape/palatable/lambda/traversable/TraversableIterable.java b/src/main/java/com/jnape/palatable/lambda/traversable/TraversableIterable.java index 340b04a79..4c71cc589 100644 --- a/src/main/java/com/jnape/palatable/lambda/traversable/TraversableIterable.java +++ b/src/main/java/com/jnape/palatable/lambda/traversable/TraversableIterable.java @@ -17,7 +17,9 @@ * wrapped {@link Iterable} is empty. * * @param the Iterable element type + * @deprecated in favor of {@link LambdaIterable} */ +@Deprecated public final class TraversableIterable implements Traversable { private final Iterable as; diff --git a/src/main/java/com/jnape/palatable/lambda/traversable/TraversableOptional.java b/src/main/java/com/jnape/palatable/lambda/traversable/TraversableOptional.java index 5bd769526..f5a6057fc 100644 --- a/src/main/java/com/jnape/palatable/lambda/traversable/TraversableOptional.java +++ b/src/main/java/com/jnape/palatable/lambda/traversable/TraversableOptional.java @@ -12,7 +12,9 @@ * Returns the result of pure if the wrapped {@link Optional} is empty. * * @param the Optional parameter type + * @deprecated in favor of {@link LambdaOptional} */ +@Deprecated public final class TraversableOptional implements Traversable { private final Optional delegate; diff --git a/src/test/java/com/jnape/palatable/lambda/traversable/TraversableIterableTest.java b/src/test/java/com/jnape/palatable/lambda/traversable/TraversableIterableTest.java deleted file mode 100644 index 21a740b29..000000000 --- a/src/test/java/com/jnape/palatable/lambda/traversable/TraversableIterableTest.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.jnape.palatable.lambda.traversable; - -import com.jnape.palatable.traitor.annotations.TestTraits; -import com.jnape.palatable.traitor.framework.Subjects; -import com.jnape.palatable.traitor.runners.Traits; -import org.junit.runner.RunWith; -import testsupport.traits.FunctorLaws; -import testsupport.traits.TraversableLaws; - -import static com.jnape.palatable.traitor.framework.Subjects.subjects; -import static java.util.Arrays.asList; - -@RunWith(Traits.class) -public class TraversableIterableTest { - - @TestTraits({FunctorLaws.class, TraversableLaws.class}) - public Subjects> testSubject() { - return subjects(TraversableIterable.empty(), TraversableIterable.wrap(asList(1, 2, 3))); - } -} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/traversable/TraversableOptionalTest.java b/src/test/java/com/jnape/palatable/lambda/traversable/TraversableOptionalTest.java deleted file mode 100644 index 13c330cb6..000000000 --- a/src/test/java/com/jnape/palatable/lambda/traversable/TraversableOptionalTest.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.jnape.palatable.lambda.traversable; - -import com.jnape.palatable.traitor.annotations.TestTraits; -import com.jnape.palatable.traitor.framework.Subjects; -import com.jnape.palatable.traitor.runners.Traits; -import org.junit.runner.RunWith; -import testsupport.traits.FunctorLaws; -import testsupport.traits.TraversableLaws; - -import java.util.Optional; - -import static com.jnape.palatable.traitor.framework.Subjects.subjects; - -@RunWith(Traits.class) -public class TraversableOptionalTest { - - @TestTraits({FunctorLaws.class, TraversableLaws.class}) - public Subjects> testSubject() { - return subjects(TraversableOptional.empty(), TraversableOptional.wrap(Optional.of(1))); - } -} \ No newline at end of file From 2c1f037318d4f7d9b4fc8b0edeb5cdb0d52c3726 Mon Sep 17 00:00:00 2001 From: jnape Date: Sat, 14 Oct 2017 18:15:08 -0500 Subject: [PATCH 16/32] Monad arrives --- .../jnape/palatable/lambda/adt/Either.java | 15 ++-- .../palatable/lambda/adt/choice/Choice2.java | 14 +++- .../palatable/lambda/adt/choice/Choice3.java | 14 +++- .../palatable/lambda/adt/choice/Choice4.java | 14 +++- .../palatable/lambda/adt/choice/Choice5.java | 14 +++- .../lambda/adt/coproduct/CoProduct2.java | 3 +- .../lambda/adt/coproduct/CoProduct3.java | 7 +- .../lambda/adt/coproduct/CoProduct4.java | 9 ++- .../lambda/adt/coproduct/CoProduct5.java | 11 +-- .../lambda/adt/hlist/SingletonHList.java | 19 +++-- .../palatable/lambda/adt/hlist/Tuple2.java | 17 +++-- .../palatable/lambda/adt/hlist/Tuple3.java | 15 +++- .../palatable/lambda/adt/hlist/Tuple4.java | 15 +++- .../palatable/lambda/adt/hlist/Tuple5.java | 18 +++-- .../jnape/palatable/lambda/functions/Fn1.java | 14 +++- .../functions/specialized/Predicate.java | 1 - .../lambda/functor/builtin/Compose.java | 10 +++ .../lambda/functor/builtin/Const.java | 19 +++-- .../lambda/functor/builtin/Identity.java | 24 +++--- .../lambda/iterators/UnfoldingIterator.java | 2 +- .../com/jnape/palatable/lambda/lens/Lens.java | 29 ++++--- .../jnape/palatable/lambda/monad/Monad.java | 76 +++++++++++++++++++ .../lambda/traversable/LambdaIterable.java | 13 +++- .../lambda/traversable/LambdaOptional.java | 12 ++- .../palatable/lambda/adt/EitherTest.java | 5 +- .../lambda/adt/choice/Choice2Test.java | 3 +- .../lambda/adt/choice/Choice3Test.java | 3 +- .../lambda/adt/choice/Choice4Test.java | 3 +- .../lambda/adt/choice/Choice5Test.java | 3 +- .../lambda/adt/hlist/SingletonHListTest.java | 3 +- .../lambda/adt/hlist/Tuple2Test.java | 18 ++++- .../lambda/adt/hlist/Tuple3Test.java | 19 ++++- .../lambda/adt/hlist/Tuple4Test.java | 19 ++++- .../lambda/adt/hlist/Tuple5Test.java | 19 ++++- .../palatable/lambda/functions/Fn1Test.java | 3 +- .../lambda/functor/builtin/ConstTest.java | 3 +- .../lambda/functor/builtin/IdentityTest.java | 3 +- .../jnape/palatable/lambda/lens/LensTest.java | 3 +- .../traversable/LambdaIterableTest.java | 6 +- .../traversable/LambdaOptionalTest.java | 3 +- .../java/testsupport/EqualityAwareFn1.java | 6 ++ .../java/testsupport/EqualityAwareLens.java | 6 ++ .../java/testsupport/traits/MonadLaws.java | 50 ++++++++++++ 43 files changed, 441 insertions(+), 122 deletions(-) create mode 100644 src/main/java/com/jnape/palatable/lambda/monad/Monad.java create mode 100644 src/test/java/testsupport/traits/MonadLaws.java 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 f69fac64d..a0fbdb333 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/Either.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/Either.java @@ -1,5 +1,6 @@ package com.jnape.palatable.lambda.adt; +import com.jnape.palatable.lambda.monad.Monad; 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; @@ -26,7 +27,7 @@ * @param The left parameter type * @param The right parameter type */ -public abstract class Either implements CoProduct2>, Applicative>, Traversable>, Bifunctor { +public abstract class Either implements CoProduct2>, Monad>, Traversable>, Bifunctor { private Either() { } @@ -104,8 +105,9 @@ public final Either filter(Function pred, Supplier * @param the new right parameter type * @return the Either resulting from applying rightFn to this right value, or this left value if left */ - public final Either flatMap(Function> rightFn) { - return flatMap(Either::left, rightFn); + @Override + public Either flatMap(Function>> rightFn) { + return flatMap(Either::left, rightFn.andThen(Applicative::coerce)); } /** @@ -192,9 +194,8 @@ public Either peek(Consumer leftConsumer, Consumer rightConsumer) { public abstract V match(Function leftFn, Function rightFn); @Override - @SuppressWarnings("unchecked") public final Either fmap(Function fn) { - return (Either) Applicative.super.fmap(fn); + return Monad.super.fmap(fn).coerce(); } @Override @@ -227,12 +228,12 @@ public Either zip(Applicative, Eit @Override public Either discardL(Applicative> appB) { - return Applicative.super.discardL(appB).coerce(); + return Monad.super.discardL(appB).coerce(); } @Override public Either discardR(Applicative> appB) { - return Applicative.super.discardR(appB).coerce(); + return Monad.super.discardR(appB).coerce(); } @Override diff --git a/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice2.java b/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice2.java index 7866c5f2b..7bd16fa1e 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice2.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice2.java @@ -5,6 +5,7 @@ import com.jnape.palatable.lambda.functor.Applicative; import com.jnape.palatable.lambda.functor.Bifunctor; import com.jnape.palatable.lambda.functor.Functor; +import com.jnape.palatable.lambda.monad.Monad; import com.jnape.palatable.lambda.traversable.Traversable; import java.util.Objects; @@ -20,7 +21,7 @@ * @see Either * @see Choice3 */ -public abstract class Choice2 implements CoProduct2>, Applicative>, Bifunctor, Traversable> { +public abstract class Choice2 implements CoProduct2>, Monad>, Bifunctor, Traversable> { private Choice2() { } @@ -37,7 +38,7 @@ public Choice2 invert() { @Override public final Choice2 fmap(Function fn) { - return Applicative.super.fmap(fn).coerce(); + return Monad.super.fmap(fn).coerce(); } @Override @@ -71,12 +72,17 @@ public Choice2 zip(Applicative, Choic @Override public Choice2 discardL(Applicative> appB) { - return Applicative.super.discardL(appB).coerce(); + return Monad.super.discardL(appB).coerce(); } @Override public Choice2 discardR(Applicative> appB) { - return Applicative.super.discardR(appB).coerce(); + return Monad.super.discardR(appB).coerce(); + } + + @Override + public final Choice2 flatMap(Function>> f) { + return match(Choice2::a, b -> f.apply(b).coerce()); } @Override diff --git a/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice3.java b/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice3.java index 97ee63929..8648ebc90 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice3.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice3.java @@ -5,6 +5,7 @@ import com.jnape.palatable.lambda.functor.Applicative; import com.jnape.palatable.lambda.functor.Bifunctor; import com.jnape.palatable.lambda.functor.Functor; +import com.jnape.palatable.lambda.monad.Monad; import com.jnape.palatable.lambda.traversable.Traversable; import java.util.Objects; @@ -21,7 +22,7 @@ */ public abstract class Choice3 implements CoProduct3>, - Applicative>, + Monad>, Bifunctor>, Traversable> { @@ -40,7 +41,7 @@ public final Choice2 converge(Function Choice3 fmap(Function fn) { - return Applicative.super.fmap(fn).coerce(); + return Monad.super.fmap(fn).coerce(); } @Override @@ -74,12 +75,17 @@ public Choice3 zip(Applicative, Ch @Override public Choice3 discardL(Applicative> appB) { - return Applicative.super.discardL(appB).coerce(); + return Monad.super.discardL(appB).coerce(); } @Override public Choice3 discardR(Applicative> appB) { - return Applicative.super.discardR(appB).coerce(); + return Monad.super.discardR(appB).coerce(); + } + + @Override + public Choice3 flatMap(Function>> f) { + return match(Choice3::a, Choice3::b, c -> f.apply(c).coerce()); } @Override diff --git a/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice4.java b/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice4.java index 77fd8fd32..4ca44d6c7 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice4.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice4.java @@ -5,6 +5,7 @@ import com.jnape.palatable.lambda.functor.Applicative; import com.jnape.palatable.lambda.functor.Bifunctor; import com.jnape.palatable.lambda.functor.Functor; +import com.jnape.palatable.lambda.monad.Monad; import com.jnape.palatable.lambda.traversable.Traversable; import java.util.Objects; @@ -22,7 +23,7 @@ */ public abstract class Choice4 implements CoProduct4>, - Applicative>, + Monad>, Bifunctor>, Traversable> { @@ -44,7 +45,7 @@ public Choice3 converge(Function Choice4 fmap(Function fn) { - return Applicative.super.fmap(fn).coerce(); + return Monad.super.fmap(fn).coerce(); } @Override @@ -78,12 +79,17 @@ public Choice4 zip(Applicative, @Override public Choice4 discardL(Applicative> appB) { - return Applicative.super.discardL(appB).coerce(); + return Monad.super.discardL(appB).coerce(); } @Override public Choice4 discardR(Applicative> appB) { - return Applicative.super.discardR(appB).coerce(); + return Monad.super.discardR(appB).coerce(); + } + + @Override + public Choice4 flatMap(Function>> f) { + return match(Choice4::a, Choice4::b, Choice4::c, d -> f.apply(d).coerce()); } @Override diff --git a/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice5.java b/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice5.java index 4e34ee0c4..21c03fffe 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice5.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/choice/Choice5.java @@ -5,6 +5,7 @@ import com.jnape.palatable.lambda.functor.Applicative; import com.jnape.palatable.lambda.functor.Bifunctor; import com.jnape.palatable.lambda.functor.Functor; +import com.jnape.palatable.lambda.monad.Monad; import com.jnape.palatable.lambda.traversable.Traversable; import java.util.Objects; @@ -22,7 +23,7 @@ */ public abstract class Choice5 implements CoProduct5>, - Applicative>, + Monad>, Bifunctor>, Traversable> { @@ -40,7 +41,7 @@ public Choice4 converge(Function Choice5 fmap(Function fn) { - return Applicative.super.fmap(fn).coerce(); + return Monad.super.fmap(fn).coerce(); } @Override @@ -74,12 +75,17 @@ public Choice5 zip(Applicative Choice5 discardL(Applicative> appB) { - return Applicative.super.discardL(appB).coerce(); + return Monad.super.discardL(appB).coerce(); } @Override public Choice5 discardR(Applicative> appB) { - return Applicative.super.discardR(appB).coerce(); + return Monad.super.discardR(appB).coerce(); + } + + @Override + public Choice5 flatMap(Function>> f) { + return match(Choice5::a, Choice5::b, Choice5::c, Choice5::d, e -> f.apply(e).coerce()); } @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 index fe2a31da9..8ae141a51 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct2.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct2.java @@ -8,6 +8,7 @@ import java.util.function.Function; import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; /** * A generalization of the coproduct of two types A and B. Coproducts represent the disjoint @@ -121,6 +122,6 @@ public R match(Function aFn, Function R embed(Function aFn, Function bFn) { - return match(__ -> aFn.apply((CP2) this), __ -> bFn.apply((CP2) this)); + return match(constantly(aFn.apply((CP2) this)), constantly(bFn.apply((CP2) this))); } } 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 index 5eab6ff97..28024dc03 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct3.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct3.java @@ -6,6 +6,7 @@ import java.util.function.Function; import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; /** * A generalization of the coproduct of three types A, B, and C. @@ -130,8 +131,8 @@ default Optional projectC() { default R embed(Function aFn, Function bFn, Function cFn) { - return match(__ -> aFn.apply((CP3) this), - __ -> bFn.apply((CP3) this), - __ -> cFn.apply((CP3) this)); + return match(constantly(aFn.apply((CP3) this)), + constantly(bFn.apply((CP3) this)), + constantly(cFn.apply((CP3) this))); } } 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 index ffc978b15..58f33219d 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct4.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct4.java @@ -6,6 +6,7 @@ import java.util.function.Function; import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; /** * A generalization of the coproduct of four types A, B, C, and D. @@ -150,10 +151,10 @@ default R embed(Function aFn, Function bFn, Function cFn, Function dFn) { - return match(__ -> aFn.apply((CP4) this), - __ -> bFn.apply((CP4) this), - __ -> cFn.apply((CP4) this), - __ -> dFn.apply((CP4) this)); + return match(constantly(aFn.apply((CP4) this)), + constantly(bFn.apply((CP4) this)), + constantly(cFn.apply((CP4) this)), + constantly(dFn.apply((CP4) this))); } } 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 index ddfe27b1d..8b1a39c67 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct5.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct5.java @@ -6,6 +6,7 @@ import java.util.function.Function; import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; /** * A generalization of the coproduct of five types A, B, C, D, and @@ -153,10 +154,10 @@ default R embed(Function aFn, Function cFn, Function dFn, Function eFn) { - return match(__ -> aFn.apply((CP5) this), - __ -> bFn.apply((CP5) this), - __ -> cFn.apply((CP5) this), - __ -> dFn.apply((CP5) this), - __ -> eFn.apply((CP5) this)); + return match(constantly(aFn.apply((CP5) this)), + constantly(bFn.apply((CP5) this)), + constantly(cFn.apply((CP5) this)), + constantly(dFn.apply((CP5) this)), + constantly(eFn.apply((CP5) this))); } } diff --git a/src/main/java/com/jnape/palatable/lambda/adt/hlist/SingletonHList.java b/src/main/java/com/jnape/palatable/lambda/adt/hlist/SingletonHList.java index c74aa83aa..edc340fbc 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/hlist/SingletonHList.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/hlist/SingletonHList.java @@ -3,6 +3,7 @@ import com.jnape.palatable.lambda.adt.hlist.HList.HCons; import com.jnape.palatable.lambda.adt.hlist.HList.HNil; import com.jnape.palatable.lambda.functor.Applicative; +import com.jnape.palatable.lambda.monad.Monad; import com.jnape.palatable.lambda.traversable.Traversable; import java.util.function.Function; @@ -17,7 +18,7 @@ * @see Tuple4 * @see Tuple5 */ -public class SingletonHList<_1> extends HCons<_1, HNil> implements Applicative<_1, SingletonHList>, Traversable<_1, SingletonHList> { +public class SingletonHList<_1> extends HCons<_1, HNil> implements Monad<_1, SingletonHList>, Traversable<_1, SingletonHList> { SingletonHList(_1 _1) { super(_1, nil()); @@ -29,9 +30,8 @@ public <_0> Tuple2<_0, _1> cons(_0 _0) { } @Override - @SuppressWarnings("unchecked") public <_1Prime> SingletonHList<_1Prime> fmap(Function fn) { - return (SingletonHList<_1Prime>) Applicative.super.fmap(fn); + return Monad.super.<_1Prime>fmap(fn).coerce(); } @Override @@ -42,19 +42,22 @@ public <_1Prime> SingletonHList<_1Prime> pure(_1Prime _1Prime) { @Override public <_1Prime> SingletonHList<_1Prime> zip( Applicative, SingletonHList> appFn) { - return new SingletonHList<>(appFn.>>coerce() - .head() - .apply(head())); + return Monad.super.zip(appFn).coerce(); } @Override public <_1Prime> SingletonHList<_1Prime> discardL(Applicative<_1Prime, SingletonHList> appB) { - return Applicative.super.discardL(appB).coerce(); + return Monad.super.discardL(appB).coerce(); } @Override public <_1Prime> SingletonHList<_1> discardR(Applicative<_1Prime, SingletonHList> appB) { - return Applicative.super.discardR(appB).coerce(); + return Monad.super.discardR(appB).coerce(); + } + + @Override + public <_1Prime> SingletonHList<_1Prime> flatMap(Function> f) { + return f.apply(head()).coerce(); } @Override diff --git a/src/main/java/com/jnape/palatable/lambda/adt/hlist/Tuple2.java b/src/main/java/com/jnape/palatable/lambda/adt/hlist/Tuple2.java index aa9fa1f93..00083fac4 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/hlist/Tuple2.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/hlist/Tuple2.java @@ -3,6 +3,7 @@ import com.jnape.palatable.lambda.adt.hlist.HList.HCons; import com.jnape.palatable.lambda.functor.Applicative; import com.jnape.palatable.lambda.functor.Bifunctor; +import com.jnape.palatable.lambda.monad.Monad; import com.jnape.palatable.lambda.traversable.Traversable; import java.util.Map; @@ -23,7 +24,7 @@ * @see Tuple5 */ public class Tuple2<_1, _2> extends HCons<_1, SingletonHList<_2>> - implements Map.Entry<_1, _2>, Applicative<_2, Tuple2<_1, ?>>, Bifunctor<_1, _2, Tuple2>, Traversable<_2, Tuple2<_1, ?>> { + implements Map.Entry<_1, _2>, Monad<_2, Tuple2<_1, ?>>, Bifunctor<_1, _2, Tuple2>, Traversable<_2, Tuple2<_1, ?>> { private final _1 _1; private final _2 _2; @@ -85,9 +86,8 @@ public _2 setValue(_2 value) { } @Override - @SuppressWarnings("unchecked") public <_2Prime> Tuple2<_1, _2Prime> fmap(Function fn) { - return (Tuple2<_1, _2Prime>) Applicative.super.fmap(fn); + return Monad.super.<_2Prime>fmap(fn).coerce(); } @Override @@ -116,17 +116,22 @@ public <_2Prime> Tuple2<_1, _2Prime> pure(_2Prime _2Prime) { @Override public <_2Prime> Tuple2<_1, _2Prime> zip( Applicative, Tuple2<_1, ?>> appFn) { - return biMapR(appFn.>>coerce()._2()); + return Monad.super.zip(appFn).coerce(); } @Override public <_2Prime> Tuple2<_1, _2Prime> discardL(Applicative<_2Prime, Tuple2<_1, ?>> appB) { - return Applicative.super.discardL(appB).coerce(); + return Monad.super.discardL(appB).coerce(); } @Override public <_2Prime> Tuple2<_1, _2> discardR(Applicative<_2Prime, Tuple2<_1, ?>> appB) { - return Applicative.super.discardR(appB).coerce(); + return Monad.super.discardR(appB).coerce(); + } + + @Override + public <_2Prime> Tuple2<_1, _2Prime> flatMap(Function>> f) { + return pure(f.apply(_2).>coerce()._2()); } @Override diff --git a/src/main/java/com/jnape/palatable/lambda/adt/hlist/Tuple3.java b/src/main/java/com/jnape/palatable/lambda/adt/hlist/Tuple3.java index d4a485467..10511c10a 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/hlist/Tuple3.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/hlist/Tuple3.java @@ -4,6 +4,7 @@ import com.jnape.palatable.lambda.functions.Fn3; import com.jnape.palatable.lambda.functor.Applicative; import com.jnape.palatable.lambda.functor.Bifunctor; +import com.jnape.palatable.lambda.monad.Monad; import com.jnape.palatable.lambda.traversable.Traversable; import java.util.function.Function; @@ -23,7 +24,7 @@ * @see Tuple5 */ public class Tuple3<_1, _2, _3> extends HCons<_1, Tuple2<_2, _3>> - implements Applicative<_3, Tuple3<_1, _2, ?>>, Bifunctor<_2, _3, Tuple3<_1, ?, ?>>, Traversable<_3, Tuple3<_1, _2, ?>> { + implements Monad<_3, Tuple3<_1, _2, ?>>, Bifunctor<_2, _3, Tuple3<_1, ?, ?>>, Traversable<_3, Tuple3<_1, _2, ?>> { private final _1 _1; private final _2 _2; private final _3 _3; @@ -83,7 +84,7 @@ public R into(Fn3 fn) { @Override @SuppressWarnings("unchecked") public <_3Prime> Tuple3<_1, _2, _3Prime> fmap(Function fn) { - return (Tuple3<_1, _2, _3Prime>) Applicative.super.fmap(fn); + return (Tuple3<_1, _2, _3Prime>) Monad.super.fmap(fn); } @Override @@ -117,12 +118,18 @@ public <_3Prime> Tuple3<_1, _2, _3Prime> zip( @Override public <_3Prime> Tuple3<_1, _2, _3Prime> discardL(Applicative<_3Prime, Tuple3<_1, _2, ?>> appB) { - return Applicative.super.discardL(appB).coerce(); + return Monad.super.discardL(appB).coerce(); } @Override public <_3Prime> Tuple3<_1, _2, _3> discardR(Applicative<_3Prime, Tuple3<_1, _2, ?>> appB) { - return Applicative.super.discardR(appB).coerce(); + return Monad.super.discardR(appB).coerce(); + } + + @Override + public <_3Prime> Tuple3<_1, _2, _3Prime> flatMap( + Function>> f) { + return pure(f.apply(_3).>coerce()._3); } @Override diff --git a/src/main/java/com/jnape/palatable/lambda/adt/hlist/Tuple4.java b/src/main/java/com/jnape/palatable/lambda/adt/hlist/Tuple4.java index f8294de82..7e0f21ea0 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/hlist/Tuple4.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/hlist/Tuple4.java @@ -4,6 +4,7 @@ import com.jnape.palatable.lambda.functions.Fn4; import com.jnape.palatable.lambda.functor.Applicative; import com.jnape.palatable.lambda.functor.Bifunctor; +import com.jnape.palatable.lambda.monad.Monad; import com.jnape.palatable.lambda.traversable.Traversable; import java.util.function.Function; @@ -24,7 +25,7 @@ * @see Tuple5 */ public class Tuple4<_1, _2, _3, _4> extends HCons<_1, Tuple3<_2, _3, _4>> - implements Applicative<_4, Tuple4<_1, _2, _3, ?>>, Bifunctor<_3, _4, Tuple4<_1, _2, ?, ?>>, Traversable<_4, Tuple4<_1, _2, _3, ?>> { + implements Monad<_4, Tuple4<_1, _2, _3, ?>>, Bifunctor<_3, _4, Tuple4<_1, _2, ?, ?>>, Traversable<_4, Tuple4<_1, _2, _3, ?>> { private final _1 _1; private final _2 _2; private final _3 _3; @@ -95,7 +96,7 @@ public R into(Fn4 Tuple4<_1, _2, _3, _4Prime> fmap(Function fn) { - return (Tuple4<_1, _2, _3, _4Prime>) Applicative.super.fmap(fn); + return (Tuple4<_1, _2, _3, _4Prime>) Monad.super.fmap(fn); } @Override @@ -129,12 +130,18 @@ public <_4Prime> Tuple4<_1, _2, _3, _4Prime> zip( @Override public <_4Prime> Tuple4<_1, _2, _3, _4Prime> discardL(Applicative<_4Prime, Tuple4<_1, _2, _3, ?>> appB) { - return Applicative.super.discardL(appB).coerce(); + return Monad.super.discardL(appB).coerce(); } @Override public <_4Prime> Tuple4<_1, _2, _3, _4> discardR(Applicative<_4Prime, Tuple4<_1, _2, _3, ?>> appB) { - return Applicative.super.discardR(appB).coerce(); + return Monad.super.discardR(appB).coerce(); + } + + @Override + public <_4Prime> Tuple4<_1, _2, _3, _4Prime> flatMap( + Function>> f) { + return pure(f.apply(_4).>coerce()._4); } @Override diff --git a/src/main/java/com/jnape/palatable/lambda/adt/hlist/Tuple5.java b/src/main/java/com/jnape/palatable/lambda/adt/hlist/Tuple5.java index 5bfd06f15..8dd505fa2 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/hlist/Tuple5.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/hlist/Tuple5.java @@ -1,5 +1,6 @@ package com.jnape.palatable.lambda.adt.hlist; +import com.jnape.palatable.lambda.monad.Monad; import com.jnape.palatable.lambda.adt.hlist.HList.HCons; import com.jnape.palatable.lambda.functor.Applicative; import com.jnape.palatable.lambda.functor.Bifunctor; @@ -24,7 +25,7 @@ * @see Tuple4 */ public class Tuple5<_1, _2, _3, _4, _5> extends HCons<_1, Tuple4<_2, _3, _4, _5>> - implements Applicative<_5, Tuple5<_1, _2, _3, _4, ?>>, Bifunctor<_4, _5, Tuple5<_1, _2, _3, ?, ?>>, Traversable<_5, Tuple5<_1, _2, _3, _4, ?>> { + implements Monad<_5, Tuple5<_1, _2, _3, _4, ?>>, Bifunctor<_4, _5, Tuple5<_1, _2, _3, ?, ?>>, Traversable<_5, Tuple5<_1, _2, _3, _4, ?>> { private final _1 _1; private final _2 _2; private final _3 _3; @@ -91,9 +92,8 @@ public _5 _5() { } @Override - @SuppressWarnings("unchecked") public <_5Prime> Tuple5<_1, _2, _3, _4, _5Prime> fmap(Function fn) { - return (Tuple5<_1, _2, _3, _4, _5Prime>) Applicative.super.fmap(fn); + return Monad.super.<_5Prime>fmap(fn).coerce(); } @Override @@ -122,17 +122,23 @@ public <_5Prime> Tuple5<_1, _2, _3, _4, _5Prime> pure(_5Prime _5Prime) { @Override public <_5Prime> Tuple5<_1, _2, _3, _4, _5Prime> zip( Applicative, Tuple5<_1, _2, _3, _4, ?>> appFn) { - return biMapR(appFn.>>coerce()._5()); + return Monad.super.zip(appFn).coerce(); } @Override public <_5Prime> Tuple5<_1, _2, _3, _4, _5Prime> discardL(Applicative<_5Prime, Tuple5<_1, _2, _3, _4, ?>> appB) { - return Applicative.super.discardL(appB).coerce(); + return Monad.super.discardL(appB).coerce(); } @Override public <_5Prime> Tuple5<_1, _2, _3, _4, _5> discardR(Applicative<_5Prime, Tuple5<_1, _2, _3, _4, ?>> appB) { - return Applicative.super.discardR(appB).coerce(); + return Monad.super.discardR(appB).coerce(); + } + + @Override + public <_5Prime> Tuple5<_1, _2, _3, _4, _5Prime> flatMap( + Function>> f) { + return pure(f.apply(_5).>coerce()._5()); } @Override 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 12e84d9f6..b53d317c6 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/Fn1.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/Fn1.java @@ -2,6 +2,7 @@ import com.jnape.palatable.lambda.functor.Applicative; import com.jnape.palatable.lambda.functor.Profunctor; +import com.jnape.palatable.lambda.monad.Monad; import java.util.function.BiFunction; import java.util.function.Function; @@ -16,7 +17,7 @@ * @param The result type */ @FunctionalInterface -public interface Fn1 extends Applicative>, Profunctor, Function { +public interface Fn1 extends Monad>, Profunctor, Function { /** * Invoke this function with the given argument. @@ -26,6 +27,11 @@ public interface Fn1 extends Applicative>, Profunctor Fn1 flatMap(Function>> f) { + return a -> f.apply(apply(a)).>coerce().apply(a); + } + /** * Also left-to-right composition (sadly). * @@ -35,7 +41,7 @@ public interface Fn1 extends Applicative>, Profunctor Fn1 fmap(Function f) { - return Applicative.super.fmap(f).coerce(); + return Monad.super.fmap(f).coerce(); } /** @@ -67,7 +73,7 @@ default Fn1 zip(Fn2 appFn) { */ @Override default Fn1 discardL(Applicative> appB) { - return Applicative.super.discardL(appB).coerce(); + return Monad.super.discardL(appB).coerce(); } /** @@ -75,7 +81,7 @@ default Fn1 discardL(Applicative> appB) { */ @Override default Fn1 discardR(Applicative> appB) { - return Applicative.super.discardR(appB).coerce(); + return Monad.super.discardR(appB).coerce(); } /** diff --git a/src/main/java/com/jnape/palatable/lambda/functions/specialized/Predicate.java b/src/main/java/com/jnape/palatable/lambda/functions/specialized/Predicate.java index 03eb6e0b8..426ce991c 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/specialized/Predicate.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/specialized/Predicate.java @@ -28,7 +28,6 @@ default boolean test(A a) { * @return a new predicate of Z (the new argument type) */ @Override - @SuppressWarnings("unchecked") default Predicate compose(Function before) { return Fn1.super.compose(before)::apply; } diff --git a/src/main/java/com/jnape/palatable/lambda/functor/builtin/Compose.java b/src/main/java/com/jnape/palatable/lambda/functor/builtin/Compose.java index 70a5c39a9..a8b260bcf 100644 --- a/src/main/java/com/jnape/palatable/lambda/functor/builtin/Compose.java +++ b/src/main/java/com/jnape/palatable/lambda/functor/builtin/Compose.java @@ -40,6 +40,16 @@ public Compose zip(Applicative, Co return new Compose<>(fga.zip(appFn.>>coerce().getCompose().fmap(gFn -> g -> g.zip(gFn)))); } + @Override + public Compose discardL(Applicative> appB) { + return Applicative.super.discardL(appB).coerce(); + } + + @Override + public Compose discardR(Applicative> appB) { + return Applicative.super.discardR(appB).coerce(); + } + @Override public boolean equals(Object other) { return other instanceof Compose && Objects.equals(fga, ((Compose) other).fga); diff --git a/src/main/java/com/jnape/palatable/lambda/functor/builtin/Const.java b/src/main/java/com/jnape/palatable/lambda/functor/builtin/Const.java index d2b58f83c..5eea967fb 100644 --- a/src/main/java/com/jnape/palatable/lambda/functor/builtin/Const.java +++ b/src/main/java/com/jnape/palatable/lambda/functor/builtin/Const.java @@ -1,5 +1,6 @@ package com.jnape.palatable.lambda.functor.builtin; +import com.jnape.palatable.lambda.monad.Monad; import com.jnape.palatable.lambda.functor.Applicative; import com.jnape.palatable.lambda.functor.Bifunctor; import com.jnape.palatable.lambda.traversable.Traversable; @@ -16,7 +17,7 @@ * @param the left parameter type, and the type of the stored value * @param the right (phantom) parameter type */ -public final class Const implements Applicative>, Bifunctor, Traversable> { +public final class Const implements Monad>, Bifunctor, Traversable> { private final A a; @@ -52,9 +53,8 @@ public int hashCode() { * @return a Const over A (the same value) and C (the new phantom parameter) */ @Override - @SuppressWarnings("unchecked") public Const fmap(Function fn) { - return (Const) Applicative.super.fmap(fn); + return Monad.super.fmap(fn).coerce(); } @Override @@ -64,19 +64,24 @@ public Const pure(C c) { } @Override - @SuppressWarnings("unchecked") public Const zip(Applicative, Const> appFn) { - return (Const) this; + return Monad.super.zip(appFn).coerce(); } @Override public Const discardL(Applicative> appB) { - return Applicative.super.discardL(appB).coerce(); + return Monad.super.discardL(appB).coerce(); } @Override public Const discardR(Applicative> appB) { - return Applicative.super.discardR(appB).coerce(); + return Monad.super.discardR(appB).coerce(); + } + + @Override + @SuppressWarnings("unchecked") + public Const flatMap(Function>> f) { + return (Const) this; } @Override diff --git a/src/main/java/com/jnape/palatable/lambda/functor/builtin/Identity.java b/src/main/java/com/jnape/palatable/lambda/functor/builtin/Identity.java index 9f0a5cda5..def56eea0 100644 --- a/src/main/java/com/jnape/palatable/lambda/functor/builtin/Identity.java +++ b/src/main/java/com/jnape/palatable/lambda/functor/builtin/Identity.java @@ -1,6 +1,7 @@ package com.jnape.palatable.lambda.functor.builtin; import com.jnape.palatable.lambda.functor.Applicative; +import com.jnape.palatable.lambda.monad.Monad; import com.jnape.palatable.lambda.traversable.Traversable; import java.util.Objects; @@ -11,7 +12,7 @@ * * @param the value type */ -public final class Identity implements Applicative, Traversable { +public final class Identity implements Monad, Traversable { private final A a; @@ -29,16 +30,19 @@ public A runIdentity() { } /** - * Covariantly map over the value. - * - * @param fn the mapping function - * @param the new value type - * @return an Identity over B (the new value) + * {@inheritDoc} + */ + @Override + public Identity flatMap(Function> f) { + return f.apply(runIdentity()).coerce(); + } + + /** + * {@inheritDoc} */ @Override - @SuppressWarnings("unchecked") public Identity fmap(Function fn) { - return (Identity) Applicative.super.fmap(fn); + return Monad.super.fmap(fn).coerce(); } @Override @@ -53,12 +57,12 @@ public Identity zip(Applicative, Identit @Override public Identity discardL(Applicative appB) { - return Applicative.super.discardL(appB).coerce(); + return Monad.super.discardL(appB).coerce(); } @Override public Identity discardR(Applicative appB) { - return Applicative.super.discardR(appB).coerce(); + return Monad.super.discardR(appB).coerce(); } @Override diff --git a/src/main/java/com/jnape/palatable/lambda/iterators/UnfoldingIterator.java b/src/main/java/com/jnape/palatable/lambda/iterators/UnfoldingIterator.java index f329d146b..bc99a2cf3 100644 --- a/src/main/java/com/jnape/palatable/lambda/iterators/UnfoldingIterator.java +++ b/src/main/java/com/jnape/palatable/lambda/iterators/UnfoldingIterator.java @@ -21,7 +21,7 @@ public boolean hasNext() { } @Override - @SuppressWarnings("OptionalGetWithoutIsPresent") + @SuppressWarnings("ConstantConditions") public A next() { if (!hasNext()) throw new NoSuchElementException(); diff --git a/src/main/java/com/jnape/palatable/lambda/lens/Lens.java b/src/main/java/com/jnape/palatable/lambda/lens/Lens.java index 750ddcb68..621ec31a7 100644 --- a/src/main/java/com/jnape/palatable/lambda/lens/Lens.java +++ b/src/main/java/com/jnape/palatable/lambda/lens/Lens.java @@ -1,5 +1,6 @@ package com.jnape.palatable.lambda.lens; +import com.jnape.palatable.lambda.monad.Monad; import com.jnape.palatable.lambda.functions.Fn2; import com.jnape.palatable.lambda.functor.Applicative; import com.jnape.palatable.lambda.functor.Functor; @@ -10,6 +11,7 @@ import java.util.function.Function; import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; +import static com.jnape.palatable.lambda.lens.Lens.Simple.adapt; import static com.jnape.palatable.lambda.lens.functions.Over.over; import static com.jnape.palatable.lambda.lens.functions.Set.set; import static com.jnape.palatable.lambda.lens.functions.View.view; @@ -135,7 +137,7 @@ * @param the type of the "smaller" update value */ @FunctionalInterface -public interface Lens extends Applicative>, Profunctor> { +public interface Lens extends Monad>, Profunctor> { , FB extends Functor> FT apply( Function fn, S s); @@ -156,9 +158,8 @@ default , FB extends Functor> } @Override - @SuppressWarnings("unchecked") default Lens fmap(Function fn) { - return (Lens) Applicative.super.fmap(fn); + return Monad.super.fmap(fn).coerce(); } @Override @@ -168,17 +169,22 @@ default Lens pure(U u) { @Override default Lens zip(Applicative, Lens> appFn) { - return lens(view(this), (s, b) -> set(appFn., A, B>>coerce(), b, s).apply(set(this, b, s))); + return Monad.super.zip(appFn).coerce(); } @Override default Lens discardL(Applicative> appB) { - return Applicative.super.discardL(appB).coerce(); + return Monad.super.discardL(appB).coerce(); } @Override default Lens discardR(Applicative> appB) { - return Applicative.super.discardR(appB).coerce(); + return Monad.super.discardR(appB).coerce(); + } + + @Override + default Lens flatMap(Function>> f) { + return lens(view(this), (s, b) -> set(f.apply(set(this, b, s)).>coerce(), b, s)); } @Override @@ -302,10 +308,9 @@ public , FB extends Functor> F * @param the type of both "smaller" values * @return the lens */ - @SuppressWarnings("unchecked") static Lens.Simple simpleLens(Function getter, BiFunction setter) { - return lens(getter, setter)::apply; + return adapt(lens(getter, setter)); } /** @@ -323,15 +328,19 @@ default , FA extends Functor> return this::apply; } - @SuppressWarnings("unchecked") default Lens.Simple compose(Lens.Simple g) { - return Lens.super.compose(g)::apply; + return Lens.super.compose(g).coerce(); } default Lens.Simple andThen(Lens.Simple f) { return f.compose(this); } + @SuppressWarnings("unchecked") + static Simple adapt(Lens lens) { + return lens::apply; + } + /** * A convenience type with a simplified type signature for fixed simple lenses. * diff --git a/src/main/java/com/jnape/palatable/lambda/monad/Monad.java b/src/main/java/com/jnape/palatable/lambda/monad/Monad.java new file mode 100644 index 000000000..47a902b1a --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/monad/Monad.java @@ -0,0 +1,76 @@ +package com.jnape.palatable.lambda.monad; + +import com.jnape.palatable.lambda.functor.Applicative; + +import java.util.function.Function; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; + +/** + * Monads are {@link Applicative} functors that support a flattening operation to unwrap M<M<A>> + * -> M<A>. This flattening operation, coupled with {@link Applicative#zip(Applicative)}, gives rise to + * {@link Monad#flatMap(Function)}, a binding operation that maps the carrier value to a new monad instance in the same + * category, and then unwraps the outer layer. + *

+ * In addition to the applicative laws, there are 3 specific monad laws that monads should obey: + *

+ *

+ * For more information, read about + * Monads. + * + * @param the type of the parameter + * @param the unification parameter to more tightly type-constrain Monads to themselves + */ +public interface Monad extends Applicative { + + /** + * Chain dependent computations that may continue or short-circuit based on previous results. + * + * @param f the dependent computation over A + * @param the resulting monad parameter type + * @return the new monad instance + */ + Monad flatMap(Function> f); + + /** + * {@inheritDoc} + */ + @Override + Monad pure(B b); + + /** + * {@inheritDoc} + */ + @Override + default Monad fmap(Function fn) { + return flatMap(fn.andThen(this::pure)); + } + + /** + * {@inheritDoc} + */ + @Override + default Monad zip(Applicative, M> appFn) { + return fmap(a -> appFn., M>>coerce().fmap(f -> f.apply(a))).flatMap(id()); + } + + /** + * {@inheritDoc} + */ + @Override + default Monad discardL(Applicative appB) { + return (Monad) Applicative.super.discardL(appB); + } + + /** + * {@inheritDoc} + */ + @Override + default Monad discardR(Applicative appB) { + return (Monad) Applicative.super.discardR(appB); + } +} \ No newline at end of file diff --git a/src/main/java/com/jnape/palatable/lambda/traversable/LambdaIterable.java b/src/main/java/com/jnape/palatable/lambda/traversable/LambdaIterable.java index 481103c58..d0f492942 100644 --- a/src/main/java/com/jnape/palatable/lambda/traversable/LambdaIterable.java +++ b/src/main/java/com/jnape/palatable/lambda/traversable/LambdaIterable.java @@ -1,11 +1,13 @@ package com.jnape.palatable.lambda.traversable; import com.jnape.palatable.lambda.functor.Applicative; +import com.jnape.palatable.lambda.monad.Monad; import java.util.Iterator; import java.util.Objects; import java.util.function.Function; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Flatten.flatten; import static com.jnape.palatable.lambda.functions.builtin.fn2.CartesianProduct.cartesianProduct; import static com.jnape.palatable.lambda.functions.builtin.fn2.Cons.cons; import static com.jnape.palatable.lambda.functions.builtin.fn2.Into.into; @@ -21,7 +23,7 @@ * * @param the Iterable element type */ -public final class LambdaIterable implements Applicative, Traversable { +public final class LambdaIterable implements Monad, Traversable { private final Iterable as; @SuppressWarnings("unchecked") @@ -67,12 +69,17 @@ public LambdaIterable zip(Applicative, L @Override public LambdaIterable discardL(Applicative appB) { - return Applicative.super.discardL(appB).coerce(); + return Monad.super.discardL(appB).coerce(); } @Override public LambdaIterable discardR(Applicative appB) { - return Applicative.super.discardR(appB).coerce(); + return Monad.super.discardR(appB).coerce(); + } + + @Override + public LambdaIterable flatMap(Function> f) { + return wrap(flatten(map(a -> f.apply(a).>coerce().unwrap(), as))); } @Override diff --git a/src/main/java/com/jnape/palatable/lambda/traversable/LambdaOptional.java b/src/main/java/com/jnape/palatable/lambda/traversable/LambdaOptional.java index 40731e8da..d93b8a2c2 100644 --- a/src/main/java/com/jnape/palatable/lambda/traversable/LambdaOptional.java +++ b/src/main/java/com/jnape/palatable/lambda/traversable/LambdaOptional.java @@ -2,6 +2,7 @@ import com.jnape.palatable.lambda.functor.Applicative; import com.jnape.palatable.lambda.functor.Functor; +import com.jnape.palatable.lambda.monad.Monad; import java.util.Objects; import java.util.Optional; @@ -13,7 +14,7 @@ * * @param the Optional parameter type */ -public final class LambdaOptional implements Applicative, Traversable { +public final class LambdaOptional implements Monad, Traversable { private final Optional delegate; @SuppressWarnings("unchecked") @@ -47,12 +48,17 @@ public LambdaOptional zip(Applicative, L @Override public LambdaOptional discardL(Applicative appB) { - return Applicative.super.discardL(appB).coerce(); + return Monad.super.discardL(appB).coerce(); } @Override public LambdaOptional discardR(Applicative appB) { - return Applicative.super.discardR(appB).coerce(); + return Monad.super.discardR(appB).coerce(); + } + + @Override + public LambdaOptional flatMap(Function> f) { + return wrap(delegate.flatMap(a -> f.apply(a).>coerce().unwrap())); } @Override 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 00785f36f..cd405b634 100644 --- a/src/test/java/com/jnape/palatable/lambda/adt/EitherTest.java +++ b/src/test/java/com/jnape/palatable/lambda/adt/EitherTest.java @@ -10,6 +10,7 @@ import testsupport.traits.ApplicativeLaws; import testsupport.traits.BifunctorLaws; import testsupport.traits.FunctorLaws; +import testsupport.traits.MonadLaws; import testsupport.traits.TraversableLaws; import java.util.Optional; @@ -31,9 +32,9 @@ public class EitherTest { @Rule public ExpectedException thrown = ExpectedException.none(); - @TestTraits({FunctorLaws.class, ApplicativeLaws.class, BifunctorLaws.class, TraversableLaws.class}) + @TestTraits({FunctorLaws.class, ApplicativeLaws.class, MonadLaws.class, BifunctorLaws.class, TraversableLaws.class}) public Subjects> testSubjects() { - return subjects(right(1)); + return subjects(left("foo"), right(1)); } @Test diff --git a/src/test/java/com/jnape/palatable/lambda/adt/choice/Choice2Test.java b/src/test/java/com/jnape/palatable/lambda/adt/choice/Choice2Test.java index 4c540dde2..49b489192 100644 --- a/src/test/java/com/jnape/palatable/lambda/adt/choice/Choice2Test.java +++ b/src/test/java/com/jnape/palatable/lambda/adt/choice/Choice2Test.java @@ -9,6 +9,7 @@ import testsupport.traits.ApplicativeLaws; import testsupport.traits.BifunctorLaws; import testsupport.traits.FunctorLaws; +import testsupport.traits.MonadLaws; import testsupport.traits.TraversableLaws; import static com.jnape.palatable.lambda.adt.choice.Choice2.a; @@ -28,7 +29,7 @@ public void setUp() { b = b(true); } - @TestTraits({FunctorLaws.class, ApplicativeLaws.class, BifunctorLaws.class, TraversableLaws.class}) + @TestTraits({FunctorLaws.class, ApplicativeLaws.class, MonadLaws.class, BifunctorLaws.class, TraversableLaws.class}) public Subjects> testSubjects() { return subjects(a("foo"), b(1)); } diff --git a/src/test/java/com/jnape/palatable/lambda/adt/choice/Choice3Test.java b/src/test/java/com/jnape/palatable/lambda/adt/choice/Choice3Test.java index 462f2835e..1d04ec9a5 100644 --- a/src/test/java/com/jnape/palatable/lambda/adt/choice/Choice3Test.java +++ b/src/test/java/com/jnape/palatable/lambda/adt/choice/Choice3Test.java @@ -9,6 +9,7 @@ import testsupport.traits.ApplicativeLaws; import testsupport.traits.BifunctorLaws; import testsupport.traits.FunctorLaws; +import testsupport.traits.MonadLaws; import testsupport.traits.TraversableLaws; import static com.jnape.palatable.lambda.adt.choice.Choice3.a; @@ -31,7 +32,7 @@ public void setUp() { c = Choice3.c(true); } - @TestTraits({FunctorLaws.class, ApplicativeLaws.class, BifunctorLaws.class, TraversableLaws.class}) + @TestTraits({FunctorLaws.class, ApplicativeLaws.class, MonadLaws.class, BifunctorLaws.class, TraversableLaws.class}) public Subjects> testSubjects() { return subjects(a("foo"), b(1), c(true)); } diff --git a/src/test/java/com/jnape/palatable/lambda/adt/choice/Choice4Test.java b/src/test/java/com/jnape/palatable/lambda/adt/choice/Choice4Test.java index 05be8aed8..0b70141e7 100644 --- a/src/test/java/com/jnape/palatable/lambda/adt/choice/Choice4Test.java +++ b/src/test/java/com/jnape/palatable/lambda/adt/choice/Choice4Test.java @@ -9,6 +9,7 @@ import testsupport.traits.ApplicativeLaws; import testsupport.traits.BifunctorLaws; import testsupport.traits.FunctorLaws; +import testsupport.traits.MonadLaws; import testsupport.traits.TraversableLaws; import static com.jnape.palatable.lambda.adt.choice.Choice4.a; @@ -34,7 +35,7 @@ public void setUp() { d = d(4D); } - @TestTraits({FunctorLaws.class, ApplicativeLaws.class, BifunctorLaws.class, TraversableLaws.class}) + @TestTraits({FunctorLaws.class, ApplicativeLaws.class, MonadLaws.class, BifunctorLaws.class, TraversableLaws.class}) public Subjects> testSubjects() { return subjects(a("foo"), b(1), c(true), d('a')); } diff --git a/src/test/java/com/jnape/palatable/lambda/adt/choice/Choice5Test.java b/src/test/java/com/jnape/palatable/lambda/adt/choice/Choice5Test.java index 4c19f9992..a7e9b8db5 100644 --- a/src/test/java/com/jnape/palatable/lambda/adt/choice/Choice5Test.java +++ b/src/test/java/com/jnape/palatable/lambda/adt/choice/Choice5Test.java @@ -9,6 +9,7 @@ import testsupport.traits.ApplicativeLaws; import testsupport.traits.BifunctorLaws; import testsupport.traits.FunctorLaws; +import testsupport.traits.MonadLaws; import testsupport.traits.TraversableLaws; import static com.jnape.palatable.lambda.adt.choice.Choice5.a; @@ -37,7 +38,7 @@ public void setUp() { e = e('z'); } - @TestTraits({FunctorLaws.class, ApplicativeLaws.class, BifunctorLaws.class, TraversableLaws.class}) + @TestTraits({FunctorLaws.class, ApplicativeLaws.class, MonadLaws.class, BifunctorLaws.class, TraversableLaws.class}) public Subjects> testSubjects() { return subjects(Choice5.a("foo"), Choice5.b(1), Choice5.c(true), Choice5.d('a'), Choice5.e(2d)); } diff --git a/src/test/java/com/jnape/palatable/lambda/adt/hlist/SingletonHListTest.java b/src/test/java/com/jnape/palatable/lambda/adt/hlist/SingletonHListTest.java index ecaf43429..1b4ab4bd8 100644 --- a/src/test/java/com/jnape/palatable/lambda/adt/hlist/SingletonHListTest.java +++ b/src/test/java/com/jnape/palatable/lambda/adt/hlist/SingletonHListTest.java @@ -7,6 +7,7 @@ import org.junit.runner.RunWith; import testsupport.traits.ApplicativeLaws; import testsupport.traits.FunctorLaws; +import testsupport.traits.MonadLaws; import testsupport.traits.TraversableLaws; import static com.jnape.palatable.lambda.adt.hlist.HList.nil; @@ -23,7 +24,7 @@ public void setUp() { singletonHList = new SingletonHList<>(1); } - @TestTraits({FunctorLaws.class, ApplicativeLaws.class, TraversableLaws.class}) + @TestTraits({FunctorLaws.class, ApplicativeLaws.class, MonadLaws.class, TraversableLaws.class}) public SingletonHList testSubject() { return singletonHList("one"); } diff --git a/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple2Test.java b/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple2Test.java index 2e1d4f160..9638d745e 100644 --- a/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple2Test.java +++ b/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple2Test.java @@ -8,10 +8,12 @@ import testsupport.traits.ApplicativeLaws; import testsupport.traits.BifunctorLaws; import testsupport.traits.FunctorLaws; +import testsupport.traits.MonadLaws; import testsupport.traits.TraversableLaws; import java.util.HashMap; import java.util.Map; +import java.util.function.Function; import static com.jnape.palatable.lambda.adt.hlist.HList.singletonHList; import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; @@ -31,7 +33,7 @@ public void setUp() throws Exception { tuple2 = new Tuple2<>(1, new SingletonHList<>(2)); } - @TestTraits({FunctorLaws.class, ApplicativeLaws.class, BifunctorLaws.class, TraversableLaws.class}) + @TestTraits({FunctorLaws.class, ApplicativeLaws.class, MonadLaws.class, BifunctorLaws.class, TraversableLaws.class}) public Tuple2 testSubject() { return tuple("one", 2); } @@ -98,4 +100,18 @@ public void staticFactoryMethodFromMapEntry() { assertEquals(tuple("string", 1), Tuple2.fromEntry(stringIntEntry)); } + + @Test + public void zipPrecedence() { + Tuple2 a = tuple("foo", 1); + Tuple2> b = tuple("bar", x -> x + 1); + assertEquals(tuple("foo", 2), a.zip(b)); + } + + @Test + public void flatMapPrecedence() { + Tuple2 a = tuple("foo", 1); + Function> b = x -> tuple("bar", x + 1); + assertEquals(tuple("foo", 2), a.flatMap(b)); + } } \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple3Test.java b/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple3Test.java index c1c1b9fe9..0730a97a9 100644 --- a/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple3Test.java +++ b/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple3Test.java @@ -8,8 +8,11 @@ import testsupport.traits.ApplicativeLaws; import testsupport.traits.BifunctorLaws; import testsupport.traits.FunctorLaws; +import testsupport.traits.MonadLaws; import testsupport.traits.TraversableLaws; +import java.util.function.Function; + import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; import static org.junit.Assert.assertEquals; import static org.mockito.Mockito.spy; @@ -27,7 +30,7 @@ public void setUp() { tuple3 = new Tuple3<>(1, new Tuple2<>("2", new SingletonHList<>('3'))); } - @TestTraits({FunctorLaws.class, ApplicativeLaws.class, BifunctorLaws.class, TraversableLaws.class}) + @TestTraits({FunctorLaws.class, ApplicativeLaws.class, MonadLaws.class, BifunctorLaws.class, TraversableLaws.class}) public Tuple3 testSubject() { return tuple("one", 2, 3d); } @@ -77,4 +80,18 @@ public void into() { public void fill() { assertEquals(tuple("foo", "foo", "foo"), Tuple3.fill("foo")); } + + @Test + public void zipPrecedence() { + Tuple3 a = tuple("foo", 1, 2); + Tuple3> b = tuple("bar", 2, x -> x + 1); + assertEquals(tuple("foo", 1, 3), a.zip(b)); + } + + @Test + public void flatMapPrecedence() { + Tuple3 a = tuple("foo", 1, 2); + Function> b = x -> tuple("bar", 2, x + 1); + assertEquals(tuple("foo", 1, 3), a.flatMap(b)); + } } diff --git a/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple4Test.java b/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple4Test.java index bfa35bc28..44a6ba8dc 100644 --- a/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple4Test.java +++ b/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple4Test.java @@ -8,8 +8,11 @@ import testsupport.traits.ApplicativeLaws; import testsupport.traits.BifunctorLaws; import testsupport.traits.FunctorLaws; +import testsupport.traits.MonadLaws; import testsupport.traits.TraversableLaws; +import java.util.function.Function; + import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; import static org.junit.Assert.assertEquals; import static org.mockito.Mockito.spy; @@ -27,7 +30,7 @@ public void setUp() { tuple4 = new Tuple4<>(1, new Tuple3<>("2", new Tuple2<>('3', new SingletonHList<>(false)))); } - @TestTraits({FunctorLaws.class, ApplicativeLaws.class, BifunctorLaws.class, TraversableLaws.class}) + @TestTraits({FunctorLaws.class, ApplicativeLaws.class, MonadLaws.class, BifunctorLaws.class, TraversableLaws.class}) public Tuple4 testSubject() { return tuple("one", 2, 3d, 4f); } @@ -80,4 +83,18 @@ public void into() { public void fill() { assertEquals(tuple("foo", "foo", "foo", "foo"), Tuple4.fill("foo")); } + + @Test + public void zipPrecedence() { + Tuple4 a = tuple("foo", 1, 2, 3); + Tuple4> b = tuple("foo", 1, 2, x -> x + 1); + assertEquals(tuple("foo", 1, 2, 4), a.zip(b)); + } + + @Test + public void flatMapPrecedence() { + Tuple4 a = tuple("foo", 1, 2, 3); + Function> b = x -> tuple("bar", 2, 3, x + 1); + assertEquals(tuple("foo", 1, 2, 4), a.flatMap(b)); + } } \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple5Test.java b/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple5Test.java index eec152f81..ecf363e27 100644 --- a/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple5Test.java +++ b/src/test/java/com/jnape/palatable/lambda/adt/hlist/Tuple5Test.java @@ -9,8 +9,11 @@ import testsupport.traits.ApplicativeLaws; import testsupport.traits.BifunctorLaws; import testsupport.traits.FunctorLaws; +import testsupport.traits.MonadLaws; import testsupport.traits.TraversableLaws; +import java.util.function.Function; + import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; import static org.junit.Assert.assertEquals; import static org.mockito.Mockito.spy; @@ -28,7 +31,7 @@ public void setUp() { tuple5 = new Tuple5<>(1, new Tuple4<>("2", new Tuple3<>('3', new Tuple2<>(false, new SingletonHList<>(5L))))); } - @TestTraits({FunctorLaws.class, ApplicativeLaws.class, BifunctorLaws.class, TraversableLaws.class}) + @TestTraits({FunctorLaws.class, ApplicativeLaws.class, MonadLaws.class, BifunctorLaws.class, TraversableLaws.class}) public Tuple5 testSubject() { return tuple("one", 2, 3d, 4f, '5'); } @@ -78,4 +81,18 @@ public void randomAccess() { public void fill() { assertEquals(tuple("foo", "foo", "foo", "foo", "foo"), Tuple5.fill("foo")); } + + @Test + public void zipPrecedence() { + Tuple5 a = tuple("foo", 1, 2, 3, 4); + Tuple5> b = tuple("bar", 2, 3, 4, x -> x + 1); + assertEquals(tuple("foo", 1, 2, 3, 5), a.zip(b)); + } + + @Test + public void flatMapPrecedence() { + Tuple5 a = tuple("foo", 1, 2, 3, 4); + Function> b = x -> tuple("bar", 2, 3, 4, x + 1); + assertEquals(tuple("foo", 1, 2, 3, 5), a.flatMap(b)); + } } \ No newline at end of file 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 fdcc4b0cb..fa3881d40 100644 --- a/src/test/java/com/jnape/palatable/lambda/functions/Fn1Test.java +++ b/src/test/java/com/jnape/palatable/lambda/functions/Fn1Test.java @@ -7,6 +7,7 @@ import testsupport.EqualityAwareFn1; import testsupport.traits.ApplicativeLaws; import testsupport.traits.FunctorLaws; +import testsupport.traits.MonadLaws; import java.util.function.Function; @@ -15,7 +16,7 @@ @RunWith(Traits.class) public class Fn1Test { - @TestTraits({FunctorLaws.class, ApplicativeLaws.class}) + @TestTraits({FunctorLaws.class, ApplicativeLaws.class, MonadLaws.class}) public Fn1 testSubject() { return new EqualityAwareFn1<>("1", Integer::parseInt); } diff --git a/src/test/java/com/jnape/palatable/lambda/functor/builtin/ConstTest.java b/src/test/java/com/jnape/palatable/lambda/functor/builtin/ConstTest.java index 4ffc5bd0e..45588a462 100644 --- a/src/test/java/com/jnape/palatable/lambda/functor/builtin/ConstTest.java +++ b/src/test/java/com/jnape/palatable/lambda/functor/builtin/ConstTest.java @@ -6,12 +6,13 @@ import testsupport.traits.ApplicativeLaws; import testsupport.traits.BifunctorLaws; import testsupport.traits.FunctorLaws; +import testsupport.traits.MonadLaws; import testsupport.traits.TraversableLaws; @RunWith(Traits.class) public class ConstTest { - @TestTraits({FunctorLaws.class, ApplicativeLaws.class, BifunctorLaws.class, TraversableLaws.class}) + @TestTraits({FunctorLaws.class, ApplicativeLaws.class, MonadLaws.class, BifunctorLaws.class, TraversableLaws.class}) public Const testSubject() { return new Const<>(1); } diff --git a/src/test/java/com/jnape/palatable/lambda/functor/builtin/IdentityTest.java b/src/test/java/com/jnape/palatable/lambda/functor/builtin/IdentityTest.java index 8d267d8f2..7a46afd1d 100644 --- a/src/test/java/com/jnape/palatable/lambda/functor/builtin/IdentityTest.java +++ b/src/test/java/com/jnape/palatable/lambda/functor/builtin/IdentityTest.java @@ -5,12 +5,13 @@ import org.junit.runner.RunWith; import testsupport.traits.ApplicativeLaws; import testsupport.traits.FunctorLaws; +import testsupport.traits.MonadLaws; import testsupport.traits.TraversableLaws; @RunWith(Traits.class) public class IdentityTest { - @TestTraits({FunctorLaws.class, ApplicativeLaws.class, TraversableLaws.class}) + @TestTraits({FunctorLaws.class, ApplicativeLaws.class, MonadLaws.class, TraversableLaws.class}) public Identity testSubject() { return new Identity<>(""); } diff --git a/src/test/java/com/jnape/palatable/lambda/lens/LensTest.java b/src/test/java/com/jnape/palatable/lambda/lens/LensTest.java index 3e4ba46e6..942bcbd7f 100644 --- a/src/test/java/com/jnape/palatable/lambda/lens/LensTest.java +++ b/src/test/java/com/jnape/palatable/lambda/lens/LensTest.java @@ -10,6 +10,7 @@ import testsupport.EqualityAwareLens; import testsupport.traits.ApplicativeLaws; import testsupport.traits.FunctorLaws; +import testsupport.traits.MonadLaws; import java.util.List; import java.util.Map; @@ -33,7 +34,7 @@ public class LensTest { private static final Lens>, Map>, List, Set> EARLIER_LENS = lens(m -> m.get("foo"), (m, s) -> singletonMap("foo", s)); private static final Lens, Set, String, Integer> LENS = lens(xs -> xs.get(0), (xs, i) -> singleton(i)); - @TestTraits({FunctorLaws.class, ApplicativeLaws.class}) + @TestTraits({FunctorLaws.class, ApplicativeLaws.class, MonadLaws.class}) public Lens, List, Integer, String> testSubject() { return new EqualityAwareLens<>(emptyMap(), lens(m -> m.get("foo"), (m, s) -> singletonList(m.get(s)))); } diff --git a/src/test/java/com/jnape/palatable/lambda/traversable/LambdaIterableTest.java b/src/test/java/com/jnape/palatable/lambda/traversable/LambdaIterableTest.java index b72308be1..9947e7f3a 100644 --- a/src/test/java/com/jnape/palatable/lambda/traversable/LambdaIterableTest.java +++ b/src/test/java/com/jnape/palatable/lambda/traversable/LambdaIterableTest.java @@ -7,6 +7,7 @@ import org.junit.runner.RunWith; import testsupport.traits.ApplicativeLaws; import testsupport.traits.FunctorLaws; +import testsupport.traits.MonadLaws; import testsupport.traits.TraversableLaws; import java.util.function.Function; @@ -14,7 +15,6 @@ import static com.jnape.palatable.lambda.functions.builtin.fn2.Replicate.replicate; import static com.jnape.palatable.traitor.framework.Subjects.subjects; import static java.util.Arrays.asList; -import static java.util.Collections.emptyList; import static java.util.Collections.singleton; import static org.junit.Assert.assertThat; import static testsupport.matchers.IterableMatcher.iterates; @@ -22,9 +22,9 @@ @RunWith(Traits.class) public class LambdaIterableTest { - @TestTraits({FunctorLaws.class, ApplicativeLaws.class, TraversableLaws.class}) + @TestTraits({FunctorLaws.class, ApplicativeLaws.class, TraversableLaws.class, MonadLaws.class}) public Subjects> testSubject() { - return subjects(LambdaIterable.wrap(emptyList()), LambdaIterable.wrap(singleton(1)), LambdaIterable.wrap(replicate(100, 1))); + return subjects(LambdaIterable.empty(), LambdaIterable.wrap(singleton(1)), LambdaIterable.wrap(replicate(100, 1))); } @Test diff --git a/src/test/java/com/jnape/palatable/lambda/traversable/LambdaOptionalTest.java b/src/test/java/com/jnape/palatable/lambda/traversable/LambdaOptionalTest.java index 0ddfc63a4..9cf67c442 100644 --- a/src/test/java/com/jnape/palatable/lambda/traversable/LambdaOptionalTest.java +++ b/src/test/java/com/jnape/palatable/lambda/traversable/LambdaOptionalTest.java @@ -5,6 +5,7 @@ import org.junit.runner.RunWith; import testsupport.traits.ApplicativeLaws; import testsupport.traits.FunctorLaws; +import testsupport.traits.MonadLaws; import testsupport.traits.TraversableLaws; import java.util.Optional; @@ -12,7 +13,7 @@ @RunWith(Traits.class) public class LambdaOptionalTest { - @TestTraits({FunctorLaws.class, ApplicativeLaws.class, TraversableLaws.class}) + @TestTraits({FunctorLaws.class, ApplicativeLaws.class, TraversableLaws.class, MonadLaws.class}) public LambdaOptional testSubject() { return LambdaOptional.wrap(Optional.of(new Object())); } diff --git a/src/test/java/testsupport/EqualityAwareFn1.java b/src/test/java/testsupport/EqualityAwareFn1.java index 8e121a761..464962d19 100644 --- a/src/test/java/testsupport/EqualityAwareFn1.java +++ b/src/test/java/testsupport/EqualityAwareFn1.java @@ -2,6 +2,7 @@ import com.jnape.palatable.lambda.functions.Fn1; import com.jnape.palatable.lambda.functor.Applicative; +import com.jnape.palatable.lambda.monad.Monad; import java.util.function.Function; @@ -21,6 +22,11 @@ public B apply(A a) { return fn.apply(a); } + @Override + public EqualityAwareFn1 flatMap(Function>> f) { + return new EqualityAwareFn1<>(a, fn.flatMap(f)); + } + @Override public EqualityAwareFn1 fmap(Function f) { return new EqualityAwareFn1<>(a, fn.fmap(f)); diff --git a/src/test/java/testsupport/EqualityAwareLens.java b/src/test/java/testsupport/EqualityAwareLens.java index e2426c35c..f70f96c37 100644 --- a/src/test/java/testsupport/EqualityAwareLens.java +++ b/src/test/java/testsupport/EqualityAwareLens.java @@ -4,6 +4,7 @@ import com.jnape.palatable.lambda.functor.Functor; import com.jnape.palatable.lambda.functor.builtin.Const; import com.jnape.palatable.lambda.lens.Lens; +import com.jnape.palatable.lambda.monad.Monad; import java.util.Objects; import java.util.function.Function; @@ -23,6 +24,11 @@ public , FB extends Functor> F return lens.apply(fn, s); } + @Override + public EqualityAwareLens flatMap(Function>> f) { + return new EqualityAwareLens<>(s, lens.flatMap(f)); + } + @Override public EqualityAwareLens fmap(Function fn) { return new EqualityAwareLens<>(s, lens.fmap(fn)); diff --git a/src/test/java/testsupport/traits/MonadLaws.java b/src/test/java/testsupport/traits/MonadLaws.java new file mode 100644 index 000000000..9215abb66 --- /dev/null +++ b/src/test/java/testsupport/traits/MonadLaws.java @@ -0,0 +1,50 @@ +package testsupport.traits; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.monad.Monad; +import com.jnape.palatable.lambda.monoid.builtin.Present; +import com.jnape.palatable.traitor.traits.Trait; + +import java.util.Optional; +import java.util.function.Function; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; +import static java.util.Arrays.asList; + +public class MonadLaws implements Trait> { + + @Override + public void test(Monad m) { + Present.present((x, y) -> x + "\n\t - " + y) + ., Optional>>foldMap(f -> f.apply(m), asList( + this::testLeftIdentity, + this::testRightIdentity, + this::testAssociativity)) + .ifPresent(s -> { + throw new AssertionError("The following Monad laws did not hold for instance of " + m.getClass() + ": \n\t - " + s); + }); + } + + private Optional testLeftIdentity(Monad m) { + Object a = new Object(); + Fn1> fn = id().andThen(m::pure); + return m.pure(a).flatMap(fn).equals(fn.apply(a)) + ? Optional.empty() + : Optional.of("left identity (m.pure(a).flatMap(fn).equals(fn.apply(a)))"); + } + + private Optional testRightIdentity(Monad m) { + return m.flatMap(m::pure).equals(m) + ? Optional.empty() + : Optional.of("right identity: (m.flatMap(m::pure).equals(m))"); + } + + private Optional testAssociativity(Monad m) { + Fn1> f = constantly(m.pure(new Object())); + Function> g = constantly(m.pure(new Object())); + return m.flatMap(f).flatMap(g).equals(m.flatMap(a -> f.apply(a).flatMap(g))) + ? Optional.empty() + : Optional.of("associativity: (m.flatMap(f).flatMap(g).equals(m.flatMap(a -> f.apply(a).flatMap(g))))"); + } +} From afc996ad0f6e9ab0915e9c01db3f6d4c4096ef6a Mon Sep 17 00:00:00 2001 From: jnape Date: Sun, 22 Oct 2017 16:05:32 -0500 Subject: [PATCH 17/32] Making Replicate instance private --- .../jnape/palatable/lambda/functions/builtin/fn2/Replicate.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Replicate.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Replicate.java index 5e08d92dc..d810f4938 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Replicate.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Replicate.java @@ -13,7 +13,7 @@ */ public final class Replicate implements Fn2> { - public static final Replicate INSTANCE = new Replicate(); + private static final Replicate INSTANCE = new Replicate(); private Replicate() { } From 31a329344b7bad6a0562af6fae6d7bfe8a8de216 Mon Sep 17 00:00:00 2001 From: jnape Date: Sun, 22 Oct 2017 16:09:56 -0500 Subject: [PATCH 18/32] Removing duplicate import; fixing typo --- CHANGELOG.md | 2 +- src/main/java/com/jnape/palatable/lambda/lens/Lens.java | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4c6bf8e4c..2fb7706a2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -168,7 +168,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/). ## [1.1] - 2016-06-21 ### Added - `scanLeft` -- `HList`, heterogenous lists +- `HList`, heterogeneous lists - Added up to `Tuple5` - `Either`, specialized coproduct with success/failure semantics diff --git a/src/main/java/com/jnape/palatable/lambda/lens/Lens.java b/src/main/java/com/jnape/palatable/lambda/lens/Lens.java index 621ec31a7..4a4c32f2d 100644 --- a/src/main/java/com/jnape/palatable/lambda/lens/Lens.java +++ b/src/main/java/com/jnape/palatable/lambda/lens/Lens.java @@ -1,6 +1,5 @@ package com.jnape.palatable.lambda.lens; -import com.jnape.palatable.lambda.monad.Monad; import com.jnape.palatable.lambda.functions.Fn2; import com.jnape.palatable.lambda.functor.Applicative; import com.jnape.palatable.lambda.functor.Functor; From fb3a2729d8099b2844253dc72e7a4c4baa746871 Mon Sep 17 00:00:00 2001 From: jnape Date: Sun, 29 Oct 2017 14:52:00 -0500 Subject: [PATCH 19/32] Adding Monad to README --- README.md | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/README.md b/README.md index 480f6ecc0..b97d83984 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,7 @@ Functional patterns for Java 8 - [Bifunctors](#bifunctors) - [Profunctors](#profunctors) - [Applicatives](#applicatives) + - [Monads](#monads) - [Traversables](#traversables) - [ADTs](#adts) - [HLists](#hlists) @@ -335,6 +336,41 @@ Examples of applicative functors include: In addition to implementing `fmap` from `Functor`, implementing an applicative functor involves providing two methods: `pure`, a method that lifts a value into the functor; and `zip`, a method that applies a lifted function to a lifted value, returning a new lifted value. As usual, there are [some laws](https://hackage.haskell.org/package/base-4.9.1.0/docs/Control-Applicative.html) that should be adhered to. +### Monads + +Monads are applicative functors that additionally support a chaining operation, `flatMap :: (a -> f b) -> f a -> f b`: a function from the functor's parameter to a new instance of the same functor over a potentially different parameter. Because the function passed to `flatMap` can return a different instance of the same functor, functors can take advantage of multiple constructions that yield different results functorial operations, like short-circuiting, as in the following example using `Either`: + +```Java +class Person { + Optional occupation() { + return Optional.empty(); + } +} + +class Occupation { +} + +public static void main(String[] args) { + Fn1> parseId = str -> Either.trying(() -> Integer.parseInt(str), __ -> str + " is not a valid id"); + + Map database = new HashMap<>(); + Fn1> lookupById = id -> Either.fromOptional(Optional.ofNullable(database.get(id)), + () -> "No person found for id " + id); + Fn1> getOccupation = p -> Either.fromOptional(p.occupation(), () -> "Person was unemployed"); + + Either occupationOrError = + parseId.apply("12") // Either + .flatMap(lookupById) // Either + .flatMap(getOccupation); // Either +} +``` + +In the previous example, if any of `parseId`, `lookupById`, or `getOccupation` fail, no further `flatMap` computations can succeed, so the result short-circuits to the first `left` value that is returned. This is completely predictable from the type signature of `Monad` and `Either`: `Either` is a `Monad`, so the single arity `flatMap` can have nothing to map in the case where there is no `R` value. With experience, it generally becomes quickly clear what the logical behavior of `flatMap` *must* be given the type signatures. + +That's it. Monads are neither [elephants](http://james-iry.blogspot.com/2007/09/monads-are-elephants-part-1.html) nor are they [burritos](https://blog.plover.com/prog/burritos.html); they're simply types that support a) the ability to lift a value into them, and b) a chaining function `flatMap :: (a -> f b) -> f a -> f b` that can potentially return different instances of the same monad. If a type can do those two things (and obeys [the laws](https://wiki.haskell.org/Monad_laws)), it is a monad. + +Further, if a type is a monad, it is necessarily an `Applicative`, which makes it necessariliy a `Functor`, so *lambda* enforces this tautology via a hierarchical constraint. + ### Traversables Traversable functors -- functors that can be "traversed from left to right" -- are implemented via the `Traversable` interface. From fdd5c28a3f2d14c80c8c34a3f7bd60ea68e3015d Mon Sep 17 00:00:00 2001 From: jnape Date: Mon, 30 Oct 2017 22:36:21 -0500 Subject: [PATCH 20/32] Fixing eager invocation of non-matching functions in CoProductN#embed --- CHANGELOG.md | 3 +++ .../java/com/jnape/palatable/lambda/adt/Either.java | 2 +- .../palatable/lambda/adt/coproduct/CoProduct2.java | 6 +++++- .../palatable/lambda/adt/coproduct/CoProduct3.java | 9 ++++++--- .../palatable/lambda/adt/coproduct/CoProduct4.java | 11 +++++++---- .../palatable/lambda/adt/coproduct/CoProduct5.java | 13 ++++++++----- 6 files changed, 30 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2fb7706a2..23e5eab5f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,9 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/). ## [Unreleased] +### Fixed +- `CoProductN#embed` no longer eagerly invokes functions + ### Added - `Force`, for forcing iteration of an `Iterable` to perform any side-effects - `Snoc`, for lazily appending an element to the end of an `Iterable` 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 a0fbdb333..d020d570e 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/Either.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/Either.java @@ -1,11 +1,11 @@ package com.jnape.palatable.lambda.adt; -import com.jnape.palatable.lambda.monad.Monad; 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.Applicative; import com.jnape.palatable.lambda.functor.Bifunctor; +import com.jnape.palatable.lambda.monad.Monad; import com.jnape.palatable.lambda.traversable.Traversable; import java.util.Objects; 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 index 8ae141a51..f6377bf70 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct2.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct2.java @@ -3,11 +3,13 @@ import com.jnape.palatable.lambda.adt.Either; import com.jnape.palatable.lambda.adt.choice.Choice2; import com.jnape.palatable.lambda.adt.hlist.Tuple2; +import com.jnape.palatable.lambda.functions.Fn1; 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.Fn1.fn1; import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; /** @@ -122,6 +124,8 @@ public R match(Function aFn, Function R embed(Function aFn, Function bFn) { - return match(constantly(aFn.apply((CP2) this)), constantly(bFn.apply((CP2) this))); + return this.>match(constantly(fn1(aFn)), + constantly(fn1(bFn))) + .apply((CP2) this); } } 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 index 28024dc03..32f743cbb 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct3.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct3.java @@ -1,11 +1,13 @@ package com.jnape.palatable.lambda.adt.coproduct; import com.jnape.palatable.lambda.adt.hlist.Tuple3; +import com.jnape.palatable.lambda.functions.Fn1; 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.Fn1.fn1; import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; /** @@ -131,8 +133,9 @@ default Optional projectC() { default R embed(Function aFn, Function bFn, Function cFn) { - return match(constantly(aFn.apply((CP3) this)), - constantly(bFn.apply((CP3) this)), - constantly(cFn.apply((CP3) this))); + return this.>match(constantly(fn1(aFn)), + constantly(fn1(bFn)), + constantly(fn1(cFn))) + .apply((CP3) this); } } 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 index 58f33219d..a9a6d7d10 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct4.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct4.java @@ -1,11 +1,13 @@ package com.jnape.palatable.lambda.adt.coproduct; import com.jnape.palatable.lambda.adt.hlist.Tuple4; +import com.jnape.palatable.lambda.functions.Fn1; 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.Fn1.fn1; import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; /** @@ -151,10 +153,11 @@ default R embed(Function aFn, Function bFn, Function cFn, Function dFn) { - return match(constantly(aFn.apply((CP4) this)), - constantly(bFn.apply((CP4) this)), - constantly(cFn.apply((CP4) this)), - constantly(dFn.apply((CP4) this))); + return this.>match(constantly(fn1(aFn)), + constantly(fn1(bFn)), + constantly(fn1(cFn)), + constantly(fn1(dFn))) + .apply((CP4) this); } } 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 index 8b1a39c67..783eac16b 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct5.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct5.java @@ -1,11 +1,13 @@ package com.jnape.palatable.lambda.adt.coproduct; import com.jnape.palatable.lambda.adt.hlist.Tuple5; +import com.jnape.palatable.lambda.functions.Fn1; 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.Fn1.fn1; import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; /** @@ -154,10 +156,11 @@ default R embed(Function aFn, Function cFn, Function dFn, Function eFn) { - return match(constantly(aFn.apply((CP5) this)), - constantly(bFn.apply((CP5) this)), - constantly(cFn.apply((CP5) this)), - constantly(dFn.apply((CP5) this)), - constantly(eFn.apply((CP5) this))); + return this.>match(constantly(fn1(aFn)), + constantly(fn1(bFn)), + constantly(fn1(cFn)), + constantly(fn1(dFn)), + constantly(fn1(eFn))) + .apply((CP5) this); } } From 0f4bf367953d5da5f52604a6b486c79787026a0c Mon Sep 17 00:00:00 2001 From: jnape Date: Mon, 30 Oct 2017 23:13:19 -0500 Subject: [PATCH 21/32] fixing javadocs --- .../java/com/jnape/palatable/lambda/functor/Profunctor.java | 3 --- src/main/java/com/jnape/palatable/lambda/monad/Monad.java | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/src/main/java/com/jnape/palatable/lambda/functor/Profunctor.java b/src/main/java/com/jnape/palatable/lambda/functor/Profunctor.java index 0dd9a1e67..948bdda51 100644 --- a/src/main/java/com/jnape/palatable/lambda/functor/Profunctor.java +++ b/src/main/java/com/jnape/palatable/lambda/functor/Profunctor.java @@ -59,9 +59,6 @@ default Profunctor diMapR(Function fn) { return diMap(id(), fn); } - /** - * @inheritDoc - */ @Override default Profunctor contraMap(Function fn) { return diMapL(fn); diff --git a/src/main/java/com/jnape/palatable/lambda/monad/Monad.java b/src/main/java/com/jnape/palatable/lambda/monad/Monad.java index 47a902b1a..770566cbf 100644 --- a/src/main/java/com/jnape/palatable/lambda/monad/Monad.java +++ b/src/main/java/com/jnape/palatable/lambda/monad/Monad.java @@ -16,7 +16,7 @@ *
    *
  • left identity: m.pure(a).flatMap(fn).equals(fn.apply(a))
  • *
  • right identity: m.flatMap(m::pure).equals(m)
  • - *
  • associativity: m.flatMap(f).flatMap(g).equals(m.flatMap(a -> f.apply(a).flatMap(g)))
  • + *
  • associativity: m.flatMap(f).flatMap(g).equals(m.flatMap(a -> f.apply(a).flatMap(g)))
  • *
*

* For more information, read about From a593cf370647fd872f0f8881523c70b7a10e1388 Mon Sep 17 00:00:00 2001 From: jnape Date: Sun, 22 Oct 2017 15:21:10 -0500 Subject: [PATCH 22/32] Maybe arrives; alas poor LambdaOptional, we barely knew him. --- CHANGELOG.md | 8 +- .../com/jnape/palatable/lambda/adt/Maybe.java | 282 ++++++++++++++++++ .../functions/builtin/fn2/Sequence.java | 8 +- .../palatable/lambda/functor/Profunctor.java | 2 +- .../lambda/lens/lenses/MaybeLens.java | 39 +++ .../palatable/lambda/traversable/Lambda.java | 34 --- .../lambda/traversable/LambdaOptional.java | 103 ------- .../traversable/TraversableOptional.java | 3 +- .../lambda/traversable/Traversables.java | 8 +- .../jnape/palatable/lambda/adt/MaybeTest.java | 111 +++++++ .../traversable/LambdaOptionalTest.java | 20 -- 11 files changed, 448 insertions(+), 170 deletions(-) create mode 100644 src/main/java/com/jnape/palatable/lambda/adt/Maybe.java create mode 100644 src/main/java/com/jnape/palatable/lambda/lens/lenses/MaybeLens.java delete mode 100644 src/main/java/com/jnape/palatable/lambda/traversable/Lambda.java delete mode 100644 src/main/java/com/jnape/palatable/lambda/traversable/LambdaOptional.java create mode 100644 src/test/java/com/jnape/palatable/lambda/adt/MaybeTest.java delete mode 100644 src/test/java/com/jnape/palatable/lambda/traversable/LambdaOptionalTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 23e5eab5f..621415190 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,8 +12,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/). - `Snoc`, for lazily appending an element to the end of an `Iterable` - `Coalesce`, for folding an `Iterable>` into an `Either, Iterable>` - `And`, `Or`, and `Xor` all gain `BiPredicate` properties -- `LambdaOptional` and `LambdaIterable`, adapters for `Optional` and `Iterable` that support lambda types -- `Lambda`, providing static factory methods for `LambdaOptional` and `LambdaIterable` +- `LambdaIterable`, an adapter `Iterable` that support lambda types +- `Maybe`, lambda's analog of `java.util.Optional` conforming to all the lambda types - `Contravariant`, an interface representing functors that map contravariantly over their parameters - `Profunctor` extends `Contravariant` @@ -23,8 +23,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/). - `Fn2#adapt(BiFunction biFunction)`, deprecated in previous release ### Deprecated -- `Traversables` and all methods therein, in favor of new `Lambda` methods -- `TraversableOptional` in favor of `LambdaOptional` +- `Traversables` and all methods therein, in favor of either `LambdaIterable` or `Maybe` +- `TraversableOptional` in favor of `Maybe` - `TraversableIterable` in favor of `LambdaIterable` ### Changed diff --git a/src/main/java/com/jnape/palatable/lambda/adt/Maybe.java b/src/main/java/com/jnape/palatable/lambda/adt/Maybe.java new file mode 100644 index 000000000..cf0600a7c --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/adt/Maybe.java @@ -0,0 +1,282 @@ +package com.jnape.palatable.lambda.adt; + +import com.jnape.palatable.lambda.functions.specialized.checked.CheckedSupplier; +import com.jnape.palatable.lambda.functor.Applicative; +import com.jnape.palatable.lambda.functor.Functor; +import com.jnape.palatable.lambda.monad.Monad; +import com.jnape.palatable.lambda.traversable.Traversable; + +import java.util.Objects; +import java.util.Optional; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Supplier; + +import static com.jnape.palatable.lambda.adt.Either.left; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; + +/** + * The optional type, representing a potentially absent value. This is lambda's analog of {@link Optional}, supporting + * all the usual suspects like {@link Functor}, {@link Applicative}, {@link Traversable}, etc. + * + * @param the optional parameter type + * @see Optional + */ +public abstract class Maybe implements Monad, Traversable { + private Maybe() { + } + + /** + * If the value is present, return it; otherwise, return the value supplied by otherSupplier. + * + * @param otherSupplier the supplier for the other value + * @return this value, or the supplied other value + */ + public abstract A orElseGet(Supplier otherSupplier); + + /** + * If the value is present, return it; otherwise, return other. + * + * @param other the other value + * @return this value, or the other value + */ + public final A orElse(A other) { + return orElseGet(() -> other); + } + + /** + * If the value is present, return it; otherwise, throw the {@link Throwable} supplied by + * throwableSupplier. + * + * @param throwableSupplier the supplier of the potentially thrown {@link Throwable} + * @param the Throwable type + * @return the value, if present + * @throws E the throwable, if the value is absent + */ + public final A orThrow(Supplier throwableSupplier) throws E { + return orElseGet((CheckedSupplier) () -> { + throw throwableSupplier.get(); + }); + } + + /** + * If this value is present and satisfies predicate, return just the value; otherwise, + * return nothing. + * + * @param predicate the predicate to apply to the possibly absent value + * @return maybe the present value that satisfied the predicate + */ + public final Maybe filter(Function predicate) { + return flatMap(a -> predicate.apply(a) ? just(a) : nothing()); + } + + /** + * If this value is absent, return the value supplied by lSupplier wrapped in Either.left. + * Otherwise, wrap the value in Either.right and return it. + * + * @param lSupplier the supplier for the left value + * @param the left parameter type + * @return this value wrapped in an Either.right, or an Either.left around the result of lSupplier + */ + public final Either toEither(Supplier lSupplier) { + return fmap(Either::right).orElseGet(() -> left(lSupplier.get())); + } + + /** + * Convert to {@link Optional}. + * + * @return the Optional + */ + public final Optional toOptional() { + return fmap(Optional::of).orElseGet(Optional::empty); + } + + /** + * Lift the value into the {@link Maybe} monad + * + * @param b the value + * @param the value type + * @return Just b + */ + @Override + public final Maybe pure(B b) { + return just(b); + } + + /** + * {@inheritDoc} + *

+ * If the value is present, return {@link Maybe#just} fn applied to the value; otherwise, return + * {@link Maybe#nothing}. + */ + @Override + public final Maybe fmap(Function fn) { + return Monad.super.fmap(fn).coerce(); + } + + @Override + public final Maybe zip(Applicative, Maybe> appFn) { + return Monad.super.zip(appFn).coerce(); + } + + @Override + public final Maybe discardL(Applicative appB) { + return Monad.super.discardL(appB).coerce(); + } + + @Override + public final Maybe discardR(Applicative appB) { + return Monad.super.discardR(appB).coerce(); + } + + @Override + public abstract Maybe flatMap(Function> f); + + @Override + public abstract Applicative, App> traverse( + Function> fn, + Function, ? extends Applicative, App>> pure); + + /** + * If this value is present, accept it by consumer; otherwise, do nothing. + * + * @param consumer the consumer + * @return the same Maybe instance + */ + public final Maybe peek(Consumer consumer) { + return fmap(a -> { + consumer.accept(a); + return a; + }); + } + + /** + * Convenience static factory method for creating a {@link Maybe} from an {@link Either}. If either is + * a right value, wrap the value in a just and return it; otherwise, return {@link #nothing()}. + * + * @param either the either instance + * @param the potential right value + * @return "Just" the right value, or nothing + */ + public static Maybe fromEither(Either either) { + return either.match(constantly(nothing()), Maybe::just); + } + + /** + * Convenience static factory method for creating a {@link Maybe} from an {@link Optional}. + * + * @param optional the optional + * @param the optional parameter type + * @return the equivalent Maybe instance + */ + public static Maybe fromOptional(Optional optional) { + return optional.map(id()).map(Maybe::just).orElse(Maybe.nothing()); + } + + /** + * Lift a potentially null value into {@link Maybe}. If a is not null, returns just(a); + * otherwise, returns {@link #nothing()}. + * + * @param a the potentially null value + * @param the value parameter type + * @return "Just" the value, or nothing + */ + public static Maybe maybe(A a) { + return a == null ? nothing() : just(a); + } + + /** + * Lift a non-null value into {@link Maybe}. This differs from {@link Maybe#maybe} in that the value *must* be + * non-null; if it is null, a {@link NullPointerException} is thrown. + * + * @param a the non-null value + * @param the value parameter type + * @return "Just" the value + * @throws NullPointerException if a is null + */ + public static Maybe just(A a) { + if (a == null) + throw new NullPointerException(); + return new Just<>(a); + } + + @SuppressWarnings("unchecked") + public static Maybe nothing() { + return Nothing.INSTANCE; + } + + private static final class Just extends Maybe { + + private final A a; + + private Just(A a) { + this.a = a; + } + + @Override + public A orElseGet(Supplier otherSupplier) { + return a; + } + + @Override + public Maybe flatMap(Function> f) { + return f.apply(a).coerce(); + } + + @Override + public Applicative, App> traverse( + Function> fn, + Function, ? extends Applicative, App>> pure) { + return fn.apply(a).fmap(Just::new); + } + + @Override + public boolean equals(Object other) { + return other instanceof Just && Objects.equals(this.a, ((Just) other).a); + } + + @Override + public int hashCode() { + return Objects.hash(a); + } + + @Override + public String toString() { + return "Just{" + + "a=" + a + + '}'; + } + } + + private static final class Nothing extends Maybe { + private static final Nothing INSTANCE = new Nothing(); + + private Nothing() { + } + + @Override + @SuppressWarnings("unchecked") + public Maybe flatMap(Function> f) { + return nothing(); + } + + @Override + @SuppressWarnings("unchecked") + public Applicative, App> traverse( + Function> fn, + Function, ? extends Applicative, App>> pure) { + return (Applicative, App>) pure.apply(nothing()); + } + + @Override + public A orElseGet(Supplier otherSupplier) { + return otherSupplier.get(); + } + + @Override + public String toString() { + return "Nothing{}"; + } + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Sequence.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Sequence.java index 4df1d3360..6fb76ee63 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Sequence.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Sequence.java @@ -1,10 +1,10 @@ package com.jnape.palatable.lambda.functions.builtin.fn2; +import com.jnape.palatable.lambda.adt.Maybe; import com.jnape.palatable.lambda.functions.Fn1; import com.jnape.palatable.lambda.functions.Fn2; import com.jnape.palatable.lambda.functor.Applicative; import com.jnape.palatable.lambda.traversable.LambdaIterable; -import com.jnape.palatable.lambda.traversable.LambdaOptional; import com.jnape.palatable.lambda.traversable.Traversable; import java.util.Optional; @@ -77,9 +77,9 @@ TravApp extends Traversable, Trav>> AppTrav sequen @SuppressWarnings("unchecked") public static , App>, OptionalApp extends Optional>> Fn1, ? extends AppOptional>, AppOptional> sequence( OptionalApp optionalApp) { - return pure -> (AppOptional) sequence(LambdaOptional.wrap(optionalApp), x -> pure.apply(((LambdaOptional) x).unwrap()) - .fmap(LambdaOptional::wrap)) - .fmap(LambdaOptional::unwrap); + return pure -> (AppOptional) sequence(Maybe.fromOptional(optionalApp), x -> pure.apply(((Maybe) x).toOptional()) + .fmap(Maybe::fromOptional)) + .fmap(Maybe::toOptional); } public static , App>, diff --git a/src/main/java/com/jnape/palatable/lambda/functor/Profunctor.java b/src/main/java/com/jnape/palatable/lambda/functor/Profunctor.java index 948bdda51..0e7235b44 100644 --- a/src/main/java/com/jnape/palatable/lambda/functor/Profunctor.java +++ b/src/main/java/com/jnape/palatable/lambda/functor/Profunctor.java @@ -26,7 +26,7 @@ public interface Profunctor extends ContravariantdiMapL(lFn).diMapR(rFn).¬ + * to diMapL(lFn).diMapR(rFn). * * @param the new left parameter type * @param the new right parameter type diff --git a/src/main/java/com/jnape/palatable/lambda/lens/lenses/MaybeLens.java b/src/main/java/com/jnape/palatable/lambda/lens/lenses/MaybeLens.java new file mode 100644 index 000000000..0faedb561 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/lens/lenses/MaybeLens.java @@ -0,0 +1,39 @@ +package com.jnape.palatable.lambda.lens.lenses; + +import com.jnape.palatable.lambda.adt.Maybe; +import com.jnape.palatable.lambda.lens.Lens; + +import static com.jnape.palatable.lambda.lens.Lens.simpleLens; +import static com.jnape.palatable.lambda.lens.functions.Set.set; +import static com.jnape.palatable.lambda.lens.functions.View.view; + +/** + * Lenses for {@link Maybe}. + */ +public final class MaybeLens { + private MaybeLens() { + } + + public static Lens, T, A, B> liftS(Lens lens, S defaultValue) { + return lens.mapS(m -> m.orElse(defaultValue)); + } + + public static Lens, A, B> liftT(Lens lens) { + return lens.mapT(Maybe::just); + } + + public static Lens, B> liftA(Lens lens) { + return lens.mapA(Maybe::just); + } + + public static Lens> liftB(Lens lens, B defaultValue) { + return lens.mapB(m -> m.orElse(defaultValue)); + } + + public static Lens.Simple, Maybe> asMaybe(Lens lens) { + return simpleLens(m -> m.fmap(view(lens)), + (maybeS, maybeB) -> maybeS.flatMap(s -> maybeB.fmap(a -> { + return set(lens, a, s); + }))); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/traversable/Lambda.java b/src/main/java/com/jnape/palatable/lambda/traversable/Lambda.java deleted file mode 100644 index 150bef15d..000000000 --- a/src/main/java/com/jnape/palatable/lambda/traversable/Lambda.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.jnape.palatable.lambda.traversable; - -import java.util.Optional; - -/** - * Static factory methods for adapting core JDK types to Lambda interfaces. - */ -public final class Lambda { - - private Lambda() { - } - - /** - * Wrap an {@link Iterable} in a {@link LambdaIterable}. - * - * @param as the Iterable - * @param the Iterable element type - * @return a LambdaIterable - */ - public static LambdaIterable lambda(Iterable as) { - return LambdaIterable.wrap(as); - } - - /** - * Wrap an {@link Optional} in a {@link LambdaOptional}. - * - * @param opt the Optional - * @param the Optional type - * @return a LambdaOptional - */ - public static LambdaOptional lambda(Optional opt) { - return LambdaOptional.wrap(opt); - } -} diff --git a/src/main/java/com/jnape/palatable/lambda/traversable/LambdaOptional.java b/src/main/java/com/jnape/palatable/lambda/traversable/LambdaOptional.java deleted file mode 100644 index d93b8a2c2..000000000 --- a/src/main/java/com/jnape/palatable/lambda/traversable/LambdaOptional.java +++ /dev/null @@ -1,103 +0,0 @@ -package com.jnape.palatable.lambda.traversable; - -import com.jnape.palatable.lambda.functor.Applicative; -import com.jnape.palatable.lambda.functor.Functor; -import com.jnape.palatable.lambda.monad.Monad; - -import java.util.Objects; -import java.util.Optional; -import java.util.function.Function; - -/** - * Adapt an {@link Optional} to support generic lambda interfaces like {@link Functor}, {@link Applicative}, - * {@link Traversable}, etc. - * - * @param the Optional parameter type - */ -public final class LambdaOptional implements Monad, Traversable { - private final Optional delegate; - - @SuppressWarnings("unchecked") - private LambdaOptional(Optional delegate) { - this.delegate = (Optional) delegate; - } - - /** - * Unwrap the underlying {@link Optional}. - * - * @return the wrapped Optional - */ - public Optional unwrap() { - return delegate; - } - - @Override - public LambdaOptional fmap(Function fn) { - return new LambdaOptional<>(delegate.map(fn)); - } - - @Override - public LambdaOptional pure(B b) { - return wrap(Optional.of(b)); - } - - @Override - public LambdaOptional zip(Applicative, LambdaOptional> appFn) { - return wrap(appFn.>>coerce().unwrap().flatMap(delegate::map)); - } - - @Override - public LambdaOptional discardL(Applicative appB) { - return Monad.super.discardL(appB).coerce(); - } - - @Override - public LambdaOptional discardR(Applicative appB) { - return Monad.super.discardR(appB).coerce(); - } - - @Override - public LambdaOptional flatMap(Function> f) { - return wrap(delegate.flatMap(a -> f.apply(a).>coerce().unwrap())); - } - - @Override - public Applicative, App> traverse( - Function> fn, - Function, ? extends Applicative, App>> pure) { - return fmap(fn).delegate.map(app -> app.fmap(Optional::of).fmap(LambdaOptional::wrap)) - .orElseGet(() -> pure.apply(LambdaOptional.empty()).fmap(x -> (LambdaOptional) x)); - } - - @Override - public boolean equals(Object other) { - return other instanceof LambdaOptional - && Objects.equals(delegate, ((LambdaOptional) other).delegate); - } - - @Override - public int hashCode() { - return Objects.hash(delegate); - } - - /** - * Wrap an {@link Optional} in a TraversableOptional. - * - * @param optional the Optional - * @param the Optional parameter type - * @return the Optional wrapped in a TraversableOptional - */ - public static LambdaOptional wrap(Optional optional) { - return new LambdaOptional<>(optional); - } - - /** - * Construct an empty TraversableOptional by wrapping {@link Optional#empty()}. - * - * @param the optional parameter type - * @return a TraversableOptional wrapping Optional.empty() - */ - public static LambdaOptional empty() { - return LambdaOptional.wrap(Optional.empty()); - } -} diff --git a/src/main/java/com/jnape/palatable/lambda/traversable/TraversableOptional.java b/src/main/java/com/jnape/palatable/lambda/traversable/TraversableOptional.java index f5a6057fc..c8f2ab62b 100644 --- a/src/main/java/com/jnape/palatable/lambda/traversable/TraversableOptional.java +++ b/src/main/java/com/jnape/palatable/lambda/traversable/TraversableOptional.java @@ -1,5 +1,6 @@ package com.jnape.palatable.lambda.traversable; +import com.jnape.palatable.lambda.adt.Maybe; import com.jnape.palatable.lambda.functor.Applicative; import java.util.Objects; @@ -12,7 +13,7 @@ * Returns the result of pure if the wrapped {@link Optional} is empty. * * @param the Optional parameter type - * @deprecated in favor of {@link LambdaOptional} + * @deprecated in favor of {@link Maybe} */ @Deprecated public final class TraversableOptional implements Traversable { diff --git a/src/main/java/com/jnape/palatable/lambda/traversable/Traversables.java b/src/main/java/com/jnape/palatable/lambda/traversable/Traversables.java index 210f3e428..efac80658 100644 --- a/src/main/java/com/jnape/palatable/lambda/traversable/Traversables.java +++ b/src/main/java/com/jnape/palatable/lambda/traversable/Traversables.java @@ -1,11 +1,13 @@ package com.jnape.palatable.lambda.traversable; +import com.jnape.palatable.lambda.adt.Maybe; + import java.util.Optional; /** * Static factory methods for adapting core JDK types to {@link Traversable}. * - * @deprecated in favor of {@link Lambda} methods + * @deprecated in favor of {@link LambdaIterable} and {@link Maybe}. */ @Deprecated public final class Traversables { @@ -19,7 +21,7 @@ private Traversables() { * @param as the Iterable * @param the Iterable element type * @return a Traversable wrapper around as - * @deprecated in favor of {@link Lambda#lambda(Iterable)} + * @deprecated in favor of {@link LambdaIterable#wrap(Iterable)} */ @Deprecated public static TraversableIterable traversable(Iterable as) { @@ -32,7 +34,7 @@ public static TraversableIterable traversable(Iterable as) { * @param opt the Optional * @param the Optional type * @return a Traversable wrapper around opt - * @deprecated in favor of {@link Lambda#lambda(Optional)} + * @deprecated in favor of {@link Maybe#fromOptional(Optional)} */ @Deprecated public static TraversableOptional traversable(Optional opt) { diff --git a/src/test/java/com/jnape/palatable/lambda/adt/MaybeTest.java b/src/test/java/com/jnape/palatable/lambda/adt/MaybeTest.java new file mode 100644 index 000000000..875664c94 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/adt/MaybeTest.java @@ -0,0 +1,111 @@ +package com.jnape.palatable.lambda.adt; + +import com.jnape.palatable.traitor.annotations.TestTraits; +import com.jnape.palatable.traitor.framework.Subjects; +import com.jnape.palatable.traitor.runners.Traits; +import org.junit.Test; +import org.junit.runner.RunWith; +import testsupport.traits.ApplicativeLaws; +import testsupport.traits.FunctorLaws; +import testsupport.traits.MonadLaws; +import testsupport.traits.TraversableLaws; + +import java.util.Optional; +import java.util.concurrent.atomic.AtomicInteger; + +import static com.jnape.palatable.lambda.adt.Either.left; +import static com.jnape.palatable.lambda.adt.Either.right; +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.adt.Maybe.nothing; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Eq.eq; +import static com.jnape.palatable.traitor.framework.Subjects.subjects; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertSame; + +@RunWith(Traits.class) +public class MaybeTest { + + @TestTraits({FunctorLaws.class, ApplicativeLaws.class, TraversableLaws.class, MonadLaws.class}) + public Subjects> testSubject() { + return subjects(Maybe.nothing(), just(1)); + } + + @Test(expected = NullPointerException.class) + public void justMustBeNonNull() { + just(null); + } + + @Test + public void nothingReusesInstance() { + assertSame(Maybe.nothing(), Maybe.nothing()); + } + + @Test + public void maybeAllowsNull() { + assertEquals(just(1), Maybe.maybe(1)); + assertEquals(Maybe.nothing(), Maybe.maybe(null)); + } + + @Test + public void orElseGet() { + assertEquals((Integer) 1, just(1).orElseGet(() -> -1)); + assertEquals(-1, Maybe.nothing().orElseGet(() -> -1)); + } + + @Test + public void orElse() { + assertEquals((Integer) 1, just(1).orElse(-1)); + assertEquals(-1, Maybe.nothing().orElse(-1)); + } + + @Test + public void filter() { + assertEquals(just(1), just(1).filter(eq(1))); + assertEquals(nothing(), just(0).filter(eq(1))); + assertEquals(nothing(), nothing().filter(eq(1))); + } + + @Test + public void toOptional() { + assertEquals(Optional.of(1), just(1).toOptional()); + assertEquals(Optional.empty(), Maybe.nothing().toOptional()); + } + + @Test + public void fromOptional() { + assertEquals(just(1), Maybe.fromOptional(Optional.of(1))); + assertEquals(Maybe.nothing(), Maybe.fromOptional(Optional.empty())); + } + + @Test + public void toEither() { + assertEquals(right(1), just(1).toEither(() -> "empty")); + assertEquals(left("empty"), nothing().toEither(() -> "empty")); + } + + @Test + public void fromEither() { + assertEquals(just(1), Maybe.fromEither(right(1))); + assertEquals(nothing(), Maybe.fromEither(left("failure"))); + } + + @Test + public void peek() { + AtomicInteger ref = new AtomicInteger(0); + assertEquals(just(1), just(1).peek(__ -> ref.incrementAndGet())); + assertEquals(1, ref.get()); + + assertEquals(nothing(), nothing().peek(__ -> ref.incrementAndGet())); + assertEquals(1, ref.get()); + } + + @Test + public void justOrThrow() { + just(1).orThrow(IllegalStateException::new); + } + + @Test(expected = IllegalStateException.class) + public void nothingOrThrow() { + nothing().orThrow(IllegalStateException::new); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/traversable/LambdaOptionalTest.java b/src/test/java/com/jnape/palatable/lambda/traversable/LambdaOptionalTest.java deleted file mode 100644 index 9cf67c442..000000000 --- a/src/test/java/com/jnape/palatable/lambda/traversable/LambdaOptionalTest.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.jnape.palatable.lambda.traversable; - -import com.jnape.palatable.traitor.annotations.TestTraits; -import com.jnape.palatable.traitor.runners.Traits; -import org.junit.runner.RunWith; -import testsupport.traits.ApplicativeLaws; -import testsupport.traits.FunctorLaws; -import testsupport.traits.MonadLaws; -import testsupport.traits.TraversableLaws; - -import java.util.Optional; - -@RunWith(Traits.class) -public class LambdaOptionalTest { - - @TestTraits({FunctorLaws.class, ApplicativeLaws.class, TraversableLaws.class, MonadLaws.class}) - public LambdaOptional testSubject() { - return LambdaOptional.wrap(Optional.of(new Object())); - } -} \ No newline at end of file From 4ac6ee31f040d0e2c8d28caa792901deb9b25433 Mon Sep 17 00:00:00 2001 From: jnape Date: Sun, 29 Oct 2017 16:11:07 -0500 Subject: [PATCH 23/32] Replacing Optional with Maybe across the board --- CHANGELOG.md | 16 +++ .../jnape/palatable/lambda/adt/Either.java | 32 ++++- .../com/jnape/palatable/lambda/adt/Maybe.java | 8 +- .../lambda/adt/coproduct/CoProduct2.java | 14 +- .../lambda/adt/coproduct/CoProduct3.java | 18 +-- .../lambda/adt/coproduct/CoProduct4.java | 22 +-- .../lambda/adt/coproduct/CoProduct5.java | 26 ++-- .../jnape/palatable/lambda/adt/hmap/HMap.java | 8 +- .../lambda/functions/builtin/fn1/Head.java | 19 +-- .../lambda/functions/builtin/fn1/Last.java | 15 +- .../lambda/functions/builtin/fn1/Uncons.java | 15 +- .../lambda/functions/builtin/fn2/Find.java | 20 +-- .../lambda/functions/builtin/fn2/Iterate.java | 4 +- .../functions/builtin/fn2/Partition.java | 16 +-- .../functions/builtin/fn2/PrependAll.java | 2 +- .../functions/builtin/fn2/ReduceLeft.java | 33 +++-- .../functions/builtin/fn2/ReduceRight.java | 26 ++-- .../functions/builtin/fn2/Sequence.java | 18 ++- .../lambda/functions/builtin/fn2/Unfoldr.java | 20 +-- .../lambda/iterators/UnfoldingIterator.java | 18 +-- .../lambda/lens/lenses/EitherLens.java | 25 ++-- .../lambda/lens/lenses/IterableLens.java | 7 +- .../lambda/lens/lenses/ListLens.java | 13 +- .../palatable/lambda/lens/lenses/MapLens.java | 13 +- .../lambda/lens/lenses/MaybeLens.java | 129 ++++++++++++++++-- .../lambda/lens/lenses/OptionalLens.java | 22 +++ .../lambda/monoid/builtin/First.java | 25 ++-- .../palatable/lambda/monoid/builtin/Last.java | 27 ++-- .../lambda/monoid/builtin/Present.java | 38 +++--- .../palatable/lambda/adt/EitherTest.java | 28 ++-- .../jnape/palatable/lambda/adt/MaybeTest.java | 4 +- .../lambda/adt/coproduct/CoProduct2Test.java | 16 ++- .../lambda/adt/coproduct/CoProduct3Test.java | 16 ++- .../lambda/adt/coproduct/CoProduct4Test.java | 20 +-- .../lambda/adt/coproduct/CoProduct5Test.java | 24 ++-- .../palatable/lambda/adt/hmap/HMapTest.java | 9 +- .../functions/builtin/fn1/HeadTest.java | 8 +- .../functions/builtin/fn1/LastTest.java | 8 +- .../functions/builtin/fn1/UnconsTest.java | 5 +- .../functions/builtin/fn2/FindTest.java | 13 +- .../functions/builtin/fn2/ReduceLeftTest.java | 14 +- .../builtin/fn2/ReduceRightTest.java | 14 +- .../functions/builtin/fn2/SequenceTest.java | 7 - .../functions/builtin/fn2/UnfoldrTest.java | 15 +- .../iterators/ConcatenatingIteratorTest.java | 4 +- .../lambda/iterators/SnocIteratorTest.java | 4 +- .../iterators/UnfoldingIteratorTest.java | 13 +- .../jnape/palatable/lambda/lens/LensTest.java | 17 +-- .../lambda/lens/lenses/EitherLensTest.java | 37 ++--- .../lambda/lens/lenses/IterableLensTest.java | 11 +- .../lambda/lens/lenses/ListLensTest.java | 10 +- .../lambda/lens/lenses/MapLensTest.java | 7 +- .../lambda/lens/lenses/MaybeLensTest.java | 81 +++++++++++ .../lambda/lens/lenses/OptionalLensTest.java | 80 ----------- .../palatable/lambda/monoid/MonoidTest.java | 9 +- .../lambda/monoid/builtin/FirstTest.java | 14 +- .../lambda/monoid/builtin/LastTest.java | 14 +- .../lambda/monoid/builtin/PresentTest.java | 12 +- .../testsupport/traits/ApplicativeLaws.java | 57 ++++---- .../testsupport/traits/BifunctorLaws.java | 36 ++--- .../java/testsupport/traits/FunctorLaws.java | 23 ++-- .../java/testsupport/traits/MonadLaws.java | 26 ++-- .../testsupport/traits/TraversableLaws.java | 40 +++--- 63 files changed, 772 insertions(+), 573 deletions(-) create mode 100644 src/test/java/com/jnape/palatable/lambda/lens/lenses/MaybeLensTest.java delete mode 100644 src/test/java/com/jnape/palatable/lambda/lens/lenses/OptionalLensTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 621415190..955e2d471 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,17 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/). - `CoProductN#embed` no longer eagerly invokes functions ### Added +- `Monad` arrives. The following `Applicative`s are now also `Monad`: + - `Lens` + - `Const` + - `Tuple*` + - `Choice*` + - `Identity` + - `Either` + - `Fn*` + - `LambdaIterable` + - `Maybe` + - `SingletonHList` - `Force`, for forcing iteration of an `Iterable` to perform any side-effects - `Snoc`, for lazily appending an element to the end of an `Iterable` - `Coalesce`, for folding an `Iterable>` into an `Either, Iterable>` @@ -26,9 +37,14 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/). - `Traversables` and all methods therein, in favor of either `LambdaIterable` or `Maybe` - `TraversableOptional` in favor of `Maybe` - `TraversableIterable` in favor of `LambdaIterable` +- `Sequence` overloads supporting `Optional` in favor of converting `Optional` to `Maybe` and then sequencing +- `Either#toOptional` and `Either#fromOptional` in favor of its `Maybe` counterparts ### Changed +- ***Breaking Change***: `java.util.Optional` replaced with `Maybe` across the board - `Profunctor#diMap/L/R` parameters allow variance +- `Either#toOptional` no longer allows `null` values in the right side, and is now in sync with CoProduct#projectB +- `Unfoldr` allows variance on input ## [1.6.3] - 2017-09-27 ### Fixed 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 d020d570e..c00b3da6b 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/Either.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/Either.java @@ -249,9 +249,21 @@ public Applicative, App> traverse( * right value. * * @return an Optional around the right value, or empty if left + * @deprecated in favor of {@link Either#toMaybe()} */ + @Deprecated public Optional toOptional() { - return match(__ -> Optional.empty(), Optional::ofNullable); + return toMaybe().toOptional(); + } + + /** + * In the left case, returns a {@link Maybe#nothing()}; otherwise, returns {@link Maybe#maybe} around the right + * value. + * + * @return Maybe the right value + */ + public Maybe toMaybe() { + return projectB(); } /** @@ -263,9 +275,25 @@ public Optional toOptional() { * @param the left parameter type * @param the right parameter type * @return a right value of the contained optional value, or a left value of leftFn's result + * @deprecated in favor of converting {@link Optional} to {@link Maybe}, then using {@link Either#fromMaybe} */ + @Deprecated public static Either fromOptional(Optional optional, Supplier leftFn) { - return optional.>map(Either::right) + return fromMaybe(Maybe.fromOptional(optional), leftFn); + } + + /** + * Convert a {@link Maybe}<R> into an Either<L, R>, supplying the left value from + * leftFn in the case of {@link Maybe#nothing()}. + * + * @param maybe the maybe + * @param leftFn the supplier to use for left values + * @param the left parameter type + * @param the right parameter type + * @return a right value of the contained maybe value, or a left value of leftFn's result + */ + public static Either fromMaybe(Maybe maybe, Supplier leftFn) { + return maybe.>fmap(Either::right) .orElseGet(() -> left(leftFn.get())); } diff --git a/src/main/java/com/jnape/palatable/lambda/adt/Maybe.java b/src/main/java/com/jnape/palatable/lambda/adt/Maybe.java index cf0600a7c..9a76139c7 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/Maybe.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/Maybe.java @@ -54,7 +54,7 @@ public final A orElse(A other) { * @return the value, if present * @throws E the throwable, if the value is absent */ - public final A orThrow(Supplier throwableSupplier) throws E { + public final A orElseThrow(Supplier throwableSupplier) throws E { return orElseGet((CheckedSupplier) () -> { throw throwableSupplier.get(); }); @@ -243,9 +243,7 @@ public int hashCode() { @Override public String toString() { - return "Just{" + - "a=" + a + - '}'; + return "Just " + a; } } @@ -276,7 +274,7 @@ public A orElseGet(Supplier otherSupplier) { @Override public String toString() { - return "Nothing{}"; + return "Nothing"; } } } 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 index f6377bf70..650c7f006 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct2.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct2.java @@ -1,13 +1,15 @@ package com.jnape.palatable.lambda.adt.coproduct; import com.jnape.palatable.lambda.adt.Either; +import com.jnape.palatable.lambda.adt.Maybe; import com.jnape.palatable.lambda.adt.choice.Choice2; import com.jnape.palatable.lambda.adt.hlist.Tuple2; import com.jnape.palatable.lambda.functions.Fn1; -import java.util.Optional; import java.util.function.Function; +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.adt.Maybe.nothing; import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; import static com.jnape.palatable.lambda.functions.Fn1.fn1; import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; @@ -74,9 +76,9 @@ public R match(Function aFn, Function, Optional> project() { - return match(a -> tuple(Optional.of(a), Optional.empty()), - b -> tuple(Optional.empty(), Optional.of(b))); + default Tuple2, Maybe> project() { + return match(a -> tuple(just(a), nothing()), + b -> tuple(nothing(), just(b))); } /** @@ -84,7 +86,7 @@ default Tuple2, Optional> project() { * * @return an optional value representing the projection of the "a" type index */ - default Optional projectA() { + default Maybe projectA() { return project()._1(); } @@ -93,7 +95,7 @@ default Optional projectA() { * * @return an optional value representing the projection of the "b" type index */ - default Optional projectB() { + default Maybe projectB() { return project()._2(); } 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 index 32f743cbb..d3c27bc70 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct3.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct3.java @@ -1,11 +1,13 @@ package com.jnape.palatable.lambda.adt.coproduct; +import com.jnape.palatable.lambda.adt.Maybe; import com.jnape.palatable.lambda.adt.hlist.Tuple3; import com.jnape.palatable.lambda.functions.Fn1; -import java.util.Optional; import java.util.function.Function; +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.adt.Maybe.nothing; import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; import static com.jnape.palatable.lambda.functions.Fn1.fn1; import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; @@ -85,10 +87,10 @@ public R match(Function aFn, Function, Optional, Optional> project() { - return match(a -> tuple(Optional.of(a), Optional.empty(), Optional.empty()), - b -> tuple(Optional.empty(), Optional.of(b), Optional.empty()), - c -> tuple(Optional.empty(), Optional.empty(), Optional.of(c))); + default Tuple3, Maybe, Maybe> project() { + return match(a -> tuple(just(a), nothing(), nothing()), + b -> tuple(nothing(), just(b), nothing()), + c -> tuple(nothing(), nothing(), just(c))); } /** @@ -96,7 +98,7 @@ default Tuple3, Optional, Optional> project() { * * @return an optional value representing the projection of the "a" type index */ - default Optional projectA() { + default Maybe projectA() { return project()._1(); } @@ -105,7 +107,7 @@ default Optional projectA() { * * @return an optional value representing the projection of the "b" type index */ - default Optional projectB() { + default Maybe projectB() { return project()._2(); } @@ -114,7 +116,7 @@ default Optional projectB() { * * @return an optional value representing the projection of the "c" type index */ - default Optional projectC() { + default Maybe projectC() { return project()._3(); } 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 index a9a6d7d10..4d8373b66 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct4.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct4.java @@ -1,11 +1,13 @@ package com.jnape.palatable.lambda.adt.coproduct; +import com.jnape.palatable.lambda.adt.Maybe; import com.jnape.palatable.lambda.adt.hlist.Tuple4; import com.jnape.palatable.lambda.functions.Fn1; -import java.util.Optional; import java.util.function.Function; +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.adt.Maybe.nothing; import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; import static com.jnape.palatable.lambda.functions.Fn1.fn1; import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; @@ -93,11 +95,11 @@ public R match(Function aFn, Function, Optional, Optional, Optional> project() { - return match(a -> tuple(Optional.of(a), Optional.empty(), Optional.empty(), Optional.empty()), - b -> tuple(Optional.empty(), Optional.of(b), Optional.empty(), Optional.empty()), - c -> tuple(Optional.empty(), Optional.empty(), Optional.of(c), Optional.empty()), - d -> tuple(Optional.empty(), Optional.empty(), Optional.empty(), Optional.of(d))); + default Tuple4, Maybe, Maybe, Maybe> project() { + return match(a -> tuple(just(a), nothing(), nothing(), nothing()), + b -> tuple(nothing(), just(b), nothing(), nothing()), + c -> tuple(nothing(), nothing(), just(c), nothing()), + d -> tuple(nothing(), nothing(), nothing(), just(d))); } /** @@ -105,7 +107,7 @@ default Tuple4, Optional, Optional, Optional> project() { * * @return an optional value representing the projection of the "a" type index */ - default Optional projectA() { + default Maybe projectA() { return project()._1(); } @@ -114,7 +116,7 @@ default Optional projectA() { * * @return an optional value representing the projection of the "b" type index */ - default Optional projectB() { + default Maybe projectB() { return project()._2(); } @@ -123,7 +125,7 @@ default Optional projectB() { * * @return an optional value representing the projection of the "c" type index */ - default Optional projectC() { + default Maybe projectC() { return project()._3(); } @@ -132,7 +134,7 @@ default Optional projectC() { * * @return an optional value representing the projection of the "d" type index */ - default Optional projectD() { + default Maybe projectD() { return project()._4(); } 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 index 783eac16b..cd91efb39 100644 --- a/src/main/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct5.java +++ b/src/main/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct5.java @@ -1,11 +1,13 @@ package com.jnape.palatable.lambda.adt.coproduct; +import com.jnape.palatable.lambda.adt.Maybe; import com.jnape.palatable.lambda.adt.hlist.Tuple5; import com.jnape.palatable.lambda.functions.Fn1; -import java.util.Optional; import java.util.function.Function; +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.adt.Maybe.nothing; import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; import static com.jnape.palatable.lambda.functions.Fn1.fn1; import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; @@ -84,12 +86,12 @@ public R match(Function aFn, Function, Optional, Optional, Optional, Optional> project() { - return match(a -> tuple(Optional.of(a), Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty()), - b -> tuple(Optional.empty(), Optional.of(b), Optional.empty(), Optional.empty(), Optional.empty()), - c -> tuple(Optional.empty(), Optional.empty(), Optional.of(c), Optional.empty(), Optional.empty()), - d -> tuple(Optional.empty(), Optional.empty(), Optional.empty(), Optional.of(d), Optional.empty()), - e -> tuple(Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty(), Optional.of(e))); + default Tuple5, Maybe, Maybe, Maybe, Maybe> project() { + return match(a -> tuple(just(a), nothing(), nothing(), nothing(), nothing()), + b -> tuple(nothing(), just(b), nothing(), nothing(), nothing()), + c -> tuple(nothing(), nothing(), just(c), nothing(), nothing()), + d -> tuple(nothing(), nothing(), nothing(), just(d), nothing()), + e -> tuple(nothing(), nothing(), nothing(), nothing(), just(e))); } /** @@ -97,7 +99,7 @@ default Tuple5, Optional, Optional, Optional, Optional> * * @return an optional value representing the projection of the "a" type index */ - default Optional projectA() { + default Maybe projectA() { return project()._1(); } @@ -106,7 +108,7 @@ default Optional projectA() { * * @return an optional value representing the projection of the "b" type index */ - default Optional projectB() { + default Maybe projectB() { return project()._2(); } @@ -115,7 +117,7 @@ default Optional projectB() { * * @return an optional value representing the projection of the "c" type index */ - default Optional projectC() { + default Maybe projectC() { return project()._3(); } @@ -124,7 +126,7 @@ default Optional projectC() { * * @return an optional value representing the projection of the "d" type index */ - default Optional projectD() { + default Maybe projectD() { return project()._4(); } @@ -133,7 +135,7 @@ default Optional projectD() { * * @return an optional value representing the projection of the "e" type index */ - default Optional projectE() { + default Maybe projectE() { return project()._5(); } 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 1fc5ad770..48d60db79 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 @@ -1,5 +1,6 @@ package com.jnape.palatable.lambda.adt.hmap; +import com.jnape.palatable.lambda.adt.Maybe; import com.jnape.palatable.lambda.adt.hlist.Tuple2; import java.util.HashMap; @@ -7,7 +8,6 @@ import java.util.Map; import java.util.NoSuchElementException; import java.util.Objects; -import java.util.Optional; import java.util.function.Consumer; import static com.jnape.palatable.lambda.functions.builtin.fn2.Map.map; @@ -36,11 +36,11 @@ private HMap(Map table) { * * @param key the key * @param the value type - * @return the value at this key wrapped in an {@link Optional}, or {@link Optional#empty}. + * @return Maybe the value at this key */ @SuppressWarnings("unchecked") - public Optional get(TypeSafeKey key) { - return Optional.ofNullable((T) table.get(key)); + public Maybe get(TypeSafeKey key) { + return Maybe.maybe((T) table.get(key)); } /** 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 index 2ce501dee..e8abf632b 100644 --- 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 @@ -1,17 +1,20 @@ package com.jnape.palatable.lambda.functions.builtin.fn1; +import com.jnape.palatable.lambda.adt.Maybe; import com.jnape.palatable.lambda.functions.Fn1; import java.util.Iterator; -import java.util.Optional; + +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.adt.Maybe.nothing; /** - * Retrieve the head element of an Iterable, wrapped in an Optional. If the - * Iterable is empty, the result is Optional.empty(). + * Retrieve the head element of an {@link Iterable}, wrapped in an {@link Maybe}. If the {@link Iterable} is empty, the + * result is {@link Maybe#nothing()}. * * @param the Iterable element type */ -public final class Head implements Fn1, Optional> { +public final class Head implements Fn1, Maybe> { private static final Head INSTANCE = new Head(); @@ -19,11 +22,9 @@ private Head() { } @Override - public Optional apply(Iterable as) { + public Maybe apply(Iterable as) { Iterator iterator = as.iterator(); - return iterator.hasNext() - ? Optional.of(iterator.next()) - : Optional.empty(); + return iterator.hasNext() ? just(iterator.next()) : nothing(); } @SuppressWarnings("unchecked") @@ -31,7 +32,7 @@ public static Head head() { return INSTANCE; } - public static Optional head(Iterable as) { + public static Maybe head(Iterable as) { return Head.head().apply(as); } } diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Last.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Last.java index 4b993c35c..4710f7900 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Last.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Last.java @@ -1,16 +1,17 @@ package com.jnape.palatable.lambda.functions.builtin.fn1; +import com.jnape.palatable.lambda.adt.Maybe; import com.jnape.palatable.lambda.functions.Fn1; -import java.util.Optional; +import static com.jnape.palatable.lambda.adt.Maybe.maybe; /** - * Retrieve the last element of an Iterable, wrapped in an Optional. If the - * Iterable is empty, the result is Optional.empty(). + * Retrieve the last element of an {@link Iterable}, wrapped in a {@link Maybe}. If the {@link Iterable} is empty, the + * result is {@link Maybe#nothing()}. * * @param the Iterable element type */ -public final class Last implements Fn1, Optional> { +public final class Last implements Fn1, Maybe> { private static final Last INSTANCE = new Last(); @@ -18,12 +19,12 @@ private Last() { } @Override - public Optional apply(Iterable as) { + public Maybe apply(Iterable as) { A last = null; for (A a : as) { last = a; } - return Optional.ofNullable(last); + return maybe(last); } @SuppressWarnings("unchecked") @@ -31,7 +32,7 @@ public static Last last() { return INSTANCE; } - public static Optional last(Iterable as) { + public static Maybe last(Iterable as) { return Last.last().apply(as); } } diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Uncons.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Uncons.java index 2f5fc5365..7c6c807bf 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Uncons.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Uncons.java @@ -1,21 +1,20 @@ package com.jnape.palatable.lambda.functions.builtin.fn1; +import com.jnape.palatable.lambda.adt.Maybe; import com.jnape.palatable.lambda.adt.hlist.Tuple2; import com.jnape.palatable.lambda.functions.Fn1; -import java.util.Optional; - import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; import static com.jnape.palatable.lambda.functions.builtin.fn1.Head.head; import static com.jnape.palatable.lambda.functions.builtin.fn1.Tail.tail; /** - * Destructure an {@link Iterable} into a {@link Tuple2} of its head and tail, wrapped in an {@link Optional}. If the - * {@link Iterable} is empty, returns Optional.empty(). + * Destructure an {@link Iterable} into a {@link Tuple2} of its head and tail, wrapped in an {@link Maybe}. If the + * {@link Iterable} is empty, returns {@link Maybe#nothing()}. * * @param the Iterable element type */ -public final class Uncons implements Fn1, Optional>>> { +public final class Uncons implements Fn1, Maybe>>> { private static final Uncons INSTANCE = new Uncons(); @@ -23,8 +22,8 @@ private Uncons() { } @Override - public Optional>> apply(Iterable as) { - return head(as).map(a -> tuple(a, tail(as))); + public Maybe>> apply(Iterable as) { + return head(as).fmap(a -> tuple(a, tail(as))); } @SuppressWarnings("unchecked") @@ -32,7 +31,7 @@ public static Uncons uncons() { return INSTANCE; } - public static Optional>> uncons(Iterable as) { + public static Maybe>> uncons(Iterable as) { return Uncons.uncons().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 index 583f29d20..e1bb57be7 100644 --- 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 @@ -1,24 +1,24 @@ package com.jnape.palatable.lambda.functions.builtin.fn2; +import com.jnape.palatable.lambda.adt.Maybe; 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.fn1.Not.not; 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. + * matches the predicate, wrapped in a {@link Maybe}. If no elements match the predicate, the result is + * {@link Maybe#nothing()}. This function short-circuits, and so is safe to use on potentially infinite {@link Iterable} + * instances that guarantee to have an eventually matching element. * * @param the Iterable element type */ -public final class Find implements Fn2, Iterable, Optional> { +public final class Find implements Fn2, Iterable, Maybe> { private static final Find INSTANCE = new Find(); @@ -26,8 +26,8 @@ private Find() { } @Override - public Optional apply(Function predicate, Iterable as) { - return head(dropWhile(((Predicate) predicate::apply).negate(), as)); + public Maybe apply(Function predicate, Iterable as) { + return head(dropWhile(not(predicate), as)); } @SuppressWarnings("unchecked") @@ -35,11 +35,11 @@ public static Find find() { return INSTANCE; } - public static Fn1, Optional> find(Function predicate) { + public static Fn1, Maybe> find(Function predicate) { return Find.find().apply(predicate); } - public static Optional find(Function predicate, Iterable as) { + public static Maybe find(Function predicate, Iterable as) { return Find.find(predicate).apply(as); } } diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Iterate.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Iterate.java index 18c299abe..d948bb12f 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Iterate.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Iterate.java @@ -3,9 +3,9 @@ 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.Maybe.just; import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; import static com.jnape.palatable.lambda.functions.builtin.fn2.Unfoldr.unfoldr; @@ -25,7 +25,7 @@ private Iterate() { @Override public Iterable apply(Function fn, A seed) { - return unfoldr(a -> Optional.of(tuple(a, fn.apply(a))), seed); + return unfoldr(a -> just(tuple(a, fn.apply(a))), seed); } @SuppressWarnings("unchecked") 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 index 647bc9c33..5a9a17148 100644 --- 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 @@ -5,12 +5,12 @@ import com.jnape.palatable.lambda.functions.Fn1; import com.jnape.palatable.lambda.functions.Fn2; -import java.util.Optional; +import java.util.Collections; import java.util.function.Function; -import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; -import static com.jnape.palatable.lambda.functions.builtin.fn2.Filter.filter; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Flatten.flatten; import static com.jnape.palatable.lambda.functions.builtin.fn2.Map.map; +import static java.util.Collections.emptySet; /** * Given an Iterable<A> as and a disjoint mapping function a -> @@ -34,12 +34,10 @@ private Partition() { @Override public Tuple2, Iterable> apply(Function> function, Iterable as) { - Iterable> coproducts = map(function, as); - - Iterable lefts = map(Optional::get, filter(Optional::isPresent, map(CoProduct2::projectA, coproducts))); - Iterable rights = map(Optional::get, filter(Optional::isPresent, map(CoProduct2::projectB, coproducts))); - - return tuple(lefts, rights); + return Tuple2.>>fill(map(function, as)) + .biMap(Map., Iterable>map(cp -> cp.match(Collections::singleton, __ -> emptySet())), + Map., Iterable>map(cp -> cp.match(__ -> emptySet(), Collections::singleton))) + .biMap(flatten(), flatten()); } @SuppressWarnings("unchecked") diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/PrependAll.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/PrependAll.java index dd2dbcc81..dea00af19 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/PrependAll.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/PrependAll.java @@ -24,7 +24,7 @@ private PrependAll() { @Override public Iterable apply(A a, Iterable as) { - return () -> head(as).map(head -> cons(a, cons(head, prependAll(a, tail(as))))).orElse(emptyList()).iterator(); + return () -> head(as).fmap(head -> cons(a, cons(head, prependAll(a, tail(as))))).orElse(emptyList()).iterator(); } @SuppressWarnings("unchecked") diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/ReduceLeft.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/ReduceLeft.java index d333ba2e9..c25b988a4 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/ReduceLeft.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/ReduceLeft.java @@ -1,29 +1,31 @@ package com.jnape.palatable.lambda.functions.builtin.fn2; +import com.jnape.palatable.lambda.adt.Maybe; import com.jnape.palatable.lambda.functions.Fn1; import com.jnape.palatable.lambda.functions.Fn2; +import com.jnape.palatable.lambda.functions.builtin.fn3.FoldLeft; import java.util.Iterator; -import java.util.Optional; import java.util.function.BiFunction; +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.adt.Maybe.nothing; import static com.jnape.palatable.lambda.functions.builtin.fn3.FoldLeft.foldLeft; /** - * Given an Iterable of As and a {@link BiFunction}<A, A, A>, iteratively - * accumulate over the Iterable, returning an Optional<A> (if the Iterable - * is empty, the result is Optional.empty(); otherwise, the result is wrapped in - * Optional.of(). For this reason, null accumulation results are considered erroneous and will - * throw. + * Given an {@link Iterable}<A> and a {@link BiFunction}<A, A, A>, iteratively + * accumulate over the {@link Iterable}, returning {@link Maybe}<A>. If the {@link Iterable} is + * empty, the result is {@link Maybe#nothing()}; otherwise, the result is wrapped in {@link Maybe#just}. For this + * reason, null accumulation results are considered erroneous and will throw. *

- * This function is isomorphic to a left fold over the Iterable where the head element is the starting - * accumulation value and the result is lifted into an Optional. + * This function is isomorphic to a left fold over the {@link Iterable} where the head element is the starting + * accumulation value and the result is lifted into {@link Maybe}. * * @param The input Iterable element type, as well as the accumulation type * @see ReduceRight - * @see com.jnape.palatable.lambda.functions.builtin.fn3.FoldLeft + * @see FoldLeft */ -public final class ReduceLeft implements Fn2, Iterable, Optional> { +public final class ReduceLeft implements Fn2, Iterable, Maybe> { private static final ReduceLeft INSTANCE = new ReduceLeft(); @@ -31,12 +33,9 @@ private ReduceLeft() { } @Override - public Optional apply(BiFunction fn, Iterable as) { + public Maybe apply(BiFunction fn, Iterable as) { Iterator iterator = as.iterator(); - if (!iterator.hasNext()) - return Optional.empty(); - - return Optional.of(foldLeft(fn, iterator.next(), () -> iterator)); + return !iterator.hasNext() ? nothing() : just(foldLeft(fn, iterator.next(), () -> iterator)); } @SuppressWarnings("unchecked") @@ -44,11 +43,11 @@ public static ReduceLeft reduceLeft() { return INSTANCE; } - public static Fn1, Optional> reduceLeft(BiFunction fn) { + public static Fn1, Maybe> reduceLeft(BiFunction fn) { return ReduceLeft.reduceLeft().apply(fn); } - public static Optional reduceLeft(BiFunction fn, Iterable as) { + public static Maybe reduceLeft(BiFunction fn, Iterable as) { return ReduceLeft.reduceLeft(fn).apply(as); } } diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/ReduceRight.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/ReduceRight.java index 4e2181024..432ac0f56 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/ReduceRight.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/ReduceRight.java @@ -1,29 +1,29 @@ package com.jnape.palatable.lambda.functions.builtin.fn2; +import com.jnape.palatable.lambda.adt.Maybe; import com.jnape.palatable.lambda.functions.Fn1; import com.jnape.palatable.lambda.functions.Fn2; +import com.jnape.palatable.lambda.functions.builtin.fn3.FoldRight; -import java.util.Optional; import java.util.function.BiFunction; import static com.jnape.palatable.lambda.functions.builtin.fn1.Reverse.reverse; import static com.jnape.palatable.lambda.functions.builtin.fn2.ReduceLeft.reduceLeft; /** - * Given an Iterable of As and a {@link BiFunction}<A, A, A>, iteratively - * accumulate over the Iterable, returning an Optional<A> (if the Iterable - * is empty, the result is Optional.empty(); otherwise, the result is wrapped in - * Optional.of(). For this reason, null accumulation results are considered erroneous and will - * throw. + * Given an {@link Iterable}<A> and a {@link BiFunction}<A, A, A>, iteratively + * accumulate over the {@link Iterable}, returning {@link Maybe}<A>. If the {@link Iterable} is + * empty, the result is {@link Maybe#nothing()}; otherwise, the result is wrapped in {@link Maybe#just}. For this + * reason, null accumulation results are considered erroneous and will throw. *

- * This function is isomorphic to a right fold over the Iterable where the tail element is the starting - * accumulation value and the result is lifted into an Optional. + * This function is isomorphic to a right fold over the {@link Iterable} where the tail element is the starting + * accumulation value and the result is lifted into {@link Maybe}. * * @param The input Iterable element type, as well as the accumulation type * @see ReduceLeft - * @see com.jnape.palatable.lambda.functions.builtin.fn3.FoldRight + * @see FoldRight */ -public final class ReduceRight implements Fn2, Iterable, Optional> { +public final class ReduceRight implements Fn2, Iterable, Maybe> { private static final ReduceRight INSTANCE = new ReduceRight(); @@ -31,7 +31,7 @@ private ReduceRight() { } @Override - public final Optional apply(BiFunction fn, Iterable as) { + public final Maybe apply(BiFunction fn, Iterable as) { return reduceLeft((b, a) -> fn.apply(a, b), reverse(as)); } @@ -40,11 +40,11 @@ public static ReduceRight reduceRight() { return INSTANCE; } - public static Fn1, Optional> reduceRight(BiFunction fn) { + public static Fn1, Maybe> reduceRight(BiFunction fn) { return ReduceRight.reduceRight().apply(fn); } - public static Optional reduceRight(BiFunction fn, Iterable as) { + public static Maybe reduceRight(BiFunction fn, Iterable as) { return ReduceRight.reduceRight(fn).apply(as); } } diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Sequence.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Sequence.java index 6fb76ee63..9c9746928 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Sequence.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Sequence.java @@ -14,10 +14,8 @@ /** * Given a {@link Traversable} of {@link Applicative}s and a pure {@link Applicative} constructor, traverse the - * elements - * from left to right, zipping the applicatives together and collecting the results. If the traversable is empty, - * simply - * wrap it in the applicative by calling pure. + * elements from left to right, zipping the {@link Applicative}s together and collecting the results. If the + * {@link Traversable} is empty, simply wrap it in the {@link Applicative} by calling pure. *

* Modulo any type-level coercion, this is equivalent to traversable.traverse(id(), pure). *

@@ -67,14 +65,18 @@ TravApp extends Traversable, Trav>> AppTrav sequen @SuppressWarnings("unchecked") public static , App>, IterableApp extends Iterable>> Fn1, ? extends AppIterable>, AppIterable> sequence( - IterableApp optionalApp) { + IterableApp iterableApp) { return pure -> - (AppIterable) sequence(LambdaIterable.wrap(optionalApp), x -> pure.apply(((LambdaIterable) x).unwrap()) + (AppIterable) sequence(LambdaIterable.wrap(iterableApp), x -> pure.apply(((LambdaIterable) x).unwrap()) .fmap(LambdaIterable::wrap)) .fmap(LambdaIterable::unwrap); } + /** + * @deprecated in favor of wrapping the {@link Optional} in {@link Maybe}, then sequencing + */ @SuppressWarnings("unchecked") + @Deprecated public static , App>, OptionalApp extends Optional>> Fn1, ? extends AppOptional>, AppOptional> sequence( OptionalApp optionalApp) { return pure -> (AppOptional) sequence(Maybe.fromOptional(optionalApp), x -> pure.apply(((Maybe) x).toOptional()) @@ -88,6 +90,10 @@ IterableApp extends Iterable>> AppIterable sequenc return Sequence.sequence(iterableApp).apply(pure); } + /** + * @deprecated in favor of wrapping the {@link Optional} in {@link Maybe}, then sequencing + */ + @Deprecated public static , App>, OptionalApp extends Optional>> AppOptional sequence(OptionalApp optionalApp, Function, ? extends AppOptional> pure) { diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Unfoldr.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Unfoldr.java index 196d743a6..cb0a277b6 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Unfoldr.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Unfoldr.java @@ -1,19 +1,19 @@ package com.jnape.palatable.lambda.functions.builtin.fn2; +import com.jnape.palatable.lambda.adt.Maybe; import com.jnape.palatable.lambda.adt.hlist.Tuple2; import com.jnape.palatable.lambda.functions.Fn1; import com.jnape.palatable.lambda.functions.Fn2; import com.jnape.palatable.lambda.iterators.UnfoldingIterator; -import java.util.Optional; import java.util.function.Function; /** - * Given an initial seed value and a function that takes the seed type and produces an Optional<{@link + * Given an initial seed value and a function that takes the seed type and produces an {@link Maybe}<{@link * Tuple2}<X, Seed>>, where the tuple's first slot represents the next Iterable element, * and the second slot represents the next input to the unfolding function, unfold an Iterable of - * Xs. Returning Optional.empty() from the unfolding function is a signal that the - * Iterable is fully unfolded. + * Xs. Returning {@link Maybe#nothing()} from the unfolding function is a signal that the {@link Iterable} + * is fully unfolded. *

* For more information, read about Anamorphisms. *

@@ -21,15 +21,15 @@ *

  * {@code
  * Iterable zeroThroughTenInclusive = unfoldr(x -> x <= 10
- *         ? Optional.of(tuple(x, x + 1))
- *         : Optional.empty(), 0);
+ *         ? Maybe.just(tuple(x, x + 1))
+ *         : Maybe.nothing(), 0);
  * }
  * 
* * @param The output Iterable element type * @param The unfolding function input type */ -public final class Unfoldr implements Fn2>>, B, Iterable> { +public final class Unfoldr implements Fn2>>, B, Iterable> { private static final Unfoldr INSTANCE = new Unfoldr(); @@ -37,7 +37,7 @@ private Unfoldr() { } @Override - public Iterable apply(Function>> fn, B b) { + public Iterable apply(Function>> fn, B b) { return () -> new UnfoldingIterator<>(fn, b); } @@ -46,11 +46,11 @@ public static Unfoldr unfoldr() { return INSTANCE; } - public static Fn1> unfoldr(Function>> fn) { + public static Fn1> unfoldr(Function>> fn) { return Unfoldr.unfoldr().apply(fn); } - public static Iterable unfoldr(Function>> fn, B b) { + public static Iterable unfoldr(Function>> fn, B b) { return unfoldr(fn).apply(b); } } diff --git a/src/main/java/com/jnape/palatable/lambda/iterators/UnfoldingIterator.java b/src/main/java/com/jnape/palatable/lambda/iterators/UnfoldingIterator.java index bc99a2cf3..a5fcfcc42 100644 --- a/src/main/java/com/jnape/palatable/lambda/iterators/UnfoldingIterator.java +++ b/src/main/java/com/jnape/palatable/lambda/iterators/UnfoldingIterator.java @@ -1,23 +1,25 @@ package com.jnape.palatable.lambda.iterators; +import com.jnape.palatable.lambda.adt.Maybe; import com.jnape.palatable.lambda.adt.hlist.Tuple2; import java.util.NoSuchElementException; -import java.util.Optional; import java.util.function.Function; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; + public class UnfoldingIterator extends ImmutableIterator { - private final Function>> function; - private Optional> optionalAcc; + private final Function>> function; + private Maybe> maybeAcc; - public UnfoldingIterator(Function>> function, B b) { + public UnfoldingIterator(Function>> function, B b) { this.function = function; - optionalAcc = function.apply(b); + maybeAcc = function.apply(b); } @Override public boolean hasNext() { - return optionalAcc.isPresent(); + return maybeAcc.fmap(constantly(true)).orElse(false); } @Override @@ -26,9 +28,9 @@ public A next() { if (!hasNext()) throw new NoSuchElementException(); - Tuple2 acc = optionalAcc.get(); + Tuple2 acc = maybeAcc.orElseThrow(NoSuchElementException::new); A next = acc._1(); - optionalAcc = function.apply(acc._2()); + maybeAcc = function.apply(acc._2()); return next; } } diff --git a/src/main/java/com/jnape/palatable/lambda/lens/lenses/EitherLens.java b/src/main/java/com/jnape/palatable/lambda/lens/lenses/EitherLens.java index 8652259ec..4a05fb543 100644 --- a/src/main/java/com/jnape/palatable/lambda/lens/lenses/EitherLens.java +++ b/src/main/java/com/jnape/palatable/lambda/lens/lenses/EitherLens.java @@ -1,10 +1,10 @@ package com.jnape.palatable.lambda.lens.lenses; import com.jnape.palatable.lambda.adt.Either; +import com.jnape.palatable.lambda.adt.Maybe; +import com.jnape.palatable.lambda.adt.coproduct.CoProduct2; import com.jnape.palatable.lambda.lens.Lens; -import java.util.Optional; - import static com.jnape.palatable.lambda.lens.Lens.simpleLens; /** @@ -16,29 +16,28 @@ private EitherLens() { } /** - * Convenience static factory method for creating a lens over right values, wrapping them in an {@link Optional}. - * When setting, an empty Optional value means to leave the either unaltered, where as a present Optional value - * replaces the either with a right over the wrapped Optional value. + * Convenience static factory method for creating a lens over right values, wrapping them in a {@link Maybe}. When + * setting, a {@link Maybe#nothing()} value means to leave the {@link Either} unaltered, where as a + * {@link Maybe#just} value replaces the either with a right over the {@link Maybe}. * * @param the left parameter type * @param the right parameter type * @return a lens that focuses on right values */ - public static Lens.Simple, Optional> right() { - return simpleLens(Either::toOptional, (lOrR, optR) -> optR.>map(Either::right).orElse(lOrR)); + public static Lens.Simple, Maybe> right() { + return simpleLens(CoProduct2::projectB, (lOrR, maybeR) -> maybeR.>fmap(Either::right).orElse(lOrR)); } /** - * Convenience static factory method for creating a lens over left values, wrapping them in an {@link Optional}. - * When setting, an empty Optional value means to leave the either unaltered, where as a present Optional value - * replaces the either with a left over the wrapped Optional value. + * Convenience static factory method for creating a lens over left values, wrapping them in a {@link Maybe}. When + * setting, a {@link Maybe#nothing()} value means to leave the {@link Either} unaltered, where as a + * {@link Maybe#just} value replaces the either with a left over the {@link Maybe}. * * @param the left parameter type * @param the right parameter type * @return a lens that focuses on left values */ - public static Lens.Simple, Optional> left() { - return simpleLens(e -> e.match(Optional::ofNullable, __ -> Optional.empty()), - (lOrR, optL) -> optL.>map(Either::left).orElse(lOrR)); + public static Lens.Simple, Maybe> left() { + return simpleLens(CoProduct2::projectA, (lOrR, maybeL) -> maybeL.>fmap(Either::left).orElse(lOrR)); } } diff --git a/src/main/java/com/jnape/palatable/lambda/lens/lenses/IterableLens.java b/src/main/java/com/jnape/palatable/lambda/lens/lenses/IterableLens.java index 488822c9e..26e6c2ffb 100644 --- a/src/main/java/com/jnape/palatable/lambda/lens/lenses/IterableLens.java +++ b/src/main/java/com/jnape/palatable/lambda/lens/lenses/IterableLens.java @@ -1,12 +1,11 @@ package com.jnape.palatable.lambda.lens.lenses; +import com.jnape.palatable.lambda.adt.Maybe; import com.jnape.palatable.lambda.functions.builtin.fn1.Head; import com.jnape.palatable.lambda.functions.builtin.fn1.Tail; import com.jnape.palatable.lambda.functions.builtin.fn2.Cons; import com.jnape.palatable.lambda.lens.Lens; -import java.util.Optional; - import static com.jnape.palatable.lambda.functions.Fn2.fn2; import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; import static com.jnape.palatable.lambda.functions.builtin.fn2.Cons.cons; @@ -27,7 +26,7 @@ private IterableLens() { * @param the Iterable element type * @return a lens focusing on the head element of an {@link Iterable} */ - public static Lens, Iterable, Optional, A> head() { + public static Lens, Iterable, Maybe, A> head() { return lens(Head::head, Cons.cons().flip().compose(Tail.tail()).toBiFunction()); } @@ -38,6 +37,6 @@ public static Lens, Iterable, Optional, A> head() { * @return a lens focusing on the tail of an {@link Iterable} */ public static Lens.Simple, Iterable> tail() { - return simpleLens(Tail::tail, fn2(Head.head().andThen(o -> o.map(cons()).orElse(id()))).toBiFunction()); + return simpleLens(Tail::tail, fn2(Head.head().andThen(o -> o.fmap(cons()).orElse(id()))).toBiFunction()); } } diff --git a/src/main/java/com/jnape/palatable/lambda/lens/lenses/ListLens.java b/src/main/java/com/jnape/palatable/lambda/lens/lenses/ListLens.java index 31f20dab1..c0f9939e9 100644 --- a/src/main/java/com/jnape/palatable/lambda/lens/lenses/ListLens.java +++ b/src/main/java/com/jnape/palatable/lambda/lens/lenses/ListLens.java @@ -1,14 +1,15 @@ package com.jnape.palatable.lambda.lens.lenses; +import com.jnape.palatable.lambda.adt.Maybe; import com.jnape.palatable.lambda.lens.Lens; import java.util.ArrayList; import java.util.List; -import java.util.Optional; +import static com.jnape.palatable.lambda.adt.Maybe.maybe; import static com.jnape.palatable.lambda.lens.Lens.lens; import static com.jnape.palatable.lambda.lens.Lens.simpleLens; -import static com.jnape.palatable.lambda.lens.lenses.OptionalLens.unLiftA; +import static com.jnape.palatable.lambda.lens.lenses.MaybeLens.unLiftA; /** * Lenses that operate on {@link List}s. @@ -31,14 +32,14 @@ public static Lens.Simple, List> asCopy() { /** * Convenience static factory method for creating a lens that focuses on an element in a list at a particular index. - * Wraps result in an Optional to handle null values or indexes that fall outside of list boundaries. + * Wraps result in a {@link Maybe} to handle null values or indexes that fall outside of list boundaries. * * @param index the index to focus on * @param the list element type - * @return an Optional wrapping the element at the index + * @return Maybe the element at the index */ - public static Lens, List, Optional, X> elementAt(int index) { - return lens(xs -> Optional.ofNullable(xs.size() > index ? xs.get(index) : null), + public static Lens, List, Maybe, X> elementAt(int index) { + return lens(xs -> maybe(xs.size() > index ? xs.get(index) : null), (xs, x) -> { if (xs.size() > index) xs.set(index, x); diff --git a/src/main/java/com/jnape/palatable/lambda/lens/lenses/MapLens.java b/src/main/java/com/jnape/palatable/lambda/lens/lenses/MapLens.java index f14f0ec40..332b804b5 100644 --- a/src/main/java/com/jnape/palatable/lambda/lens/lenses/MapLens.java +++ b/src/main/java/com/jnape/palatable/lambda/lens/lenses/MapLens.java @@ -1,5 +1,6 @@ package com.jnape.palatable.lambda.lens.lenses; +import com.jnape.palatable.lambda.adt.Maybe; import com.jnape.palatable.lambda.functions.builtin.fn2.Filter; import com.jnape.palatable.lambda.lens.Lens; @@ -7,17 +8,17 @@ import java.util.HashMap; import java.util.HashSet; import java.util.Map; -import java.util.Optional; import java.util.Set; import java.util.function.Function; +import static com.jnape.palatable.lambda.adt.Maybe.maybe; import static com.jnape.palatable.lambda.functions.builtin.fn2.Eq.eq; import static com.jnape.palatable.lambda.functions.builtin.fn2.Map.map; import static com.jnape.palatable.lambda.functions.builtin.fn2.ToCollection.toCollection; import static com.jnape.palatable.lambda.lens.Lens.lens; import static com.jnape.palatable.lambda.lens.Lens.simpleLens; import static com.jnape.palatable.lambda.lens.functions.View.view; -import static com.jnape.palatable.lambda.lens.lenses.OptionalLens.unLiftA; +import static com.jnape.palatable.lambda.lens.lenses.MaybeLens.unLiftA; import static java.util.stream.Collectors.toMap; import static java.util.stream.Collectors.toSet; @@ -41,15 +42,15 @@ public static Lens.Simple, Map> asCopy() { } /** - * A lens that focuses on a value at a key in a map, as an {@link Optional}. + * A lens that focuses on a value at a key in a map, as a {@link Maybe}. * * @param k the key to focus on * @param the key type * @param the value type - * @return a lens that focuses on the value at key, as an {@link Optional} + * @return a lens that focuses on the value at key, as a {@link Maybe} */ - public static Lens, Map, Optional, V> valueAt(K k) { - return lens(m -> Optional.ofNullable(m.get(k)), (m, v) -> { + public static Lens, Map, Maybe, V> valueAt(K k) { + return lens(m -> maybe(m.get(k)), (m, v) -> { m.put(k, v); return m; }); diff --git a/src/main/java/com/jnape/palatable/lambda/lens/lenses/MaybeLens.java b/src/main/java/com/jnape/palatable/lambda/lens/lenses/MaybeLens.java index 0faedb561..30888d815 100644 --- a/src/main/java/com/jnape/palatable/lambda/lens/lenses/MaybeLens.java +++ b/src/main/java/com/jnape/palatable/lambda/lens/lenses/MaybeLens.java @@ -4,36 +4,141 @@ import com.jnape.palatable.lambda.lens.Lens; import static com.jnape.palatable.lambda.lens.Lens.simpleLens; -import static com.jnape.palatable.lambda.lens.functions.Set.set; -import static com.jnape.palatable.lambda.lens.functions.View.view; /** - * Lenses for {@link Maybe}. + * Lenses that operate on {@link Maybe}. */ public final class MaybeLens { + private MaybeLens() { } - public static Lens, T, A, B> liftS(Lens lens, S defaultValue) { - return lens.mapS(m -> m.orElse(defaultValue)); + /** + * Given a lens and a default S, lift S into {@link Maybe}. + * + * @param lens the lens + * @param defaultS the S to use if {@link Maybe#nothing()} is given + * @param the type of the "larger" value for reading + * @param the type of the "larger" value for putting + * @param the type of the "smaller" value that is read + * @param the type of the "smaller" update value + * @return the lens with S lifted + */ + public static Lens, T, A, B> liftS(Lens lens, S defaultS) { + return lens.mapS(m -> m.orElse(defaultS)); } + /** + * Given a lens, lift T into {@link Maybe}. + * + * @param lens the lens + * @param the type of the "larger" value for reading + * @param the type of the "larger" value for putting + * @param the type of the "smaller" value that is read + * @param the type of the "smaller" update value + * @return the lens with T lifted + */ public static Lens, A, B> liftT(Lens lens) { return lens.mapT(Maybe::just); } + /** + * Given a lens, lift A into {@link Maybe}. + * + * @param lens the lens + * @param the type of the "larger" value for reading + * @param the type of the "larger" value for putting + * @param the type of the "smaller" value that is read + * @param the type of the "smaller" update value + * @return the lens with A lifted + */ public static Lens, B> liftA(Lens lens) { return lens.mapA(Maybe::just); } - public static Lens> liftB(Lens lens, B defaultValue) { - return lens.mapB(m -> m.orElse(defaultValue)); + /** + * Given a lens and a default B, lift B into {@link Maybe}. + * + * @param lens the lens + * @param defaultB the B to use if {@link Maybe#nothing()} is given + * @param the type of the "larger" value for reading + * @param the type of the "larger" value for putting + * @param the type of the "smaller" value that is read + * @param the type of the "smaller" update value + * @return the lens with B lifted + */ + public static Lens> liftB(Lens lens, B defaultB) { + return lens.mapB(m -> m.orElse(defaultB)); + } + + /** + * Given a lens with S lifted into {@link Maybe}, flatten S back down. + * + * @param lens the lens + * @param the type of the "larger" value for reading + * @param the type of the "larger" value for putting + * @param the type of the "smaller" value that is read + * @param the type of the "smaller" update value + * @return the lens with S flattened + */ + public static Lens unLiftS(Lens, T, A, B> lens) { + return lens.mapS(Maybe::just); + } + + /** + * Given a lens with T lifted into {@link Maybe} and a default T, flatten T + * back down. + * + * @param lens the lens + * @param defaultT the T to use if lens produces {@link Maybe#nothing()} + * @param the type of the "larger" value for reading + * @param the type of the "larger" value for putting + * @param the type of the "smaller" value that is read + * @param the type of the "smaller" update value + * @return the lens with T flattened + */ + public static Lens unLiftT(Lens, A, B> lens, T defaultT) { + return lens.mapT(m -> m.orElse(defaultT)); + } + + /** + * Given a lens with A lifted into {@link Maybe} and a default A, flatten A + * back + * down. + * + * @param lens the lens + * @param defaultA the A to use if lens produces {@link Maybe#nothing()} + * @param the type of the "larger" value for reading + * @param the type of the "larger" value for putting + * @param the type of the "smaller" value that is read + * @param the type of the "smaller" update value + * @return the lens with A flattened + */ + public static Lens unLiftA(Lens, B> lens, A defaultA) { + return lens.mapA(m -> m.orElse(defaultA)); + } + + /** + * Given a lens with B lifted into {@link Maybe}, flatten B back down. + * + * @param lens the lens + * @param the type of the "larger" value for reading + * @param the type of the "larger" value for putting + * @param the type of the "smaller" value that is read + * @param the type of the "smaller" update value + * @return the lens with B flattened + */ + public static Lens unLiftB(Lens> lens) { + return lens.mapB(Maybe::just); } - public static Lens.Simple, Maybe> asMaybe(Lens lens) { - return simpleLens(m -> m.fmap(view(lens)), - (maybeS, maybeB) -> maybeS.flatMap(s -> maybeB.fmap(a -> { - return set(lens, a, s); - }))); + /** + * Convenience static factory method for creating a lens that focuses on a value as a {@link Maybe}. + * + * @param the value type + * @return a lens that focuses on the value as a {@link Maybe} + */ + public static Lens.Simple> asMaybe() { + return simpleLens(Maybe::maybe, (v, maybeV) -> maybeV.orElse(v)); } } diff --git a/src/main/java/com/jnape/palatable/lambda/lens/lenses/OptionalLens.java b/src/main/java/com/jnape/palatable/lambda/lens/lenses/OptionalLens.java index ec8eba0e6..79baafcb8 100644 --- a/src/main/java/com/jnape/palatable/lambda/lens/lenses/OptionalLens.java +++ b/src/main/java/com/jnape/palatable/lambda/lens/lenses/OptionalLens.java @@ -1,5 +1,6 @@ package com.jnape.palatable.lambda.lens.lenses; +import com.jnape.palatable.lambda.adt.Maybe; import com.jnape.palatable.lambda.lens.Lens; import java.util.Optional; @@ -8,7 +9,10 @@ /** * Lenses that operate on {@link Optional}s. + * + * @deprecated in favor of converting {@link Optional} to {@link Maybe} and using {@link MaybeLens} analogs */ +@Deprecated public final class OptionalLens { private OptionalLens() { @@ -19,7 +23,9 @@ private OptionalLens() { * * @param the value type * @return a lens that focuses on the value as an Optional + * @deprecated in favor of {@link MaybeLens#asMaybe} */ + @Deprecated public static Lens.Simple> asOptional() { return simpleLens(Optional::ofNullable, (v, optV) -> optV.orElse(v)); } @@ -34,7 +40,9 @@ public static Lens.Simple> asOptional() { * @param the type of the "smaller" value that is read * @param the type of the "smaller" update value * @return the lens with S lifted + * @deprecated in favor of {@link MaybeLens#liftS} */ + @Deprecated public static Lens, T, A, B> liftS(Lens lens, S defaultS) { return lens.mapS(optS -> optS.orElse(defaultS)); } @@ -48,7 +56,9 @@ public static Lens, T, A, B> liftS(Lens len * @param the type of the "smaller" value that is read * @param the type of the "smaller" update value * @return the lens with T lifted + * @deprecated in favor of {@link MaybeLens#liftT} */ + @Deprecated public static Lens, A, B> liftT(Lens lens) { return lens.mapT(Optional::ofNullable); } @@ -62,7 +72,9 @@ public static Lens, A, B> liftT(Lens len * @param the type of the "smaller" value that is read * @param the type of the "smaller" update value * @return the lens with A lifted + * @deprecated in favor of {@link MaybeLens#liftA} */ + @Deprecated public static Lens, B> liftA(Lens lens) { return lens.mapA(Optional::ofNullable); } @@ -77,7 +89,9 @@ public static Lens, B> liftA(Lens len * @param the type of the "smaller" value that is read * @param the type of the "smaller" update value * @return the lens with B lifted + * @deprecated in favor of {@link MaybeLens#liftB} */ + @Deprecated public static Lens> liftB(Lens lens, B defaultB) { return lens.mapB(optB -> optB.orElse(defaultB)); } @@ -91,7 +105,9 @@ public static Lens> liftB(Lens len * @param the type of the "smaller" value that is read * @param the type of the "smaller" update value * @return the lens with S flattened + * @deprecated in favor of {@link MaybeLens#unLiftS} */ + @Deprecated public static Lens unLiftS(Lens, T, A, B> lens) { return lens.mapS(Optional::ofNullable); } @@ -107,7 +123,9 @@ public static Lens unLiftS(Lens, T, A, B> l * @param the type of the "smaller" value that is read * @param the type of the "smaller" update value * @return the lens with T flattened + * @deprecated in favor of {@link MaybeLens#unLiftT} */ + @Deprecated public static Lens unLiftT(Lens, A, B> lens, T defaultT) { return lens.mapT(optT -> optT.orElse(defaultT)); } @@ -123,7 +141,9 @@ public static Lens unLiftT(Lens, A, B> l * @param the type of the "smaller" value that is read * @param the type of the "smaller" update value * @return the lens with A flattened + * @deprecated in favor of {@link MaybeLens#unLiftA} */ + @Deprecated public static Lens unLiftA(Lens, B> lens, A defaultA) { return lens.mapA(optA -> optA.orElse(defaultA)); } @@ -137,7 +157,9 @@ public static Lens unLiftA(Lens, B> l * @param the type of the "smaller" value that is read * @param the type of the "smaller" update value * @return the lens with B flattened + * @deprecated in favor of {@link MaybeLens#unLiftB} */ + @Deprecated public static Lens unLiftB(Lens> lens) { return lens.mapB(Optional::ofNullable); } diff --git a/src/main/java/com/jnape/palatable/lambda/monoid/builtin/First.java b/src/main/java/com/jnape/palatable/lambda/monoid/builtin/First.java index d9ee1acd0..a933c86a8 100644 --- a/src/main/java/com/jnape/palatable/lambda/monoid/builtin/First.java +++ b/src/main/java/com/jnape/palatable/lambda/monoid/builtin/First.java @@ -1,21 +1,22 @@ package com.jnape.palatable.lambda.monoid.builtin; +import com.jnape.palatable.lambda.adt.Maybe; import com.jnape.palatable.lambda.functions.Fn1; import com.jnape.palatable.lambda.monoid.Monoid; -import java.util.Optional; +import static com.jnape.palatable.lambda.adt.Maybe.nothing; /** - * A {@link Monoid} instance formed by {@link Optional}<A>. The application to two {@link Optional} - * values produces the first non-empty value, or Optional.empty() if all values are empty. + * A {@link Monoid} instance formed by {@link Maybe}<A>. The application to two {@link Maybe} values + * produces the first non-empty value, or {@link Maybe#nothing()} if all values are empty. * - * @param the Optional value parameter type + * @param the Maybe value parameter type * @see Last * @see Present * @see Monoid - * @see Optional + * @see Maybe */ -public final class First implements Monoid> { +public final class First implements Monoid> { private static final First INSTANCE = new First(); @@ -23,13 +24,13 @@ private First() { } @Override - public Optional identity() { - return Optional.empty(); + public Maybe identity() { + return nothing(); } @Override - public Optional apply(Optional x, Optional y) { - return x.map(Optional::of).orElse(y); + public Maybe apply(Maybe x, Maybe y) { + return x.fmap(Maybe::just).orElse(y); } @SuppressWarnings("unchecked") @@ -37,11 +38,11 @@ public static First first() { return INSTANCE; } - public static Fn1, Optional> first(Optional x) { + public static Fn1, Maybe> first(Maybe x) { return First.first().apply(x); } - public static Optional first(Optional x, Optional y) { + public static Maybe first(Maybe x, Maybe y) { return first(x).apply(y); } } diff --git a/src/main/java/com/jnape/palatable/lambda/monoid/builtin/Last.java b/src/main/java/com/jnape/palatable/lambda/monoid/builtin/Last.java index 90b80f329..bb039c09a 100644 --- a/src/main/java/com/jnape/palatable/lambda/monoid/builtin/Last.java +++ b/src/main/java/com/jnape/palatable/lambda/monoid/builtin/Last.java @@ -1,34 +1,37 @@ package com.jnape.palatable.lambda.monoid.builtin; +import com.jnape.palatable.lambda.adt.Maybe; import com.jnape.palatable.lambda.functions.Fn1; import com.jnape.palatable.lambda.monoid.Monoid; -import java.util.Optional; +import static com.jnape.palatable.lambda.adt.Maybe.nothing; +import static com.jnape.palatable.lambda.monoid.builtin.First.first; + /** - * A {@link Monoid} instance formed by {@link Optional}<A>. The application to two {@link Optional} - * values produces the last non-empty value, or Optional.empty() if all values are empty. + * A {@link Monoid} instance formed by {@link Maybe}<A>. The application to two {@link Maybe} + * values produces the last non-empty value, or {@link Maybe#nothing()} if all values are empty. * - * @param the Optional value parameter type + * @param the Maybe value parameter type * @see First * @see Present * @see Monoid - * @see Optional + * @see Maybe */ -public final class Last implements Monoid> { +public final class Last implements Monoid> { private static final Last INSTANCE = new Last(); private Last() { } @Override - public Optional identity() { - return Optional.empty(); + public Maybe identity() { + return nothing(); } @Override - public Optional apply(Optional x, Optional y) { - return y.map(Optional::of).orElse(x); + public Maybe apply(Maybe x, Maybe y) { + return first(y, x); } @SuppressWarnings("unchecked") @@ -36,11 +39,11 @@ public static Last last() { return INSTANCE; } - public static Fn1, Optional> last(Optional x) { + public static Fn1, Maybe> last(Maybe x) { return Last.last().apply(x); } - public static Optional last(Optional x, Optional y) { + public static Maybe last(Maybe x, Maybe y) { return last(x).apply(y); } } diff --git a/src/main/java/com/jnape/palatable/lambda/monoid/builtin/Present.java b/src/main/java/com/jnape/palatable/lambda/monoid/builtin/Present.java index 371ede8b3..d01911cf0 100644 --- a/src/main/java/com/jnape/palatable/lambda/monoid/builtin/Present.java +++ b/src/main/java/com/jnape/palatable/lambda/monoid/builtin/Present.java @@ -1,58 +1,54 @@ package com.jnape.palatable.lambda.monoid.builtin; +import com.jnape.palatable.lambda.adt.Maybe; import com.jnape.palatable.lambda.functions.Fn1; import com.jnape.palatable.lambda.functions.specialized.MonoidFactory; import com.jnape.palatable.lambda.monoid.Monoid; import com.jnape.palatable.lambda.semigroup.Semigroup; -import java.util.Optional; -import java.util.function.Function; - +import static com.jnape.palatable.lambda.adt.Maybe.nothing; import static com.jnape.palatable.lambda.monoid.Monoid.monoid; +import static com.jnape.palatable.lambda.monoid.builtin.First.first; /** - * A {@link Monoid} instance formed by {@link Optional}<A> and a semigroup over A. The - * application to two {@link Optional} values is present-biased, such that for a given {@link Optional} x + * A {@link Monoid} instance formed by {@link Maybe}<A> and a semigroup over A. The + * application to two {@link Maybe} values is presence-biased, such that for a given {@link Maybe} x * and y: *
    - *
  • if x is a present value and y is empty, the result is x
  • - *
  • if x is an empty value and y is present, the result is y
  • + *
  • if x is present and y is absent, the result is x
  • + *
  • if x is absent, 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
  • - *
  • if both x and y are empty, the result is empty
  • + * terms of the provided semigroup, wrapped in {@link Maybe#just} *
* - * @param
the Optional value parameter type + * @param the Maybe value parameter type * @see Monoid - * @see Optional + * @see Maybe */ -public final class Present implements MonoidFactory, Optional> { +public final class Present implements MonoidFactory, Maybe> { private Present() { } @Override - public Monoid> apply(Semigroup aSemigroup) { - Semigroup> semigroup = (optX, optY) -> { - Function> combine = x -> Optional.of(optY.map(aSemigroup.apply(x)).orElse(x)); - return optX.map(combine).orElse(optY); - }; - return monoid(semigroup, Optional.empty()); + public Monoid> apply(Semigroup aSemigroup) { + return monoid((maybeX, maybeY) -> first(maybeX.fmap(x -> maybeY.fmap(aSemigroup.apply(x)).orElse(x)), maybeY), + nothing()); } public static Present present() { return new Present<>(); } - public static Monoid> present(Semigroup semigroup) { + public static Monoid> present(Semigroup semigroup) { return Present.present().apply(semigroup); } - public static Fn1, Optional> present(Semigroup aSemigroup, Optional x) { + public static Fn1, Maybe> present(Semigroup aSemigroup, Maybe x) { return present(aSemigroup).apply(x); } - public static Optional present(Semigroup semigroup, Optional x, Optional y) { + public static Maybe present(Semigroup semigroup, Maybe x, Maybe y) { return present(semigroup, x).apply(y); } } 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 cd405b634..bde70f340 100644 --- a/src/test/java/com/jnape/palatable/lambda/adt/EitherTest.java +++ b/src/test/java/com/jnape/palatable/lambda/adt/EitherTest.java @@ -13,14 +13,15 @@ import testsupport.traits.MonadLaws; import testsupport.traits.TraversableLaws; -import java.util.Optional; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; import java.util.function.BiFunction; -import static com.jnape.palatable.lambda.adt.Either.fromOptional; +import static com.jnape.palatable.lambda.adt.Either.fromMaybe; import static com.jnape.palatable.lambda.adt.Either.left; import static com.jnape.palatable.lambda.adt.Either.right; +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.adt.Maybe.nothing; import static com.jnape.palatable.traitor.framework.Subjects.subjects; import static org.hamcrest.core.Is.is; import static org.junit.Assert.assertEquals; @@ -133,26 +134,25 @@ public void matchDuallyLiftsAndFlattens() { } @Test - public void toOptionalMapsEitherToOptional() { - assertEquals(Optional.of(1), Either.right(1).toOptional()); - assertEquals(Optional.empty(), Either.right(null).toOptional()); - assertEquals(Optional.empty(), Either.left("fail").toOptional()); + public void toMaybeMapsEitherToOptional() { + assertEquals(just(1), Either.right(1).toMaybe()); + assertEquals(nothing(), Either.left("fail").toMaybe()); } @Test - public void fromOptionalMapsOptionalToEither() { - Optional present = Optional.of(1); - Optional absent = Optional.empty(); + public void fromMaybeMapsMaybeToEither() { + Maybe just = just(1); + Maybe nothing = nothing(); - assertThat(fromOptional(present, () -> "fail"), is(right(1))); - assertThat(fromOptional(absent, () -> "fail"), is(left("fail"))); + assertThat(fromMaybe(just, () -> "fail"), is(right(1))); + assertThat(fromMaybe(nothing, () -> "fail"), is(left("fail"))); } @Test - public void fromOptionalDoesNotEvaluateLeftFnForRight() { - Optional present = Optional.of(1); + public void fromMaybeDoesNotEvaluateLeftFnForRight() { + Maybe just = just(1); AtomicInteger atomicInteger = new AtomicInteger(0); - fromOptional(present, atomicInteger::incrementAndGet); + fromMaybe(just, atomicInteger::incrementAndGet); assertThat(atomicInteger.get(), is(0)); } diff --git a/src/test/java/com/jnape/palatable/lambda/adt/MaybeTest.java b/src/test/java/com/jnape/palatable/lambda/adt/MaybeTest.java index 875664c94..9205db543 100644 --- a/src/test/java/com/jnape/palatable/lambda/adt/MaybeTest.java +++ b/src/test/java/com/jnape/palatable/lambda/adt/MaybeTest.java @@ -101,11 +101,11 @@ public void peek() { @Test public void justOrThrow() { - just(1).orThrow(IllegalStateException::new); + just(1).orElseThrow(IllegalStateException::new); } @Test(expected = IllegalStateException.class) public void nothingOrThrow() { - nothing().orThrow(IllegalStateException::new); + nothing().orElseThrow(IllegalStateException::new); } } \ No newline at end of file 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 index b5fc3e4fd..8b3d8f978 100644 --- a/src/test/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct2Test.java +++ b/src/test/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct2Test.java @@ -1,11 +1,13 @@ package com.jnape.palatable.lambda.adt.coproduct; +import com.jnape.palatable.lambda.adt.Maybe; import org.junit.Before; import org.junit.Test; -import java.util.Optional; import java.util.function.Function; +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.adt.Maybe.nothing; import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; import static org.junit.Assert.assertEquals; @@ -42,8 +44,8 @@ public void diverge() { @Test public void projections() { - assertEquals(tuple(Optional.of(1), Optional.empty()), a.project()); - assertEquals(tuple(Optional.empty(), Optional.of(true)), b.project()); + assertEquals(tuple(just(1), nothing()), a.project()); + assertEquals(tuple(nothing(), just(true)), b.project()); assertEquals(tuple(a.projectA(), a.projectB()), a.project()); assertEquals(tuple(b.projectA(), b.projectB()), b.project()); @@ -51,13 +53,13 @@ public void projections() { @Test public void invert() { - assertEquals(Optional.of(1), a.invert().projectB()); - assertEquals(Optional.of(true), b.invert().projectA()); + assertEquals(just(1), a.invert().projectB()); + assertEquals(just(true), b.invert().projectA()); } @Test public void embed() { - assertEquals(Optional.of(a), a.embed(Optional::of, Optional::of)); - assertEquals(Optional.of(b), b.embed(Optional::of, Optional::of)); + assertEquals(just(a), a.embed(Maybe::just, Maybe::just)); + assertEquals(just(b), b.embed(Maybe::just, Maybe::just)); } } \ 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 index 1e3217b37..7bf9f867a 100644 --- a/src/test/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct3Test.java +++ b/src/test/java/com/jnape/palatable/lambda/adt/coproduct/CoProduct3Test.java @@ -1,11 +1,13 @@ package com.jnape.palatable.lambda.adt.coproduct; +import com.jnape.palatable.lambda.adt.Maybe; import org.junit.Before; import org.junit.Test; -import java.util.Optional; import java.util.function.Function; +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.adt.Maybe.nothing; import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; import static org.junit.Assert.assertEquals; @@ -82,9 +84,9 @@ public R match(Function aFn, Function R match(Function aFn, Function R match(Function aFn, Function stringKey = typeSafeKey(); - assertEquals(Optional.of("string value"), + assertEquals(just("string value"), singletonHMap(stringKey, "string value").get(stringKey)); } @Test public void getForAbsentKey() { - assertEquals(Optional.empty(), + assertEquals(nothing(), singletonHMap(typeSafeKey(), "string value") .get(typeSafeKey())); } @@ -38,7 +39,7 @@ public void getForAbsentKey() { @Test public void getForPresentKeyWithNullValue() { TypeSafeKey stringKey = typeSafeKey(); - assertEquals(Optional.empty(), + assertEquals(nothing(), singletonHMap(stringKey, null).get(stringKey)); } 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 index 6c09d03b1..47a0deffb 100644 --- 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 @@ -4,8 +4,8 @@ import org.junit.Test; import org.junit.runner.RunWith; -import java.util.Optional; - +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.adt.Maybe.nothing; import static com.jnape.palatable.lambda.functions.builtin.fn1.Head.head; import static java.util.Arrays.asList; import static java.util.Collections.emptyList; @@ -16,11 +16,11 @@ public class HeadTest { @Test public void returnsTheHeadOfNonEmptyIterable() { - assertEquals(Optional.of(1), head(asList(1, 2, 3))); + assertEquals(just(1), head(asList(1, 2, 3))); } @Test public void isEmptyForEmptyIterable() { - assertEquals(Optional.empty(), head(emptyList())); + assertEquals(nothing(), head(emptyList())); } } \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/LastTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/LastTest.java index bbfc550f9..acc66f46d 100644 --- a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/LastTest.java +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/LastTest.java @@ -2,8 +2,8 @@ import org.junit.Test; -import java.util.Optional; - +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.adt.Maybe.nothing; import static com.jnape.palatable.lambda.functions.builtin.fn1.Last.last; import static java.util.Arrays.asList; import static java.util.Collections.emptyList; @@ -13,11 +13,11 @@ public class LastTest { @Test public void presentForNonEmptyIterable() { - assertEquals(Optional.of(3), last(asList(1, 2, 3))); + assertEquals(just(3), last(asList(1, 2, 3))); } @Test public void emptyForEmptyIterables() { - assertEquals(Optional.empty(), last(emptyList())); + assertEquals(nothing(), last(emptyList())); } } \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/UnconsTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/UnconsTest.java index 3ddc9a5dc..b3c8ece22 100644 --- a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/UnconsTest.java +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/UnconsTest.java @@ -7,8 +7,7 @@ import org.junit.runner.RunWith; import testsupport.traits.EmptyIterableSupport; -import java.util.Optional; - +import static com.jnape.palatable.lambda.adt.Maybe.nothing; import static com.jnape.palatable.lambda.functions.builtin.fn1.Uncons.uncons; import static java.util.Arrays.asList; import static java.util.Collections.emptyList; @@ -35,6 +34,6 @@ public void nonEmptyIterable() { @Test public void emptyIterable() { - assertEquals(Optional.empty(), uncons(emptyList())); + assertEquals(nothing(), uncons(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 index 585a01793..76eb4cbd9 100644 --- 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 @@ -2,8 +2,8 @@ import org.junit.Test; -import java.util.Optional; - +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.adt.Maybe.nothing; 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; @@ -14,19 +14,16 @@ public class FindTest { @Test public void findsFirstElementMatchingPredicate() { - assertEquals(Optional.of("three"), - find(s -> s.length() > 3, asList("one", "two", "three", "four"))); + assertEquals(just("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"))); + assertEquals(nothing(), 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))); + assertEquals(just(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/builtin/fn2/ReduceLeftTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/ReduceLeftTest.java index 8baf48eb8..6ca005aef 100644 --- a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/ReduceLeftTest.java +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/ReduceLeftTest.java @@ -2,8 +2,8 @@ import org.junit.Test; -import java.util.Optional; - +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.adt.Maybe.nothing; import static com.jnape.palatable.lambda.functions.builtin.fn2.ReduceLeft.reduceLeft; import static java.util.Arrays.asList; import static java.util.Collections.emptyList; @@ -15,17 +15,11 @@ public class ReduceLeftTest { @Test public void reduceLeftAccumulatesLeftToRightUsingFirstElementAsStartingAccumulation() { - assertThat( - reduceLeft(explainFold(), asList("1", "2", "3", "4", "5")), - is(Optional.of("((((1 + 2) + 3) + 4) + 5)")) - ); + assertThat(reduceLeft(explainFold(), asList("1", "2", "3", "4", "5")), is(just("((((1 + 2) + 3) + 4) + 5)"))); } @Test public void isEmptyIfIterableIsEmpty() { - assertThat( - reduceLeft(explainFold(), emptyList()), - is(Optional.empty()) - ); + assertThat(reduceLeft(explainFold(), emptyList()), is(nothing())); } } diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/ReduceRightTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/ReduceRightTest.java index eb4bfb8d7..1c9747684 100644 --- a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/ReduceRightTest.java +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/ReduceRightTest.java @@ -2,8 +2,8 @@ import org.junit.Test; -import java.util.Optional; - +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.adt.Maybe.nothing; import static com.jnape.palatable.lambda.functions.builtin.fn2.ReduceRight.reduceRight; import static java.util.Arrays.asList; import static java.util.Collections.emptyList; @@ -15,17 +15,11 @@ public class ReduceRightTest { @Test public void accumulatesRightToLeftUsingLastElementAsStartingAccumulation() { - assertThat( - reduceRight(explainFold(), asList("1", "2", "3", "4", "5")), - is(Optional.of("(1 + (2 + (3 + (4 + 5))))")) - ); + assertThat(reduceRight(explainFold(), asList("1", "2", "3", "4", "5")), is(just("(1 + (2 + (3 + (4 + 5))))"))); } @Test public void isEmptyIfIterableIsEmpty() { - assertThat( - reduceRight(explainFold(), emptyList()), - is(Optional.empty()) - ); + assertThat(reduceRight(explainFold(), emptyList()), is(nothing())); } } diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/SequenceTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/SequenceTest.java index f5a8825d5..3eea6917f 100644 --- a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/SequenceTest.java +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/SequenceTest.java @@ -5,7 +5,6 @@ import com.jnape.palatable.lambda.functor.builtin.Identity; import org.junit.Test; -import java.util.Optional; import java.util.function.Function; import static com.jnape.palatable.lambda.adt.Either.right; @@ -47,10 +46,4 @@ public void iterableSpecialization() { .orThrow(l -> new AssertionError("Expected a right value, but was a left value of <" + l + ">")), iterates(1, 2)); } - - @Test - public void optionalSpecialization() { - assertEquals(right(Optional.of(1)), - sequence(Optional.of(right(1)), Either::right)); - } } \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/UnfoldrTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/UnfoldrTest.java index ed23697fa..95b0cf7c1 100644 --- a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/UnfoldrTest.java +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/UnfoldrTest.java @@ -9,12 +9,14 @@ import testsupport.traits.InfiniteIteration; import testsupport.traits.Laziness; -import java.util.Optional; - +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.adt.Maybe.nothing; import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; import static com.jnape.palatable.lambda.functions.builtin.fn2.Take.take; import static com.jnape.palatable.lambda.functions.builtin.fn2.Unfoldr.unfoldr; import static org.junit.Assert.assertThat; +import static testsupport.matchers.IterableMatcher.isEmpty; import static testsupport.matchers.IterableMatcher.iterates; @RunWith(Traits.class) @@ -22,11 +24,16 @@ public class UnfoldrTest { @TestTraits({Laziness.class, InfiniteIteration.class, ImmutableIteration.class}) public Fn1 createTestSubject() { - return unfoldr(x -> Optional.of(tuple(x, x))); + return unfoldr(x -> just(tuple(x, x))); } @Test public void iteratesIterableFromSeedValueAndSuccessiveFunctionApplications() { - assertThat(take(5, unfoldr(x -> Optional.of(tuple(x, x + 1)), 0)), iterates(0, 1, 2, 3, 4)); + assertThat(take(5, unfoldr(x -> just(tuple(x, x + 1)), 0)), iterates(0, 1, 2, 3, 4)); + } + + @Test + public void emptyIteration() { + assertThat(unfoldr(constantly(nothing()), 1), isEmpty()); } } diff --git a/src/test/java/com/jnape/palatable/lambda/iterators/ConcatenatingIteratorTest.java b/src/test/java/com/jnape/palatable/lambda/iterators/ConcatenatingIteratorTest.java index 116e738fb..47c360f0e 100644 --- a/src/test/java/com/jnape/palatable/lambda/iterators/ConcatenatingIteratorTest.java +++ b/src/test/java/com/jnape/palatable/lambda/iterators/ConcatenatingIteratorTest.java @@ -8,8 +8,8 @@ import java.util.Collections; import java.util.Iterator; -import java.util.Optional; +import static com.jnape.palatable.lambda.adt.Maybe.just; import static com.jnape.palatable.lambda.functions.builtin.fn1.Last.last; import static com.jnape.palatable.lambda.functions.builtin.fn1.Size.size; import static com.jnape.palatable.lambda.functions.builtin.fn2.Iterate.iterate; @@ -93,7 +93,7 @@ public void stackSafety() { Iterable ints = () -> new ConcatenatingIterator<>(deeplyNestedConcat, deeplyNestedConcat); - assertEquals(Optional.of(stackBlowingNumber), last(ints)); + assertEquals(just(stackBlowingNumber), last(ints)); assertEquals((long) (stackBlowingNumber * 2), size(ints).longValue()); } } \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/iterators/SnocIteratorTest.java b/src/test/java/com/jnape/palatable/lambda/iterators/SnocIteratorTest.java index 6ccbe3fc3..a0b156a57 100644 --- a/src/test/java/com/jnape/palatable/lambda/iterators/SnocIteratorTest.java +++ b/src/test/java/com/jnape/palatable/lambda/iterators/SnocIteratorTest.java @@ -4,8 +4,8 @@ import org.junit.Test; import java.util.Collections; -import java.util.Optional; +import static com.jnape.palatable.lambda.adt.Maybe.just; import static com.jnape.palatable.lambda.functions.builtin.fn1.Last.last; import static com.jnape.palatable.lambda.functions.builtin.fn2.Iterate.iterate; import static com.jnape.palatable.lambda.functions.builtin.fn2.Take.take; @@ -53,6 +53,6 @@ public void stackSafety() { Collections::emptyIterator, take(stackBlowingNumber, iterate(x -> x + 1, 1))); - assertEquals(Optional.of(stackBlowingNumber), last(ints)); + assertEquals(just(stackBlowingNumber), last(ints)); } } \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/iterators/UnfoldingIteratorTest.java b/src/test/java/com/jnape/palatable/lambda/iterators/UnfoldingIteratorTest.java index 1aadfde06..2bcb6474a 100644 --- a/src/test/java/com/jnape/palatable/lambda/iterators/UnfoldingIteratorTest.java +++ b/src/test/java/com/jnape/palatable/lambda/iterators/UnfoldingIteratorTest.java @@ -1,28 +1,27 @@ package com.jnape.palatable.lambda.iterators; +import com.jnape.palatable.lambda.adt.Maybe; import com.jnape.palatable.lambda.adt.hlist.Tuple2; import com.jnape.palatable.lambda.functions.Fn1; import org.junit.Test; -import java.util.Optional; - +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.adt.Maybe.nothing; import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; import static org.hamcrest.core.Is.is; import static org.junit.Assert.assertThat; public class UnfoldingIteratorTest { - private static final Fn1>> STRINGIFY = x -> Optional.of(tuple(x.toString(), x + 1)); + private static final Fn1>> STRINGIFY = x -> just(tuple(x.toString(), x + 1)); @Test public void hasNextIfFunctionProducesPresentValue() { - UnfoldingIterator unfoldingIterator = new UnfoldingIterator<>(STRINGIFY, 0); - assertThat(unfoldingIterator.hasNext(), is(true)); + assertThat(new UnfoldingIterator<>(STRINGIFY, 0).hasNext(), is(true)); } @Test public void doesNotHaveNextIfFunctionProducesEmptyValue() { - UnfoldingIterator unfoldingIterator = new UnfoldingIterator<>(x -> Optional.>empty(), 0); - assertThat(unfoldingIterator.hasNext(), is(false)); + assertThat(new UnfoldingIterator(x -> nothing(), 0).hasNext(), is(false)); } } \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/lens/LensTest.java b/src/test/java/com/jnape/palatable/lambda/lens/LensTest.java index 942bcbd7f..df8e40942 100644 --- a/src/test/java/com/jnape/palatable/lambda/lens/LensTest.java +++ b/src/test/java/com/jnape/palatable/lambda/lens/LensTest.java @@ -1,5 +1,6 @@ package com.jnape.palatable.lambda.lens; +import com.jnape.palatable.lambda.adt.Maybe; import com.jnape.palatable.lambda.functions.Fn1; import com.jnape.palatable.lambda.functor.builtin.Const; import com.jnape.palatable.lambda.functor.builtin.Identity; @@ -14,9 +15,9 @@ import java.util.List; import java.util.Map; -import java.util.Optional; import java.util.Set; +import static com.jnape.palatable.lambda.adt.Maybe.just; import static com.jnape.palatable.lambda.lens.Lens.lens; import static com.jnape.palatable.lambda.lens.functions.Set.set; import static com.jnape.palatable.lambda.lens.functions.View.view; @@ -65,14 +66,14 @@ public void fix() { @Test public void mapsIndividuallyOverParameters() { Lens lens = lens(s -> s.charAt(0), (s, b) -> s.length() == b); - Lens, Optional, Optional, Optional> theGambit = lens - .mapS((Optional optS) -> optS.orElse("")) - .mapT(Optional::ofNullable) - .mapA(Optional::ofNullable) - .mapB((Optional optI) -> optI.orElse(-1)); + Lens, Maybe, Maybe, Maybe> theGambit = lens + .mapS((Maybe maybeS) -> maybeS.orElse("")) + .mapT(Maybe::maybe) + .mapA(Maybe::maybe) + .mapB((Maybe maybeI) -> maybeI.orElse(-1)); - Lens.Fixed, Optional, Optional, Optional, Identity, Identity>, Identity>> fixed = theGambit.fix(); - assertEquals(Optional.of(true), fixed.apply(optC -> new Identity<>(optC.map(c -> parseInt(Character.toString(c)))), Optional.of("321")).runIdentity()); + Lens.Fixed, Maybe, Maybe, Maybe, Identity, Identity>, Identity>> fixed = theGambit.fix(); + assertEquals(just(true), fixed.apply(maybeC -> new Identity<>(maybeC.fmap(c -> parseInt(Character.toString(c)))), just("321")).runIdentity()); } @Test diff --git a/src/test/java/com/jnape/palatable/lambda/lens/lenses/EitherLensTest.java b/src/test/java/com/jnape/palatable/lambda/lens/lenses/EitherLensTest.java index fb518dd9b..08bf1d3e3 100644 --- a/src/test/java/com/jnape/palatable/lambda/lens/lenses/EitherLensTest.java +++ b/src/test/java/com/jnape/palatable/lambda/lens/lenses/EitherLensTest.java @@ -1,13 +1,14 @@ package com.jnape.palatable.lambda.lens.lenses; import com.jnape.palatable.lambda.adt.Either; +import com.jnape.palatable.lambda.adt.Maybe; import com.jnape.palatable.lambda.lens.Lens; import org.junit.Test; -import java.util.Optional; - import static com.jnape.palatable.lambda.adt.Either.left; import static com.jnape.palatable.lambda.adt.Either.right; +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.adt.Maybe.nothing; import static com.jnape.palatable.lambda.lens.functions.Set.set; import static com.jnape.palatable.lambda.lens.functions.View.view; import static org.junit.Assert.assertEquals; @@ -16,25 +17,25 @@ public class EitherLensTest { @Test public void rightFocusesOnRightValues() { - Lens.Simple, Optional> right = EitherLens.right(); - - assertEquals(Optional.of(1), view(right, right(1))); - assertEquals(Optional.empty(), view(right, left("fail"))); - assertEquals(right(2), set(right, Optional.of(2), right(1))); - assertEquals(right(1), set(right, Optional.empty(), right(1))); - assertEquals(right(2), set(right, Optional.of(2), left("fail"))); - assertEquals(left("fail"), set(right, Optional.empty(), left("fail"))); + Lens.Simple, Maybe> right = EitherLens.right(); + + assertEquals(just(1), view(right, right(1))); + assertEquals(nothing(), view(right, left("fail"))); + assertEquals(right(2), set(right, just(2), right(1))); + assertEquals(right(1), set(right, nothing(), right(1))); + assertEquals(right(2), set(right, just(2), left("fail"))); + assertEquals(left("fail"), set(right, nothing(), left("fail"))); } @Test public void leftFocusesOnLeftValues() { - Lens.Simple, Optional> left = EitherLens.left(); - - assertEquals(Optional.of("fail"), view(left, left("fail"))); - assertEquals(Optional.empty(), view(left, right(1))); - assertEquals(left("foo"), set(left, Optional.of("foo"), left("fail"))); - assertEquals(left("fail"), set(left, Optional.empty(), left("fail"))); - assertEquals(left("foo"), set(left, Optional.of("foo"), right(1))); - assertEquals(right(1), set(left, Optional.empty(), right(1))); + Lens.Simple, Maybe> left = EitherLens.left(); + + assertEquals(just("fail"), view(left, left("fail"))); + assertEquals(nothing(), view(left, right(1))); + assertEquals(left("foo"), set(left, just("foo"), left("fail"))); + assertEquals(left("fail"), set(left, nothing(), left("fail"))); + assertEquals(left("foo"), set(left, just("foo"), right(1))); + assertEquals(right(1), set(left, nothing(), right(1))); } } \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/lens/lenses/IterableLensTest.java b/src/test/java/com/jnape/palatable/lambda/lens/lenses/IterableLensTest.java index c6f0dd294..ed878eb75 100644 --- a/src/test/java/com/jnape/palatable/lambda/lens/lenses/IterableLensTest.java +++ b/src/test/java/com/jnape/palatable/lambda/lens/lenses/IterableLensTest.java @@ -1,10 +1,11 @@ package com.jnape.palatable.lambda.lens.lenses; +import com.jnape.palatable.lambda.adt.Maybe; import com.jnape.palatable.lambda.lens.Lens; import org.junit.Test; -import java.util.Optional; - +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.adt.Maybe.nothing; import static com.jnape.palatable.lambda.functions.builtin.fn2.Map.map; import static com.jnape.palatable.lambda.lens.functions.Over.over; import static com.jnape.palatable.lambda.lens.functions.Set.set; @@ -21,10 +22,10 @@ public class IterableLensTest { @Test public void head() { - Lens, Iterable, Optional, Integer> head = IterableLens.head(); + Lens, Iterable, Maybe, Integer> head = IterableLens.head(); - assertEquals(Optional.of(1), view(head, asList(1, 2, 3))); - assertEquals(Optional.empty(), view(head, emptyList())); + assertEquals(just(1), view(head, asList(1, 2, 3))); + assertEquals(nothing(), view(head, emptyList())); assertThat(set(head, 1, emptyList()), iterates(1)); assertThat(set(head, 1, asList(2, 2, 3)), iterates(1, 2, 3)); diff --git a/src/test/java/com/jnape/palatable/lambda/lens/lenses/ListLensTest.java b/src/test/java/com/jnape/palatable/lambda/lens/lenses/ListLensTest.java index aa31408bd..a405dc33f 100644 --- a/src/test/java/com/jnape/palatable/lambda/lens/lenses/ListLensTest.java +++ b/src/test/java/com/jnape/palatable/lambda/lens/lenses/ListLensTest.java @@ -1,13 +1,15 @@ package com.jnape.palatable.lambda.lens.lenses; +import com.jnape.palatable.lambda.adt.Maybe; import com.jnape.palatable.lambda.lens.Lens; import org.junit.Before; import org.junit.Test; import java.util.ArrayList; import java.util.List; -import java.util.Optional; +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.adt.Maybe.nothing; import static com.jnape.palatable.lambda.lens.functions.Set.set; import static com.jnape.palatable.lambda.lens.functions.View.view; import static java.util.Arrays.asList; @@ -42,10 +44,10 @@ public void asCopyFocusesOnListThroughCopy() { @Test public void elementAtFocusesOnElementAtIndex() { - Lens, List, Optional, String> at0 = ListLens.elementAt(0); + Lens, List, Maybe, String> at0 = ListLens.elementAt(0); - assertEquals(Optional.of("foo"), view(at0, xs)); - assertEquals(Optional.empty(), view(at0, emptyList())); + assertEquals(just("foo"), view(at0, xs)); + assertEquals(nothing(), view(at0, emptyList())); assertEquals(asList("quux", "bar", "baz"), set(at0, "quux", xs)); assertEquals(emptyList(), set(at0, "quux", emptyList())); } diff --git a/src/test/java/com/jnape/palatable/lambda/lens/lenses/MapLensTest.java b/src/test/java/com/jnape/palatable/lambda/lens/lenses/MapLensTest.java index 9d2047c3d..ebd19025a 100644 --- a/src/test/java/com/jnape/palatable/lambda/lens/lenses/MapLensTest.java +++ b/src/test/java/com/jnape/palatable/lambda/lens/lenses/MapLensTest.java @@ -1,5 +1,6 @@ package com.jnape.palatable.lambda.lens.lenses; +import com.jnape.palatable.lambda.adt.Maybe; import com.jnape.palatable.lambda.lens.Lens; import org.junit.Before; import org.junit.Test; @@ -8,9 +9,9 @@ import java.util.HashMap; import java.util.HashSet; import java.util.Map; -import java.util.Optional; import java.util.Set; +import static com.jnape.palatable.lambda.adt.Maybe.just; import static com.jnape.palatable.lambda.lens.functions.Set.set; import static com.jnape.palatable.lambda.lens.functions.View.view; import static com.jnape.palatable.lambda.lens.lenses.MapLens.keys; @@ -51,9 +52,9 @@ public void asCopyFocusesOnMapThroughCopy() { @Test public void valueAtFocusesOnValueAtKey() { - Lens, Map, Optional, Integer> atFoo = MapLens.valueAt("foo"); + Lens, Map, Maybe, Integer> atFoo = MapLens.valueAt("foo"); - assertEquals(Optional.of(1), view(atFoo, m)); + assertEquals(just(1), view(atFoo, m)); Map updated = set(atFoo, -1, m); assertEquals(new HashMap() {{ diff --git a/src/test/java/com/jnape/palatable/lambda/lens/lenses/MaybeLensTest.java b/src/test/java/com/jnape/palatable/lambda/lens/lenses/MaybeLensTest.java new file mode 100644 index 000000000..4af6e6777 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/lens/lenses/MaybeLensTest.java @@ -0,0 +1,81 @@ +package com.jnape.palatable.lambda.lens.lenses; + +import com.jnape.palatable.lambda.adt.Maybe; +import com.jnape.palatable.lambda.lens.Lens; +import org.junit.Before; +import org.junit.Test; + +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.adt.Maybe.nothing; +import static com.jnape.palatable.lambda.lens.Lens.lens; +import static com.jnape.palatable.lambda.lens.functions.Set.set; +import static com.jnape.palatable.lambda.lens.functions.View.view; +import static com.jnape.palatable.lambda.lens.lenses.MaybeLens.liftA; +import static com.jnape.palatable.lambda.lens.lenses.MaybeLens.liftB; +import static com.jnape.palatable.lambda.lens.lenses.MaybeLens.liftS; +import static com.jnape.palatable.lambda.lens.lenses.MaybeLens.liftT; +import static org.junit.Assert.assertEquals; + +public class MaybeLensTest { + + private Lens lens; + + @Before + public void setUp() throws Exception { + lens = lens(s -> s.charAt(0), (s, b) -> s.length() == b); + } + + @Test + public void asMaybeWrapsValuesInMaybe() { + Lens.Simple> asMaybe = MaybeLens.asMaybe(); + + assertEquals(just("foo"), view(asMaybe, "foo")); + assertEquals(nothing(), view(asMaybe, null)); + assertEquals("bar", set(asMaybe, just("bar"), "foo")); + assertEquals("foo", set(asMaybe, nothing(), "foo")); + } + + @Test + public void liftSLiftsSToMaybe() { + assertEquals((Character) '3', view(liftS(lens, "3"), nothing())); + } + + @Test + public void liftTLiftsTToMaybe() { + assertEquals(just(true), set(liftT(lens), 3, "123")); + } + + @Test + public void liftALiftsAToMaybe() { + assertEquals(just('1'), view(liftA(lens), "123")); + } + + @Test + public void liftBLiftsBToMaybe() { + assertEquals(true, set(MaybeLens.liftB(lens, 1), nothing(), "1")); + } + + @Test + public void unLiftSPullsSOutOfMaybe() { + Lens, Maybe, Maybe, Maybe> liftedToMaybe = liftS(liftT(liftA(liftB(lens, 3))), "123"); + assertEquals(just('f'), view(MaybeLens.unLiftS(liftedToMaybe), "f")); + } + + @Test + public void unLiftTPullsTOutOfMaybe() { + Lens, Maybe, Maybe, Maybe> liftedToMaybe = liftS(liftT(liftA(liftB(lens, 3))), "123"); + assertEquals(true, set(MaybeLens.unLiftT(liftedToMaybe, false), just(3), just("321"))); + } + + @Test + public void unLiftAPullsAOutOfMaybe() { + Lens, Maybe, Maybe, Maybe> liftedToMaybe = liftS(liftT(liftA(liftB(lens, 3))), "123"); + assertEquals((Character) '1', view(MaybeLens.unLiftA(liftedToMaybe, '4'), nothing())); + } + + @Test + public void unLiftBPullsBOutOfMaybe() { + Lens, Maybe, Maybe, Maybe> liftedToMaybe = liftS(liftT(liftA(liftB(lens, 3))), "123"); + assertEquals(just(true), set(MaybeLens.unLiftB(liftedToMaybe), 3, just("321"))); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/lens/lenses/OptionalLensTest.java b/src/test/java/com/jnape/palatable/lambda/lens/lenses/OptionalLensTest.java deleted file mode 100644 index b5908d600..000000000 --- a/src/test/java/com/jnape/palatable/lambda/lens/lenses/OptionalLensTest.java +++ /dev/null @@ -1,80 +0,0 @@ -package com.jnape.palatable.lambda.lens.lenses; - -import com.jnape.palatable.lambda.lens.Lens; -import org.junit.Before; -import org.junit.Test; - -import java.util.Optional; - -import static com.jnape.palatable.lambda.lens.Lens.lens; -import static com.jnape.palatable.lambda.lens.functions.Set.set; -import static com.jnape.palatable.lambda.lens.functions.View.view; -import static com.jnape.palatable.lambda.lens.lenses.OptionalLens.liftA; -import static com.jnape.palatable.lambda.lens.lenses.OptionalLens.liftB; -import static com.jnape.palatable.lambda.lens.lenses.OptionalLens.liftS; -import static com.jnape.palatable.lambda.lens.lenses.OptionalLens.liftT; -import static org.junit.Assert.assertEquals; - -public class OptionalLensTest { - - private Lens lens; - - @Before - public void setUp() throws Exception { - lens = lens(s -> s.charAt(0), (s, b) -> s.length() == b); - } - - @Test - public void asOptionalWrapsValuesInOptional() { - Lens.Simple> asOptional = OptionalLens.asOptional(); - - assertEquals(Optional.of("foo"), view(asOptional, "foo")); - assertEquals(Optional.empty(), view(asOptional, null)); - assertEquals("bar", set(asOptional, Optional.of("bar"), "foo")); - assertEquals("foo", set(asOptional, Optional.empty(), "foo")); - } - - @Test - public void liftSLiftsSToOptional() { - assertEquals((Character) '3', view(liftS(lens, "3"), Optional.empty())); - } - - @Test - public void liftTLiftsTToOptional() { - assertEquals(Optional.of(true), set(liftT(lens), 3, "123")); - } - - @Test - public void liftALiftsAToOptional() { - assertEquals(Optional.of('1'), view(liftA(lens), "123")); - } - - @Test - public void liftBLiftsBToOptional() { - assertEquals(true, set(OptionalLens.liftB(lens, 1), Optional.empty(), "1")); - } - - @Test - public void unLiftSPullsSOutOfOptional() { - Lens, Optional, Optional, Optional> liftedToOptional = liftS(liftT(liftA(liftB(lens, 3))), "123"); - assertEquals(Optional.of('f'), view(OptionalLens.unLiftS(liftedToOptional), "f")); - } - - @Test - public void unLiftTPullsTOutOfOptional() { - Lens, Optional, Optional, Optional> liftedToOptional = liftS(liftT(liftA(liftB(lens, 3))), "123"); - assertEquals(true, set(OptionalLens.unLiftT(liftedToOptional, false), Optional.of(3), Optional.of("321"))); - } - - @Test - public void unLiftAPullsAOutOfOptional() { - Lens, Optional, Optional, Optional> liftedToOptional = liftS(liftT(liftA(liftB(lens, 3))), "123"); - assertEquals((Character) '1', view(OptionalLens.unLiftA(liftedToOptional, '4'), Optional.empty())); - } - - @Test - public void unLiftBPullsBOutOfOptional() { - Lens, Optional, Optional, Optional> liftedToOptional = liftS(liftT(liftA(liftB(lens, 3))), "123"); - assertEquals(Optional.of(true), set(OptionalLens.unLiftB(liftedToOptional), 3, Optional.of("321"))); - } -} \ 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 730228769..1282b4f66 100644 --- a/src/test/java/com/jnape/palatable/lambda/monoid/MonoidTest.java +++ b/src/test/java/com/jnape/palatable/lambda/monoid/MonoidTest.java @@ -1,11 +1,12 @@ package com.jnape.palatable.lambda.monoid; -import com.jnape.palatable.lambda.monoid.Monoid; +import com.jnape.palatable.lambda.adt.Maybe; import org.junit.Test; import java.util.List; -import java.util.Optional; +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.adt.Maybe.nothing; import static com.jnape.palatable.lambda.monoid.Monoid.monoid; import static java.util.Arrays.asList; import static org.junit.Assert.assertEquals; @@ -27,7 +28,7 @@ public void reduceRight() { @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)); + List> maybeInts = asList(just(1), just(2), nothing(), just(3), nothing()); + assertEquals((Integer) 6, sum.foldMap(maybeX -> maybeX.orElse(0), maybeInts)); } } \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/monoid/builtin/FirstTest.java b/src/test/java/com/jnape/palatable/lambda/monoid/builtin/FirstTest.java index 149dd686d..4851243ba 100644 --- a/src/test/java/com/jnape/palatable/lambda/monoid/builtin/FirstTest.java +++ b/src/test/java/com/jnape/palatable/lambda/monoid/builtin/FirstTest.java @@ -2,8 +2,8 @@ import org.junit.Test; -import java.util.Optional; - +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.adt.Maybe.nothing; import static com.jnape.palatable.lambda.monoid.builtin.First.first; import static org.junit.Assert.assertEquals; @@ -11,15 +11,15 @@ public class FirstTest { @Test public void identity() { - assertEquals(Optional.empty(), first().identity()); + assertEquals(nothing(), first().identity()); } @Test public void monoid() { First first = first(); - assertEquals(Optional.of(1), first.apply(Optional.of(1), Optional.of(2))); - assertEquals(Optional.of(1), first.apply(Optional.of(1), Optional.empty())); - assertEquals(Optional.of(2), first.apply(Optional.empty(), Optional.of(2))); - assertEquals(Optional.empty(), first.apply(Optional.empty(), Optional.empty())); + assertEquals(just(1), first.apply(just(1), just(2))); + assertEquals(just(1), first.apply(just(1), nothing())); + assertEquals(just(2), first.apply(nothing(), just(2))); + assertEquals(nothing(), first.apply(nothing(), nothing())); } } \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/monoid/builtin/LastTest.java b/src/test/java/com/jnape/palatable/lambda/monoid/builtin/LastTest.java index 339c6204c..7f263b2c5 100644 --- a/src/test/java/com/jnape/palatable/lambda/monoid/builtin/LastTest.java +++ b/src/test/java/com/jnape/palatable/lambda/monoid/builtin/LastTest.java @@ -2,8 +2,8 @@ import org.junit.Test; -import java.util.Optional; - +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.adt.Maybe.nothing; import static com.jnape.palatable.lambda.monoid.builtin.Last.last; import static org.junit.Assert.assertEquals; @@ -11,15 +11,15 @@ public class LastTest { @Test public void identity() { - assertEquals(Optional.empty(), last().identity()); + assertEquals(nothing(), last().identity()); } @Test public void monoid() { Last last = last(); - assertEquals(Optional.of(2), last.apply(Optional.of(1), Optional.of(2))); - assertEquals(Optional.of(2), last.apply(Optional.empty(), Optional.of(2))); - assertEquals(Optional.of(1), last.apply(Optional.of(1), Optional.empty())); - assertEquals(Optional.empty(), last.apply(Optional.empty(), Optional.empty())); + assertEquals(just(2), last.apply(just(1), just(2))); + assertEquals(just(2), last.apply(nothing(), just(2))); + assertEquals(just(1), last.apply(just(1), nothing())); + assertEquals(nothing(), last.apply(nothing(), nothing())); } } \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/monoid/builtin/PresentTest.java b/src/test/java/com/jnape/palatable/lambda/monoid/builtin/PresentTest.java index 058cd82ce..98b1f952d 100644 --- a/src/test/java/com/jnape/palatable/lambda/monoid/builtin/PresentTest.java +++ b/src/test/java/com/jnape/palatable/lambda/monoid/builtin/PresentTest.java @@ -3,8 +3,8 @@ import com.jnape.palatable.lambda.semigroup.Semigroup; import org.junit.Test; -import java.util.Optional; - +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.adt.Maybe.nothing; import static com.jnape.palatable.lambda.monoid.builtin.Present.present; import static org.junit.Assert.assertEquals; @@ -15,9 +15,9 @@ public void monoid() { Present present = present(); Semigroup addition = (x, y) -> x + y; - assertEquals(Optional.of(3), present.apply(addition, Optional.of(1), Optional.of(2))); - assertEquals(Optional.of(1), present.apply(addition, Optional.empty(), Optional.of(1))); - assertEquals(Optional.of(1), present.apply(addition, Optional.of(1), Optional.empty())); - assertEquals(Optional.empty(), present.apply(addition, Optional.empty(), Optional.empty())); + assertEquals(just(3), present.apply(addition, just(1), just(2))); + assertEquals(just(1), present.apply(addition, nothing(), just(1))); + assertEquals(just(1), present.apply(addition, just(1), nothing())); + assertEquals(nothing(), present.apply(addition, nothing(), nothing())); } } \ No newline at end of file diff --git a/src/test/java/testsupport/traits/ApplicativeLaws.java b/src/test/java/testsupport/traits/ApplicativeLaws.java index 9ac993b0c..6255798ce 100644 --- a/src/test/java/testsupport/traits/ApplicativeLaws.java +++ b/src/test/java/testsupport/traits/ApplicativeLaws.java @@ -1,14 +1,15 @@ package testsupport.traits; -import com.jnape.palatable.lambda.functions.builtin.fn2.Map; +import com.jnape.palatable.lambda.adt.Maybe; import com.jnape.palatable.lambda.functor.Applicative; import com.jnape.palatable.lambda.monoid.builtin.Present; import com.jnape.palatable.traitor.traits.Trait; -import java.util.Optional; import java.util.Random; import java.util.function.Function; +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.adt.Maybe.nothing; import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; import static java.util.Arrays.asList; import static java.util.function.Function.identity; @@ -17,26 +18,30 @@ public class ApplicativeLaws implements Trait applicative) { - Iterable> testResults = Map., Optional>, Optional>map( - f -> f.apply(applicative), - asList(this::testIdentity, this::testComposition, this::testHomomorphism, this::testInterchange, this::testDiscardL, this::testDiscardR) - ); Present.present((x, y) -> x + "\n\t - " + y) - .reduceLeft(testResults) - .ifPresent(s -> { + ., Maybe>>foldMap( + f -> f.apply(applicative), + asList(this::testIdentity, + this::testComposition, + this::testHomomorphism, + this::testInterchange, + this::testDiscardL, + this::testDiscardR) + ) + .peek(s -> { throw new AssertionError("The following Applicative laws did not hold for instance of " + applicative.getClass() + ": \n\t - " + s); }); } - private Optional testIdentity(Applicative applicative) { + private Maybe testIdentity(Applicative applicative) { Applicative v = applicative.pure(1); Applicative, App> pureId = v.pure(identity()); return v.zip(pureId).equals(v) - ? Optional.empty() - : Optional.of("identity (v.zip(pureId).equals(v))"); + ? nothing() + : just("identity (v.zip(pureId).equals(v))"); } - private Optional testComposition(Applicative applicative) { + private Maybe testComposition(Applicative applicative) { Random random = new Random(); Integer firstInt = random.nextInt(100); Integer secondInt = random.nextInt(100); @@ -48,11 +53,11 @@ private Optional testComposition(Applicative applicative) { Applicative, ? extends Function, ? extends Function>>, App> pureCompose = u.pure(compose); return w.zip(v.zip(u.zip(pureCompose))).equals(w.zip(v).zip(u)) - ? Optional.empty() - : Optional.of("composition (w.zip(v.zip(u.zip(pureCompose))).equals((w.zip(v)).zip(u)))"); + ? nothing() + : just("composition (w.zip(v.zip(u.zip(pureCompose))).equals((w.zip(v)).zip(u)))"); } - private Optional testHomomorphism(Applicative applicative) { + private Maybe testHomomorphism(Applicative applicative) { Function f = x -> x + 1; int x = 1; @@ -60,35 +65,35 @@ private Optional testHomomorphism(Applicative applicative) { Applicative, App> pureF = applicative.pure(f); Applicative pureFx = applicative.pure(f.apply(x)); return pureX.zip(pureF).equals(pureFx) - ? Optional.empty() - : Optional.of("homomorphism (pureX.zip(pureF).equals(pureFx))"); + ? nothing() + : just("homomorphism (pureX.zip(pureF).equals(pureFx))"); } - private Optional testInterchange(Applicative applicative) { + private Maybe testInterchange(Applicative applicative) { Applicative, App> u = applicative.pure(x -> x + 1); int y = 1; Applicative pureY = applicative.pure(y); return pureY.zip(u).equals(u.zip(applicative.pure(f -> f.apply(y)))) - ? Optional.empty() - : Optional.of("interchange (pureY.zip(u).equals(u.zip(applicative.pure(f -> f.apply(y)))))"); + ? nothing() + : just("interchange (pureY.zip(u).equals(u.zip(applicative.pure(f -> f.apply(y)))))"); } - private Optional testDiscardL(Applicative applicative) { + private Maybe testDiscardL(Applicative applicative) { Applicative u = applicative.pure("u"); Applicative v = applicative.pure("v"); return u.discardL(v).equals(v.zip(u.zip(applicative.pure(constantly(identity()))))) - ? Optional.empty() - : Optional.of("discardL u.discardL(v).equals(v.zip(u.zip(applicative.pure(constantly(identity())))))"); + ? nothing() + : just("discardL u.discardL(v).equals(v.zip(u.zip(applicative.pure(constantly(identity())))))"); } - private Optional testDiscardR(Applicative applicative) { + private Maybe testDiscardR(Applicative applicative) { Applicative u = applicative.pure("u"); Applicative v = applicative.pure("v"); return u.discardR(v).equals(v.zip(u.zip(applicative.pure(constantly())))) - ? Optional.empty() - : Optional.of("discardR u.discardR(v).equals(v.zip(u.zip(applicative.pure(constantly()))))"); + ? nothing() + : just("discardR u.discardR(v).equals(v.zip(u.zip(applicative.pure(constantly()))))"); } } diff --git a/src/test/java/testsupport/traits/BifunctorLaws.java b/src/test/java/testsupport/traits/BifunctorLaws.java index b8f410472..b8bb42856 100644 --- a/src/test/java/testsupport/traits/BifunctorLaws.java +++ b/src/test/java/testsupport/traits/BifunctorLaws.java @@ -1,13 +1,14 @@ package testsupport.traits; -import com.jnape.palatable.lambda.functions.builtin.fn2.Map; +import com.jnape.palatable.lambda.adt.Maybe; import com.jnape.palatable.lambda.functor.Bifunctor; import com.jnape.palatable.lambda.monoid.builtin.Present; import com.jnape.palatable.traitor.traits.Trait; -import java.util.Optional; import java.util.function.Function; +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.adt.Maybe.nothing; import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; import static java.util.Arrays.asList; @@ -15,32 +16,33 @@ public class BifunctorLaws implements Trait bifunctor) { - Iterable> testResults = Map., Optional>, Optional>map( - f -> f.apply(bifunctor), - asList(this::testLeftIdentity, this::testRightIdentity, this::testMutualIdentity) - ); Present.present((x, y) -> x + "\n\t - " + y) - .reduceLeft(testResults) - .ifPresent(s -> { + ., Maybe>>foldMap( + f -> f.apply(bifunctor), + asList(this::testLeftIdentity, + this::testRightIdentity, + this::testMutualIdentity) + ) + .peek(s -> { throw new AssertionError("The following Bifunctor laws did not hold for instance of " + bifunctor.getClass() + ": \n\t - " + s); }); } - private Optional testLeftIdentity(Bifunctor bifunctor) { + private Maybe testLeftIdentity(Bifunctor bifunctor) { return bifunctor.biMapL(id()).equals(bifunctor) - ? Optional.empty() - : Optional.of("left identity (bifunctor.biMapL(id()).equals(bifunctor))"); + ? nothing() + : just("left identity (bifunctor.biMapL(id()).equals(bifunctor))"); } - private Optional testRightIdentity(Bifunctor bifunctor) { + private Maybe testRightIdentity(Bifunctor bifunctor) { return bifunctor.biMapR(id()).equals(bifunctor) - ? Optional.empty() - : Optional.of("right identity (bifunctor.biMapR(id()).equals(bifunctor))"); + ? nothing() + : just("right identity (bifunctor.biMapR(id()).equals(bifunctor))"); } - private Optional testMutualIdentity(Bifunctor bifunctor) { + private Maybe testMutualIdentity(Bifunctor bifunctor) { return bifunctor.biMapL(id()).biMapR(id()).equals(bifunctor.biMap(id(), id())) - ? Optional.empty() - : Optional.of("mutual identity (bifunctor.biMapL(id()).biMapR(id()).equals(bifunctor.biMap(id(),id()))"); + ? nothing() + : just("mutual identity (bifunctor.biMapL(id()).biMapR(id()).equals(bifunctor.biMap(id(),id()))"); } } diff --git a/src/test/java/testsupport/traits/FunctorLaws.java b/src/test/java/testsupport/traits/FunctorLaws.java index ad45231cf..a6b5f3d0e 100644 --- a/src/test/java/testsupport/traits/FunctorLaws.java +++ b/src/test/java/testsupport/traits/FunctorLaws.java @@ -1,12 +1,14 @@ package testsupport.traits; +import com.jnape.palatable.lambda.adt.Maybe; import com.jnape.palatable.lambda.functor.Functor; import com.jnape.palatable.lambda.monoid.builtin.Present; import com.jnape.palatable.traitor.traits.Trait; -import java.util.Optional; import java.util.function.Function; +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.adt.Maybe.nothing; import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; import static java.util.Arrays.asList; import static java.util.function.Function.identity; @@ -16,24 +18,27 @@ public class FunctorLaws implements Trait> { @Override public void test(Functor f) { Present.present((x, y) -> x + "\n\t - " + y) - .reduceLeft(asList(testIdentity(f), testComposition(f))) - .ifPresent(s -> { + ., Maybe>>foldMap( + fn -> fn.apply(f), + asList(this::testIdentity, + this::testComposition)) + .peek(s -> { throw new AssertionError("The following Functor laws did not hold for instance of " + f.getClass() + ": \n\t - " + s); }); } - private Optional testIdentity(Functor f) { + private Maybe testIdentity(Functor f) { return f.fmap(identity()).equals(f) - ? Optional.empty() - : Optional.of("identity (f.fmap(identity()).equals(f))"); + ? nothing() + : just("identity (f.fmap(identity()).equals(f))"); } - private Optional testComposition(Functor functor) { + private Maybe testComposition(Functor functor) { Functor subject = functor.fmap(constantly(1)); Function f = x -> x * 3; Function g = x -> x - 2; return subject.fmap(f.compose(g)).equals(subject.fmap(g).fmap(f)) - ? Optional.empty() - : Optional.of("composition (functor.fmap(f.compose(g)).equals(functor.fmap(g).fmap(f)))"); + ? nothing() + : just("composition (functor.fmap(f.compose(g)).equals(functor.fmap(g).fmap(f)))"); } } diff --git a/src/test/java/testsupport/traits/MonadLaws.java b/src/test/java/testsupport/traits/MonadLaws.java index 9215abb66..da7ba9e66 100644 --- a/src/test/java/testsupport/traits/MonadLaws.java +++ b/src/test/java/testsupport/traits/MonadLaws.java @@ -1,13 +1,15 @@ package testsupport.traits; +import com.jnape.palatable.lambda.adt.Maybe; import com.jnape.palatable.lambda.functions.Fn1; import com.jnape.palatable.lambda.monad.Monad; import com.jnape.palatable.lambda.monoid.builtin.Present; import com.jnape.palatable.traitor.traits.Trait; -import java.util.Optional; import java.util.function.Function; +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.adt.Maybe.nothing; import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly; import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; import static java.util.Arrays.asList; @@ -17,34 +19,34 @@ public class MonadLaws implements Trait> { @Override public void test(Monad m) { Present.present((x, y) -> x + "\n\t - " + y) - ., Optional>>foldMap(f -> f.apply(m), asList( + ., Maybe>>foldMap(f -> f.apply(m), asList( this::testLeftIdentity, this::testRightIdentity, this::testAssociativity)) - .ifPresent(s -> { + .peek(s -> { throw new AssertionError("The following Monad laws did not hold for instance of " + m.getClass() + ": \n\t - " + s); }); } - private Optional testLeftIdentity(Monad m) { + private Maybe testLeftIdentity(Monad m) { Object a = new Object(); Fn1> fn = id().andThen(m::pure); return m.pure(a).flatMap(fn).equals(fn.apply(a)) - ? Optional.empty() - : Optional.of("left identity (m.pure(a).flatMap(fn).equals(fn.apply(a)))"); + ? nothing() + : just("left identity (m.pure(a).flatMap(fn).equals(fn.apply(a)))"); } - private Optional testRightIdentity(Monad m) { + private Maybe testRightIdentity(Monad m) { return m.flatMap(m::pure).equals(m) - ? Optional.empty() - : Optional.of("right identity: (m.flatMap(m::pure).equals(m))"); + ? nothing() + : just("right identity: (m.flatMap(m::pure).equals(m))"); } - private Optional testAssociativity(Monad m) { + private Maybe testAssociativity(Monad m) { Fn1> f = constantly(m.pure(new Object())); Function> g = constantly(m.pure(new Object())); return m.flatMap(f).flatMap(g).equals(m.flatMap(a -> f.apply(a).flatMap(g))) - ? Optional.empty() - : Optional.of("associativity: (m.flatMap(f).flatMap(g).equals(m.flatMap(a -> f.apply(a).flatMap(g))))"); + ? nothing() + : just("associativity: (m.flatMap(f).flatMap(g).equals(m.flatMap(a -> f.apply(a).flatMap(g))))"); } } diff --git a/src/test/java/testsupport/traits/TraversableLaws.java b/src/test/java/testsupport/traits/TraversableLaws.java index 3908ed83a..a7d15fbd8 100644 --- a/src/test/java/testsupport/traits/TraversableLaws.java +++ b/src/test/java/testsupport/traits/TraversableLaws.java @@ -1,7 +1,7 @@ package testsupport.traits; import com.jnape.palatable.lambda.adt.Either; -import com.jnape.palatable.lambda.functions.builtin.fn2.Map; +import com.jnape.palatable.lambda.adt.Maybe; import com.jnape.palatable.lambda.functor.Applicative; import com.jnape.palatable.lambda.functor.builtin.Compose; import com.jnape.palatable.lambda.functor.builtin.Identity; @@ -9,10 +9,11 @@ import com.jnape.palatable.lambda.traversable.Traversable; import com.jnape.palatable.traitor.traits.Trait; -import java.util.Optional; import java.util.function.Function; import static com.jnape.palatable.lambda.adt.Either.right; +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.adt.Maybe.nothing; import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; import static java.util.Arrays.asList; @@ -21,18 +22,19 @@ public class TraversableLaws implements Trait traversable) { - Iterable> testResults = Map., Optional>, Optional>map( - f -> f.apply(traversable), - asList(this::testNaturality, this::testIdentity, this::testComposition) - ); Present.present((x, y) -> x + "\n\t - " + y) - .reduceLeft(testResults) - .ifPresent(s -> { + ., Maybe>>foldMap( + f -> f.apply(traversable), + asList(this::testNaturality, + this::testIdentity, + this::testComposition) + ) + .peek(s -> { throw new AssertionError("The following Traversable laws did not hold for instance of " + traversable.getClass() + ": \n\t - " + s); }); } - private Optional testNaturality(Traversable trav) { + private Maybe testNaturality(Traversable trav) { Function> f = Identity::new; Function, Either> t = id -> right(id.runIdentity()); @@ -41,26 +43,26 @@ private Optional testNaturality(Traversable trav) { return t.apply(trav.traverse(f, pureFn).fmap(id()).coerce()) .equals(trav.traverse(t.compose(f), pureFn2).fmap(id()).coerce()) - ? Optional.empty() - : Optional.of("naturality (t.apply(trav.traverse(f, pureFn).fmap(id()).coerce())\n" + - " .equals(trav.traverse(t.compose(f), pureFn2).fmap(id()).coerce()))"); + ? nothing() + : just("naturality (t.apply(trav.traverse(f, pureFn).fmap(id()).coerce())\n" + + " .equals(trav.traverse(t.compose(f), pureFn2).fmap(id()).coerce()))"); } - private Optional testIdentity(Traversable trav) { + private Maybe testIdentity(Traversable trav) { return trav.traverse(Identity::new, x -> new Identity<>(x)).equals(new Identity<>(trav)) - ? Optional.empty() - : Optional.of("identity (trav.traverse(Identity::new, x -> new Identity<>(x)).equals(new Identity<>(trav))"); + ? nothing() + : just("identity (trav.traverse(Identity::new, x -> new Identity<>(x)).equals(new Identity<>(trav))"); } @SuppressWarnings("unchecked") - private Optional testComposition(Traversable trav) { + private Maybe testComposition(Traversable trav) { Function> f = Identity::new; Function> g = x -> new Identity<>(x); return trav.traverse(f.andThen(x -> x.fmap(g)).andThen(Compose::new), x -> new Compose<>(new Identity<>(new Identity<>(x)))) .equals(new Compose>(trav.traverse(f, x -> new Identity<>(x)).fmap(t -> t.traverse(g, x -> new Identity<>(x))))) - ? Optional.empty() - : Optional.of("compose (trav.traverse(f.andThen(x -> x.fmap(g)).andThen(Compose::new), x -> new Compose<>(new Identity<>(new Identity<>(x))))\n" + - " .equals(new Compose>(trav.traverse(f, x -> new Identity<>(x)).fmap(t -> t.traverse(g, x -> new Identity<>(x))))))"); + ? nothing() + : just("compose (trav.traverse(f.andThen(x -> x.fmap(g)).andThen(Compose::new), x -> new Compose<>(new Identity<>(new Identity<>(x))))\n" + + " .equals(new Compose>(trav.traverse(f, x -> new Identity<>(x)).fmap(t -> t.traverse(g, x -> new Identity<>(x))))))"); } } From c5f8cdbb0660dcabb30ccd17a4506720122f193c Mon Sep 17 00:00:00 2001 From: jnape Date: Tue, 31 Oct 2017 00:22:14 -0500 Subject: [PATCH 24/32] Fixing javadocs --- .../palatable/lambda/functions/builtin/fn2/Sequence.java | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Sequence.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Sequence.java index 9c9746928..8fda2a962 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Sequence.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Sequence.java @@ -72,9 +72,6 @@ TravApp extends Traversable, Trav>> AppTrav sequen .fmap(LambdaIterable::unwrap); } - /** - * @deprecated in favor of wrapping the {@link Optional} in {@link Maybe}, then sequencing - */ @SuppressWarnings("unchecked") @Deprecated public static , App>, OptionalApp extends Optional>> Fn1, ? extends AppOptional>, AppOptional> sequence( @@ -90,9 +87,6 @@ IterableApp extends Iterable>> AppIterable sequenc return Sequence.sequence(iterableApp).apply(pure); } - /** - * @deprecated in favor of wrapping the {@link Optional} in {@link Maybe}, then sequencing - */ @Deprecated public static , App>, OptionalApp extends Optional>> AppOptional sequence(OptionalApp optionalApp, From ea011d499980324d25cb4d8712fa4fa0aa033d51 Mon Sep 17 00:00:00 2001 From: jnape Date: Thu, 2 Nov 2017 01:28:48 -0500 Subject: [PATCH 25/32] Adding inits --- CHANGELOG.md | 1 + .../lambda/functions/builtin/fn1/Inits.java | 40 +++++++++++++++++ .../functions/builtin/fn1/InitsTest.java | 44 +++++++++++++++++++ .../lambda/functions/builtin/fn2/MapTest.java | 3 +- 4 files changed, 87 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Inits.java create mode 100644 src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/InitsTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 955e2d471..1d501b100 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/). - `LambdaIterable` - `Maybe` - `SingletonHList` +- `Inits`, for iterating all the initial element subsequences of an `Iterable` - `Force`, for forcing iteration of an `Iterable` to perform any side-effects - `Snoc`, for lazily appending an element to the end of an `Iterable` - `Coalesce`, for folding an `Iterable>` into an `Either, Iterable>` diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Inits.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Inits.java new file mode 100644 index 000000000..52c0413b6 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Inits.java @@ -0,0 +1,40 @@ +package com.jnape.palatable.lambda.functions.builtin.fn1; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.builtin.fn2.Snoc; + +import java.util.Collections; + +import static com.jnape.palatable.lambda.functions.builtin.fn3.ScanLeft.scanLeft; + +/** + * Given an {@link Iterable}<A>, produce an + * {@link Iterable}<{@link Iterable}<A>>, representing all of the subsequences of initial + * elements, ordered by size, starting with the empty {@link Iterable}. + *

+ * For example, inits(asList(1,2,3)) would iterate [], [1], [1,2], + * and [1,2,3]. + * + * @param the Iterable element type + */ +public final class Inits implements Fn1, Iterable>> { + + private static final Inits INSTANCE = new Inits(); + + private Inits() { + } + + @Override + public Iterable> apply(Iterable as) { + return scanLeft(Snoc.snoc().flip().toBiFunction(), Collections::emptyIterator, as); + } + + @SuppressWarnings("unchecked") + public static Inits inits() { + return INSTANCE; + } + + public static Iterable> inits(Iterable as) { + return Inits.inits().apply(as); + } +} diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/InitsTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/InitsTest.java new file mode 100644 index 000000000..191425e2c --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/InitsTest.java @@ -0,0 +1,44 @@ +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.InfiniteIterableSupport; +import testsupport.traits.Laziness; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Inits.inits; +import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; +import static org.junit.Assert.assertThat; +import static testsupport.matchers.IterableMatcher.iterates; + +@RunWith(Traits.class) +public class InitsTest { + + @TestTraits({Laziness.class, EmptyIterableSupport.class, InfiniteIterableSupport.class, FiniteIteration.class, ImmutableIteration.class}) + public Fn1 testSubject() { + return inits(); + } + + @Test + public void empty() { + assertThat(inits(emptyList()), iterates(emptyList())); + } + + @Test + public void nonEmpty() { + assertThat(inits(singletonList(1)), iterates(emptyList(), singletonList(1))); + assertThat(inits(asList(1, 2, 3, 4, 5)), iterates(emptyList(), + singletonList(1), + asList(1, 2), + asList(1, 2, 3), + asList(1, 2, 3, 4), + asList(1, 2, 3, 4, 5))); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/MapTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/MapTest.java index eb4c68867..92a6e725b 100644 --- a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/MapTest.java +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/MapTest.java @@ -8,6 +8,7 @@ import testsupport.traits.EmptyIterableSupport; import testsupport.traits.FiniteIteration; import testsupport.traits.ImmutableIteration; +import testsupport.traits.InfiniteIterableSupport; import testsupport.traits.Laziness; import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; @@ -19,7 +20,7 @@ @RunWith(Traits.class) public class MapTest { - @TestTraits({Laziness.class, EmptyIterableSupport.class, FiniteIteration.class, ImmutableIteration.class}) + @TestTraits({Laziness.class, EmptyIterableSupport.class, InfiniteIterableSupport.class, FiniteIteration.class, ImmutableIteration.class}) public Fn1 createTraitsTestSubject() { return map(id()); } From 0ed79ba06c97bc950f8b515fda0083a5b40f1cec Mon Sep 17 00:00:00 2001 From: jnape Date: Thu, 2 Nov 2017 01:42:26 -0500 Subject: [PATCH 26/32] squashed another stream usage in MapLens#mappingValues --- .../palatable/lambda/lens/lenses/MapLens.java | 25 ++++++++++--------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/src/main/java/com/jnape/palatable/lambda/lens/lenses/MapLens.java b/src/main/java/com/jnape/palatable/lambda/lens/lenses/MapLens.java index 332b804b5..42f0e8b7f 100644 --- a/src/main/java/com/jnape/palatable/lambda/lens/lenses/MapLens.java +++ b/src/main/java/com/jnape/palatable/lambda/lens/lenses/MapLens.java @@ -1,6 +1,7 @@ package com.jnape.palatable.lambda.lens.lenses; import com.jnape.palatable.lambda.adt.Maybe; +import com.jnape.palatable.lambda.adt.hlist.Tuple2; import com.jnape.palatable.lambda.functions.builtin.fn2.Filter; import com.jnape.palatable.lambda.lens.Lens; @@ -15,11 +16,11 @@ import static com.jnape.palatable.lambda.functions.builtin.fn2.Eq.eq; import static com.jnape.palatable.lambda.functions.builtin.fn2.Map.map; import static com.jnape.palatable.lambda.functions.builtin.fn2.ToCollection.toCollection; +import static com.jnape.palatable.lambda.functions.builtin.fn2.ToMap.toMap; import static com.jnape.palatable.lambda.lens.Lens.lens; import static com.jnape.palatable.lambda.lens.Lens.simpleLens; import static com.jnape.palatable.lambda.lens.functions.View.view; import static com.jnape.palatable.lambda.lens.lenses.MaybeLens.unLiftA; -import static java.util.stream.Collectors.toMap; import static java.util.stream.Collectors.toSet; /** @@ -137,16 +138,16 @@ public static Lens.Simple, Map> inverted() { * @return a lens that focuses on a map while mapping its values */ public static Lens.Simple, Map> mappingValues(Function fn) { - return Lens.simpleLens(m -> m.entrySet().stream().collect(toMap(Map.Entry::getKey, kv -> fn.apply(kv.getValue()))), - (s, b) -> { - //todo: remove this madness upon arrival of either invertible functions or Iso - Set retainKeys = Filter.>filter(kv -> eq(fn.apply(kv.getValue()), b.get(kv.getKey()))) - .andThen(map(Map.Entry::getKey)) - .andThen(toCollection(HashSet::new)) - .apply(s.entrySet()); - Map copy = new HashMap<>(s); - copy.keySet().retainAll(retainKeys); - return copy; - }); + return simpleLens(m -> toMap(HashMap::new, map(t -> t.biMapR(fn), map(Tuple2::fromEntry, m.entrySet()))), + (s, b) -> { + //todo: remove this madness upon arrival of either invertible functions or Iso + Set retainKeys = Filter.>filter(kv -> eq(fn.apply(kv.getValue()), b.get(kv.getKey()))) + .andThen(map(Map.Entry::getKey)) + .andThen(toCollection(HashSet::new)) + .apply(s.entrySet()); + Map copy = new HashMap<>(s); + copy.keySet().retainAll(retainKeys); + return copy; + }); } } From 17c052e2369282c578d196b0f26ca273b74e73e2 Mon Sep 17 00:00:00 2001 From: jnape Date: Sat, 4 Nov 2017 17:23:02 -0500 Subject: [PATCH 27/32] Adding tails --- CHANGELOG.md | 1 + .../lambda/functions/builtin/fn1/Tails.java | 48 +++++++++++++++++++ .../functions/builtin/fn1/TailsTest.java | 44 +++++++++++++++++ 3 files changed, 93 insertions(+) create mode 100644 src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Tails.java create mode 100644 src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/TailsTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 1d501b100..7a865e688 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/). - `Maybe` - `SingletonHList` - `Inits`, for iterating all the initial element subsequences of an `Iterable` +- `Tails`, for iterating all the tail element subsequences of an `Iterable` - `Force`, for forcing iteration of an `Iterable` to perform any side-effects - `Snoc`, for lazily appending an element to the end of an `Iterable` - `Coalesce`, for folding an `Iterable>` into an `Either, Iterable>` diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Tails.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Tails.java new file mode 100644 index 000000000..ca61cec74 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Tails.java @@ -0,0 +1,48 @@ +package com.jnape.palatable.lambda.functions.builtin.fn1; + +import com.jnape.palatable.lambda.functions.Fn1; + +import java.util.Collections; + +import static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.adt.Maybe.nothing; +import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Empty.empty; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Tail.tail; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Snoc.snoc; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Unfoldr.unfoldr; + +/** + * Given an {@link Iterable}<A>, produce an + * {@link Iterable}<{@link Iterable}<A>>, representing all of the subsequences of tail + * elements, ordered by size, starting with the full {@link Iterable}. + *

+ * For example, tails(asList(1,2,3)) would iterate [1,2,3], [2,3], + * [3], and []. + * + * @param the Iterable element type + */ +public final class Tails implements Fn1, Iterable>> { + + private static final Tails INSTANCE = new Tails(); + + private Tails() { + } + + @Override + public Iterable> apply(Iterable as) { + return snoc(Collections::emptyIterator, + unfoldr(next -> empty(next) + ? nothing() + : just(tuple(next, tail(next))), as)); + } + + @SuppressWarnings("unchecked") + public static Tails tails() { + return INSTANCE; + } + + public static Iterable> tails(Iterable as) { + return Tails.tails().apply(as); + } +} diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/TailsTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/TailsTest.java new file mode 100644 index 000000000..55e0d09fa --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/TailsTest.java @@ -0,0 +1,44 @@ +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.InfiniteIterableSupport; +import testsupport.traits.Laziness; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Tails.tails; +import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; +import static org.junit.Assert.assertThat; +import static testsupport.matchers.IterableMatcher.iterates; + +@RunWith(Traits.class) +public class TailsTest { + + @TestTraits({EmptyIterableSupport.class, InfiniteIterableSupport.class, FiniteIteration.class, ImmutableIteration.class, Laziness.class}) + public Fn1 testSubject() { + return tails(); + } + + @Test + public void empty() { + assertThat(tails(emptyList()), iterates(emptyList())); + } + + @Test + public void nonEmpty() { + assertThat(tails(singletonList(1)), iterates(singletonList(1), emptyList())); + assertThat(tails(asList(1, 2, 3, 4, 5)), iterates(asList(1, 2, 3, 4, 5), + asList(2, 3, 4, 5), + asList(3, 4, 5), + asList(4, 5), + singletonList(5), + emptyList())); + } +} \ No newline at end of file From b15fdd0d318dfd6cd8c3331af3190707e259c275 Mon Sep 17 00:00:00 2001 From: jnape Date: Sat, 4 Nov 2017 17:59:37 -0500 Subject: [PATCH 28/32] Adding init --- CHANGELOG.md | 5 ++- .../lambda/functions/builtin/fn1/Init.java | 32 +++++++++++++++ .../lambda/iterators/InitIterator.java | 32 +++++++++++++++ .../functions/builtin/fn1/InitTest.java | 40 +++++++++++++++++++ .../lambda/iterators/InitIteratorTest.java | 37 +++++++++++++++++ 5 files changed, 144 insertions(+), 2 deletions(-) create mode 100644 src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Init.java create mode 100644 src/main/java/com/jnape/palatable/lambda/iterators/InitIterator.java create mode 100644 src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/InitTest.java create mode 100644 src/test/java/com/jnape/palatable/lambda/iterators/InitIteratorTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 7a865e688..18948f33b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,8 +19,6 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/). - `LambdaIterable` - `Maybe` - `SingletonHList` -- `Inits`, for iterating all the initial element subsequences of an `Iterable` -- `Tails`, for iterating all the tail element subsequences of an `Iterable` - `Force`, for forcing iteration of an `Iterable` to perform any side-effects - `Snoc`, for lazily appending an element to the end of an `Iterable` - `Coalesce`, for folding an `Iterable>` into an `Either, Iterable>` @@ -29,6 +27,9 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/). - `Maybe`, lambda's analog of `java.util.Optional` conforming to all the lambda types - `Contravariant`, an interface representing functors that map contravariantly over their parameters - `Profunctor` extends `Contravariant` +- `Tails`, for iterating all the tail element subsequences of an `Iterable` +- `Inits`, for iterating all the initial element subsequences of an `Iterable` +- `Init`, for iterating all but the last element of an `Iterable` ### Removed - `Fn1#then(Function)`, deprecated in previous release diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Init.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Init.java new file mode 100644 index 000000000..0ba39822b --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Init.java @@ -0,0 +1,32 @@ +package com.jnape.palatable.lambda.functions.builtin.fn1; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.iterators.InitIterator; + +/** + * Given an {@link Iterable}<A>, produce an + * {@link Iterable}<A> of all elements but the last one. + * + * @param the Iterable element type + */ +public final class Init implements Fn1, Iterable> { + + private static final Init INSTANCE = new Init(); + + private Init() { + } + + @Override + public Iterable apply(Iterable as) { + return () -> new InitIterator<>(as); + } + + @SuppressWarnings("unchecked") + public static Init init() { + return INSTANCE; + } + + public static Iterable init(Iterable as) { + return Init.init().apply(as); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/iterators/InitIterator.java b/src/main/java/com/jnape/palatable/lambda/iterators/InitIterator.java new file mode 100644 index 000000000..0de021061 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/iterators/InitIterator.java @@ -0,0 +1,32 @@ +package com.jnape.palatable.lambda.iterators; + +import java.util.Iterator; +import java.util.NoSuchElementException; + +public class InitIterator extends ImmutableIterator { + private final Iterator asIterator; + private A queued; + + public InitIterator(Iterable as) { + asIterator = as.iterator(); + } + + @Override + public boolean hasNext() { + if (queued == null) + if (asIterator.hasNext()) + queued = asIterator.next(); + + return asIterator.hasNext(); + } + + @Override + public A next() { + if (!hasNext()) + throw new NoSuchElementException(); + + A next = queued; + queued = asIterator.next(); + return next; + } +} diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/InitTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/InitTest.java new file mode 100644 index 000000000..dde20b9fe --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/InitTest.java @@ -0,0 +1,40 @@ +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.InfiniteIterableSupport; +import testsupport.traits.Laziness; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Init.init; +import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; +import static org.junit.Assert.assertThat; +import static testsupport.matchers.IterableMatcher.isEmpty; +import static testsupport.matchers.IterableMatcher.iterates; + +@RunWith(Traits.class) +public class InitTest { + + @TestTraits({EmptyIterableSupport.class, InfiniteIterableSupport.class, Laziness.class, ImmutableIteration.class, FiniteIteration.class}) + public Fn1 testSubject() { + return init(); + } + + @Test + public void empty() { + assertThat(init(emptyList()), isEmpty()); + } + + @Test + public void nonEmpty() { + assertThat(init(singletonList(1)), isEmpty()); + assertThat(init(asList(1, 2, 3)), iterates(1, 2)); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/iterators/InitIteratorTest.java b/src/test/java/com/jnape/palatable/lambda/iterators/InitIteratorTest.java new file mode 100644 index 000000000..32eeabce0 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/iterators/InitIteratorTest.java @@ -0,0 +1,37 @@ +package com.jnape.palatable.lambda.iterators; + +import org.junit.Test; + +import java.util.Collections; + +import static java.util.Arrays.asList; +import static java.util.Collections.singletonList; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class InitIteratorTest { + + @Test + public void iteratesNothingForEmptyIterable() { + assertFalse(new InitIterator<>(Collections::emptyIterator).hasNext()); + } + + @Test + public void iteratesNothingForSingleElementIterable() { + assertFalse(new InitIterator<>(singletonList(1)).hasNext()); + } + + @Test + public void iteratesAllButLastElement() { + InitIterator iterator = new InitIterator<>(asList(1, 2, 3)); + + assertTrue(iterator.hasNext()); + assertEquals((Integer) 1, iterator.next()); + + assertTrue(iterator.hasNext()); + assertEquals((Integer) 2, iterator.next()); + + assertFalse(iterator.hasNext()); + } +} \ No newline at end of file From 36ecd533f80bb16519f597994daa3775ea190827 Mon Sep 17 00:00:00 2001 From: jnape Date: Fri, 10 Nov 2017 19:31:12 -0600 Subject: [PATCH 29/32] PrependAll now only allocates a single Iterable --- CHANGELOG.md | 1 + .../functions/builtin/fn2/PrependAll.java | 8 +--- .../lambda/iterators/PrependingIterator.java | 30 ++++++++++++ .../iterators/PrependingIteratorTest.java | 46 +++++++++++++++++++ 4 files changed, 79 insertions(+), 6 deletions(-) create mode 100644 src/main/java/com/jnape/palatable/lambda/iterators/PrependingIterator.java create mode 100644 src/test/java/com/jnape/palatable/lambda/iterators/PrependingIteratorTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 18948f33b..5fde985c8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/). ## [Unreleased] ### Fixed - `CoProductN#embed` no longer eagerly invokes functions +- `PrependAll` now only creates `O(1)` `Iterable`s instead of `O(3n + 1)` ### Added - `Monad` arrives. The following `Applicative`s are now also `Monad`: diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/PrependAll.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/PrependAll.java index dea00af19..da4a3dca7 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/PrependAll.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/PrependAll.java @@ -2,11 +2,7 @@ import com.jnape.palatable.lambda.functions.Fn1; import com.jnape.palatable.lambda.functions.Fn2; - -import static com.jnape.palatable.lambda.functions.builtin.fn1.Head.head; -import static com.jnape.palatable.lambda.functions.builtin.fn1.Tail.tail; -import static com.jnape.palatable.lambda.functions.builtin.fn2.Cons.cons; -import static java.util.Collections.emptyList; +import com.jnape.palatable.lambda.iterators.PrependingIterator; /** * Lazily prepend each value with of the Iterable with the supplied separator value. An empty @@ -24,7 +20,7 @@ private PrependAll() { @Override public Iterable apply(A a, Iterable as) { - return () -> head(as).fmap(head -> cons(a, cons(head, prependAll(a, tail(as))))).orElse(emptyList()).iterator(); + return () -> new PrependingIterator<>(a, as.iterator()); } @SuppressWarnings("unchecked") diff --git a/src/main/java/com/jnape/palatable/lambda/iterators/PrependingIterator.java b/src/main/java/com/jnape/palatable/lambda/iterators/PrependingIterator.java new file mode 100644 index 000000000..3b3a75fde --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/iterators/PrependingIterator.java @@ -0,0 +1,30 @@ +package com.jnape.palatable.lambda.iterators; + +import java.util.Iterator; +import java.util.NoSuchElementException; + +public final class PrependingIterator extends ImmutableIterator { + + private final A antecedent; + private final Iterator iterator; + private boolean prependNext; + + public PrependingIterator(A antecedent, Iterator iterator) { + this.antecedent = antecedent; + this.iterator = iterator; + prependNext = true; + } + + @Override + public boolean hasNext() { + return iterator.hasNext(); + } + + @Override + public A next() { + if (!iterator.hasNext()) + throw new NoSuchElementException(); + + return (prependNext = !prependNext) ? iterator.next() : antecedent; + } +} diff --git a/src/test/java/com/jnape/palatable/lambda/iterators/PrependingIteratorTest.java b/src/test/java/com/jnape/palatable/lambda/iterators/PrependingIteratorTest.java new file mode 100644 index 000000000..8f4eaa173 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/iterators/PrependingIteratorTest.java @@ -0,0 +1,46 @@ +package com.jnape.palatable.lambda.iterators; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import java.util.NoSuchElementException; + +import static java.util.Arrays.asList; +import static java.util.Collections.emptyIterator; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class PrependingIteratorTest { + + @Rule public ExpectedException thrown = ExpectedException.none(); + + @Test + public void empty() { + PrependingIterator iterator = new PrependingIterator<>(0, emptyIterator()); + assertFalse(iterator.hasNext()); + + thrown.expect(NoSuchElementException.class); + iterator.next(); + } + + @Test + public void nonEmpty() { + PrependingIterator iterator = new PrependingIterator<>(0, asList(1, 2, 3).iterator()); + + assertTrue(iterator.hasNext()); + assertEquals((Integer) 0, iterator.next()); + assertTrue(iterator.hasNext()); + assertEquals((Integer) 1, iterator.next()); + assertTrue(iterator.hasNext()); + assertEquals((Integer) 0, iterator.next()); + assertTrue(iterator.hasNext()); + assertEquals((Integer) 2, iterator.next()); + assertTrue(iterator.hasNext()); + assertEquals((Integer) 0, iterator.next()); + assertTrue(iterator.hasNext()); + assertEquals((Integer) 3, iterator.next()); + assertFalse(iterator.hasNext()); + } +} \ No newline at end of file From 10f1b0d51977ed1071ce9902823b2cfe24004643 Mon Sep 17 00:00:00 2001 From: jnape Date: Fri, 10 Nov 2017 19:37:31 -0600 Subject: [PATCH 30/32] Fixing CCE accidentally introduced in this version --- src/main/java/com/jnape/palatable/lambda/lens/Lens.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/jnape/palatable/lambda/lens/Lens.java b/src/main/java/com/jnape/palatable/lambda/lens/Lens.java index 4a4c32f2d..7042b603d 100644 --- a/src/main/java/com/jnape/palatable/lambda/lens/Lens.java +++ b/src/main/java/com/jnape/palatable/lambda/lens/Lens.java @@ -327,8 +327,9 @@ default , FA extends Functor> return this::apply; } - default Lens.Simple compose(Lens.Simple g) { - return Lens.super.compose(g).coerce(); + @SuppressWarnings("unchecked") + default Lens.Simple compose(Lens.Simple g) { + return Lens.super.compose(g)::apply; } default Lens.Simple andThen(Lens.Simple f) { From 44db57ec8e69a6bb10a3bcf6be22f0ff87ca24e6 Mon Sep 17 00:00:00 2001 From: jnape Date: Sat, 11 Nov 2017 16:46:31 -0600 Subject: [PATCH 31/32] adding CatMaybes to unwrap an Iterable> to Iterable --- CHANGELOG.md | 1 + .../functions/builtin/fn1/CatMaybes.java | 37 +++++++++++++++++ .../functions/builtin/fn1/CatMaybesTest.java | 40 +++++++++++++++++++ 3 files changed, 78 insertions(+) create mode 100644 src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/CatMaybes.java create mode 100644 src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/CatMaybesTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 5fde985c8..d8871c8c6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,6 +31,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/). - `Tails`, for iterating all the tail element subsequences of an `Iterable` - `Inits`, for iterating all the initial element subsequences of an `Iterable` - `Init`, for iterating all but the last element of an `Iterable` +- `CatMaybes`, for unwrapping the present values in an `Iterable>` to produce an `Iterable` ### Removed - `Fn1#then(Function)`, deprecated in previous release diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/CatMaybes.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/CatMaybes.java new file mode 100644 index 000000000..64b9e0a4c --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/CatMaybes.java @@ -0,0 +1,37 @@ +package com.jnape.palatable.lambda.functions.builtin.fn1; + +import com.jnape.palatable.lambda.adt.Maybe; +import com.jnape.palatable.lambda.functions.Fn1; + +import java.util.Collections; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Flatten.flatten; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Map.map; + +/** + * Given an {@link Iterable}<{@link Maybe}<A>>, return an + * {@link Iterable}<A> of only the present values. + * + * @param the {@link Maybe} element type, as well as the resulting {@link Iterable} element type + */ +public final class CatMaybes implements Fn1>, Iterable> { + private static final CatMaybes INSTANCE = new CatMaybes(); + + private CatMaybes() { + } + + @Override + public Iterable apply(Iterable> maybes) { + return flatten(map(m -> m.>fmap(Collections::singletonList) + .orElse(Collections::emptyIterator), maybes)); + } + + @SuppressWarnings("unchecked") + public static CatMaybes catMaybes() { + return INSTANCE; + } + + public static Iterable catMaybes(Iterable> as) { + return CatMaybes.catMaybes().apply(as); + } +} diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/CatMaybesTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/CatMaybesTest.java new file mode 100644 index 000000000..ed841a988 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/CatMaybesTest.java @@ -0,0 +1,40 @@ +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 static com.jnape.palatable.lambda.adt.Maybe.just; +import static com.jnape.palatable.lambda.adt.Maybe.nothing; +import static com.jnape.palatable.lambda.functions.builtin.fn1.CatMaybes.catMaybes; +import static com.jnape.palatable.lambda.functions.builtin.fn1.Repeat.repeat; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Take.take; +import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; +import static org.junit.Assert.assertThat; +import static testsupport.matchers.IterableMatcher.isEmpty; +import static testsupport.matchers.IterableMatcher.iterates; + +@RunWith(Traits.class) +public class CatMaybesTest { + + @Test + public void empty() { + assertThat(catMaybes(emptyList()), isEmpty()); + } + + @Test + public void onlyNothingsIsEquivalentToEmpty() { + assertThat(catMaybes(asList(nothing(), nothing(), nothing())), isEmpty()); + } + + @Test + public void nonEmpty() { + assertThat(catMaybes(asList(nothing(), just(1), just(2), nothing(), just(3))), iterates(1, 2, 3)); + } + + @Test + public void infiniteIterableSupport() { + assertThat(take(3, catMaybes(repeat(just(1)))), iterates(1, 1, 1)); + } +} \ No newline at end of file From e44638c8f462805dfa7eabc409f332146f095e88 Mon Sep 17 00:00:00 2001 From: jnape Date: Mon, 13 Nov 2017 01:03:37 -0600 Subject: [PATCH 32/32] [maven-release-plugin] prepare release lambda-2.0.0 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 226409c5c..b395baaa9 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ lambda - 1.6.4-SNAPSHOT + 2.0.0 jar Lambda