diff --git a/CHANGELOG.md b/CHANGELOG.md index 56605e5a8..26b2fe0a8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,29 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/). ## [Unreleased] ### Fixed +- `ConcatenatingIterator` bug where deeply nested `xs` skip elements + +### Deprecated +- `Fn1#then` in favor of `Fn1#andThen` (redundant) +- `Fn1#adapt` in favor of `Fn1#fn1` (rename) +- `Fn2#adapt` in favor of `Fn2#fn2` (rename) + +### Changed +- Loosening variance on `Fn2#fn2` and `Fn1#fn1` + +### Added +- `Fn1#andThen` overload to support composition with `Bifunction` +- `Fn1#compose` overload to support composition with `Bifunction` and `Fn2` +- `LiftA2` to lift and apply a `Bifunction` to two `Applicative`s +- `Flatten` to lazily flatten nested `Iterable>`s to `Iterable` +- `Replicate`, short-hand composition of `take` and `repeat` +- `Distinct` to produce an `Iterable` of distinct values in another `Iterable` +- `Sort` and `SortBy` for eagerly, monolithically sorting `Iterable`s and producing `List`s +- `IterableLens`, general lenses over `Iterable` +- `Xor`, a monoid representing logical exclusive-or + +## [1.6.2] - 2017-08-20 +### Fixed - ClassCastException `BiPredicate.flip` ### Added @@ -137,7 +160,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.1...HEAD +[Unreleased]: https://github.com/palatable/lambda/compare/lambda-1.6.2...HEAD +[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 [1.5.6]: https://github.com/palatable/lambda/compare/lambda-1.5.5...lambda-1.5.6 diff --git a/README.md b/README.md index 83d52c923..e34a5ab7f 100644 --- a/README.md +++ b/README.md @@ -55,14 +55,14 @@ Add the following dependency to your: com.jnape.palatable lambda - 1.6.1 + 1.6.2 ``` `build.gradle` ([Gradle](https://docs.gradle.org/current/userguide/dependency_management.html)): ```gradle -compile group: 'com.jnape.palatable', name: 'lambda', version: '1.6.1' +compile group: 'com.jnape.palatable', name: 'lambda', version: '1.6.2' ``` Examples @@ -81,8 +81,8 @@ Every function in lambda is [curried](https://www.wikiwand.com/en/Currying), so ```Java Fn1, Integer> sumOfEvenIncrementsFn = map((Integer x) -> x + 1) - .then(filter(x -> x % 2 == 0)) - .then(reduceLeft((x, y) -> x + y)); + .andThen(filter(x -> x % 2 == 0)) + .andThen(reduceLeft((x, y) -> x + y)); Integer sumOfEvenIncrements = sumOfEvenIncrementsFn.apply(asList(1, 2, 3, 4, 5)); //-> 12 @@ -120,7 +120,7 @@ Let's compose two functions: Fn1 add = x -> x + 1; Fn1 subtract = x -> x -1; -Fn1 noOp = add.then(subtract); +Fn1 noOp = add.andThen(subtract); // same as Fn1 alsoNoOp = subtract.compose(add); ``` diff --git a/pom.xml b/pom.xml index 05fcf0ab9..a58b8340f 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ lambda - 1.6.2 + 1.6.3 jar Lambda @@ -48,7 +48,7 @@ jnape John Napier - john@jnape.com + jnape09@gmail.com 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 8d24de2b4..fb1b7f27e 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/Fn1.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/Fn1.java @@ -3,8 +3,11 @@ import com.jnape.palatable.lambda.functor.Applicative; import com.jnape.palatable.lambda.functor.Profunctor; +import java.util.function.BiFunction; import java.util.function.Function; +import static com.jnape.palatable.lambda.functions.Fn2.fn2; + /** * A function taking a single argument. This is the core function type that all other function types extend and * auto-curry with. @@ -30,7 +33,9 @@ public interface Fn1 extends Applicative>, Profunctor 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); } @@ -41,34 +46,47 @@ default Fn1 then(Function f) { * @param the return type of the next function to invoke * @param f the function to invoke with this function's return value * @return a function representing the composition of this function and f - * @see Fn1#then(Function) */ @Override - @SuppressWarnings("unchecked") default Fn1 fmap(Function f) { - return (Fn1) Applicative.super.fmap(f); + return Applicative.super.fmap(f).coerce(); } + /** + * {@inheritDoc} + */ @Override default Fn1 pure(C c) { return __ -> c; } + /** + * {@inheritDoc} + */ @Override default Fn1 zip(Applicative, Fn1> appFn) { return a -> appFn.>>coerce().apply(a).apply(apply(a)); } + /** + * {@inheritDoc} + */ @SuppressWarnings("unchecked") default Fn1 zip(Fn2 appFn) { return zip((Fn1>) (Object) appFn); } + /** + * {@inheritDoc} + */ @Override default Fn1 discardL(Applicative> appB) { return Applicative.super.discardL(appB).coerce(); } + /** + * {@inheritDoc} + */ @Override default Fn1 discardR(Applicative> appB) { return Applicative.super.discardR(appB).coerce(); @@ -127,6 +145,44 @@ default Fn1 compose(Function before) { return z -> apply(before.apply(z)); } + /** + * Right-to-left composition between different arity functions. Preserves highest arity in the return type, + * specialized to lambda types (in this case, {@link BiFunction} -> {@link Fn2}). + * + * @param before the function to pass its return value to this function's input + * @param the resulting function's first argument type + * @param the resulting function's second argument type + * @return a new function from Y x Z to B + */ + default Fn2 compose(BiFunction before) { + return compose(fn2(before)); + } + + /** + * Right-to-left composition between different arity functions. Preserves highest arity in the return type. + * + * @param before the function to pass its return value to this function's input + * @param the resulting function's first argument type + * @param the resulting function's second argument type + * @return a new function from Y x Z to B + */ + default Fn2 compose(Fn2 before) { + return fn2(before.fmap(this::compose))::apply; + } + + /** + * Left-to-right composition between different arity functions. Preserves highest arity in the return type, + * specialized to lambda types (in this case, {@link BiFunction} -> {@link Fn2}). + * + * @param after the function to invoke on this function's return value + * @param the resulting function's second argument type + * @param the resulting function's return type + * @return a new function from A x C to D + */ + default Fn2 andThen(BiFunction after) { + return (a, c) -> after.apply(apply(a), c); + } + /** * Override of {@link Function#andThen(Function)}, returning an instance of Fn1 for compatibility. * Left-to-right composition. @@ -148,8 +204,23 @@ default Fn1 andThen(Function after) { * @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. + * + * @param function the function to adapt + * @param the input argument type + * @param the output type + * @return the Fn1 + */ + static Fn1 fn1(Function function) { + return function::apply; + } } diff --git a/src/main/java/com/jnape/palatable/lambda/functions/Fn2.java b/src/main/java/com/jnape/palatable/lambda/functions/Fn2.java index c449fb2aa..3855e6caa 100644 --- a/src/main/java/com/jnape/palatable/lambda/functions/Fn2.java +++ b/src/main/java/com/jnape/palatable/lambda/functions/Fn2.java @@ -3,6 +3,7 @@ import com.jnape.palatable.lambda.adt.hlist.Tuple2; import java.util.function.BiFunction; +import java.util.function.Function; /** * A function taking two arguments. Note that defining Fn2 in terms of Fn1 provides a @@ -26,6 +27,18 @@ public interface Fn2 extends Fn1> { */ C apply(A a, B b); + /** + * Same as normal composition, except that the result is an instance of Fn2 for convenience. + * + * @param before the function who's return value is this function's argument + * @param the new argument type + * @return a new Fn2<Z,B,C> + */ + @Override + default Fn2 compose(Function before) { + return fn2(Fn1.super.compose(before)); + } + /** * Partially apply this function by passing its first argument. * @@ -74,8 +87,37 @@ default BiFunction toBiFunction() { * @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. + * + * @param biFunction the biFunction to adapt + * @param the first input argument type + * @param the second input argument type + * @param the output type + * @return the Fn2 + */ + static Fn2 fn2(BiFunction biFunction) { + return biFunction::apply; + } + + /** + * Static factory method for wrapping a curried {@link Fn1} in an {@link Fn2}. + * + * @param curriedFn1 the curried fn1 to adapt + * @param the first input argument type + * @param the second input argument type + * @param the output type + * @return the Fn2 + */ + static Fn2 fn2(Fn1> curriedFn1) { + return (a, b) -> curriedFn1.apply(a).apply(b); + } } diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Distinct.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Distinct.java new file mode 100644 index 000000000..197f90abd --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Distinct.java @@ -0,0 +1,34 @@ +package com.jnape.palatable.lambda.functions.builtin.fn1; + +import com.jnape.palatable.lambda.functions.Fn1; + +import java.util.HashMap; + +import static com.jnape.palatable.lambda.functions.builtin.fn2.Filter.filter; + +/** + * Return an {@link Iterable} of the distinct values from the given input {@link Iterable}. + * + * @param the Iterable element type + */ +public final class Distinct implements Fn1, Iterable> { + private static final Distinct INSTANCE = new Distinct(); + + private Distinct() { + } + + @Override + public Iterable apply(Iterable as) { + HashMap known = new HashMap<>(); + return filter(a -> known.putIfAbsent(a, true) == null, as); + } + + @SuppressWarnings("unchecked") + public static Distinct distinct() { + return INSTANCE; + } + + public static Iterable distinct(Iterable as) { + return Distinct.distinct().apply(as); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Flatten.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Flatten.java new file mode 100644 index 000000000..0bb35e775 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Flatten.java @@ -0,0 +1,31 @@ +package com.jnape.palatable.lambda.functions.builtin.fn1; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.iterators.FlatteningIterator; + +/** + * Given a nested {@link Iterable} of {@link Iterable}s, return a lazily flattening {@link Iterable} + * of the nested elements. + * + * @param the nested Iterable element type + */ +public final class Flatten implements Fn1>, Iterable> { + private static final Flatten INSTANCE = new Flatten(); + + private Flatten() { + } + + @Override + public Iterable apply(Iterable> iterables) { + return () -> new FlatteningIterator<>(iterables.iterator()); + } + + @SuppressWarnings("unchecked") + public static Flatten flatten() { + return INSTANCE; + } + + public static Iterable flatten(Iterable> as) { + return Flatten.flatten().apply(as); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Sort.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Sort.java new file mode 100644 index 000000000..ce297dd9b --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn1/Sort.java @@ -0,0 +1,37 @@ +package com.jnape.palatable.lambda.functions.builtin.fn1; + +import com.jnape.palatable.lambda.functions.Fn1; + +import java.util.List; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; +import static com.jnape.palatable.lambda.functions.builtin.fn2.SortBy.sortBy; + +/** + * Given an {@link Iterable} of {@link Comparable} elements, return a {@link List} of the sorted elements. Note that + * this is both eager and monolithic. + * + * @param the input Iterable and output List element type + * @see com.jnape.palatable.lambda.functions.builtin.fn2.SortBy + */ +public final class Sort> implements Fn1, List> { + + private static final Sort INSTANCE = new Sort(); + + private Sort() { + } + + @Override + public List apply(Iterable as) { + return sortBy(id(), as); + } + + @SuppressWarnings("unchecked") + public static > Sort sort() { + return INSTANCE; + } + + public static > List sort(Iterable as) { + return Sort.sort().apply(as); + } +} 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 new file mode 100644 index 000000000..5e08d92dc --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/Replicate.java @@ -0,0 +1,38 @@ +package com.jnape.palatable.lambda.functions.builtin.fn2; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.Fn2; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Repeat.repeat; +import static com.jnape.palatable.lambda.functions.builtin.fn2.Take.take; + +/** + * Produce an {@link Iterable} of a value n times. + * + * @param the output Iterable element type + */ +public final class Replicate implements Fn2> { + + public static final Replicate INSTANCE = new Replicate(); + + private Replicate() { + } + + @Override + public Iterable apply(Integer n, A a) { + return take(n, repeat(a)); + } + + @SuppressWarnings("unchecked") + public static Replicate replicate() { + return INSTANCE; + } + + public static Fn1> replicate(Integer n) { + return Replicate.replicate().apply(n); + } + + public static Iterable replicate(Integer n, A a) { + return Replicate.replicate(n).apply(a); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/SortBy.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/SortBy.java new file mode 100644 index 000000000..de395356d --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn2/SortBy.java @@ -0,0 +1,49 @@ +package com.jnape.palatable.lambda.functions.builtin.fn2; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.Fn2; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Function; + +import static com.jnape.palatable.lambda.functions.builtin.fn2.ToCollection.toCollection; +import static java.util.Comparator.comparing; + +/** + * Given an {@link Iterable} and some mapping function from the {@link Iterable} element type to some + * {@link Comparable} type, produce a sorted {@link List} of the original elements based on sorting applied to the + * result of the mapping function. Note that this is both eager and monolithic. + * + * @param the input Iterable and output List element type + * @param the mapped Comparable type + * @see com.jnape.palatable.lambda.functions.builtin.fn1.Sort + */ +public final class SortBy> implements Fn2, Iterable, List> { + + private static final SortBy INSTANCE = new SortBy(); + + private SortBy() { + } + + @Override + public List apply(Function fn, Iterable as) { + List result = toCollection(ArrayList::new, as); + result.sort(comparing(fn)); + return result; + } + + @SuppressWarnings("unchecked") + public static > SortBy sortBy() { + return INSTANCE; + } + + public static > Fn1, List> sortBy( + Function fn) { + return SortBy.sortBy().apply(fn); + } + + public static > List sortBy(Function fn, Iterable as) { + return SortBy.sortBy(fn).apply(as); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/LiftA2.java b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/LiftA2.java new file mode 100644 index 000000000..c3dd1742b --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/functions/builtin/fn3/LiftA2.java @@ -0,0 +1,56 @@ +package com.jnape.palatable.lambda.functions.builtin.fn3; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.Fn2; +import com.jnape.palatable.lambda.functions.Fn3; +import com.jnape.palatable.lambda.functor.Applicative; + +import java.util.function.BiFunction; + +import static com.jnape.palatable.lambda.functions.Fn2.fn2; + +/** + * Lift into and apply a {@link BiFunction} to two {@link Applicative} values, returning the result inside the same + * {@link Applicative} context. Equivalent ot appB.zip(appA.fmap(fn)). + * + * @param the function's first argument type + * @param the function's second argument type + * @param the function's return type + * @param the applicative unification type + * @see Applicative#zip(Applicative) + */ +public final class LiftA2 implements Fn3, + Applicative, Applicative, Applicative> { + + private static final LiftA2 INSTANCE = new LiftA2(); + + private LiftA2() { + } + + @Override + public Applicative apply(BiFunction fn, + Applicative appA, + Applicative appB) { + return appB.zip(appA.fmap(fn2(fn))); + } + + @SuppressWarnings("unchecked") + public static LiftA2 liftA2() { + return INSTANCE; + } + + public static Fn2, Applicative, Applicative> liftA2( + BiFunction fn) { + return LiftA2.liftA2().apply(fn); + } + + public static Fn1, Applicative> liftA2( + BiFunction fn, Applicative appA) { + return LiftA2.liftA2(fn).apply(appA); + } + + public static Applicative liftA2( + BiFunction fn, Applicative appA, Applicative appB) { + return LiftA2.liftA2(fn, appA).apply(appB); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/iterators/ConcatenatingIterator.java b/src/main/java/com/jnape/palatable/lambda/iterators/ConcatenatingIterator.java index 0191fd58a..cd58f6f74 100644 --- a/src/main/java/com/jnape/palatable/lambda/iterators/ConcatenatingIterator.java +++ b/src/main/java/com/jnape/palatable/lambda/iterators/ConcatenatingIterator.java @@ -1,39 +1,40 @@ package com.jnape.palatable.lambda.iterators; import java.util.Iterator; -import java.util.LinkedList; import java.util.NoSuchElementException; -import java.util.Queue; import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Supplier; public class ConcatenatingIterator implements Iterator { - private final Iterable xs; - private final Iterable ys; + private final Supplier> xsSupplier; + private final Supplier> ysSupplier; private final AtomicReference> xsRef; private final AtomicReference> ysRef; - private final Queue> afterXs; - private final Queue> afterYs; + private boolean iteratedXs; public ConcatenatingIterator(Iterable xs, Iterable ys) { - this.xs = xs; - this.ys = ys; + xsSupplier = xs::iterator; + ysSupplier = ys::iterator; xsRef = new AtomicReference<>(); ysRef = new AtomicReference<>(); - afterXs = new LinkedList<>(); - afterYs = new LinkedList<>(); + iteratedXs = false; } @Override public boolean hasNext() { - queueNext(xsRef, xs, afterXs); - - if (xsIterator().hasNext()) + if (hasNext(xsRef, xsSupplier)) return true; - queueNext(ysRef, ys, afterYs); + iteratedXs = true; + return hasNext(ysRef, ysSupplier); + } - return ysIterator().hasNext(); + private boolean hasNext(AtomicReference> ref, Supplier> supplier) { + Iterator as = ref.updateAndGet(it -> it == null ? supplier.get() : it); + while (as instanceof ConcatenatingIterator && ((ConcatenatingIterator) as).iteratedXs) + as = ref.updateAndGet(it -> ((ConcatenatingIterator) it).ysRef.get()); + return as.hasNext(); } @Override @@ -41,40 +42,6 @@ public A next() { if (!hasNext()) throw new NoSuchElementException(); - return xsIterator().hasNext() ? xsIterator().next() : ysIterator().next(); - } - - private Iterator xsIterator() { - return xsRef.get(); + return !iteratedXs ? xsRef.get().next() : ysRef.get().next(); } - - private Iterator ysIterator() { - return ysRef.get(); - } - - private void queueNext(AtomicReference> iteratorRef, Iterable iterable, - Queue> queued) { - iteratorRef.updateAndGet(iterator -> { - if (iterator == null) - iterator = iterable.iterator(); - - while (iterator instanceof ConcatenatingIterator && iterator.hasNext()) { - ConcatenatingIterator concatenatingXsIterator = (ConcatenatingIterator) iterator; - - if (concatenatingXsIterator.xsIterator().hasNext()) { - queued.add(concatenatingXsIterator.ys); - iterator = concatenatingXsIterator.xsIterator(); - } else { - iterator = concatenatingXsIterator.ysIterator(); - } - } - - while (!iterator.hasNext() && !queued.isEmpty()) { - iterator = queued.poll().iterator(); - } - - return iterator; - }); - } - } diff --git a/src/main/java/com/jnape/palatable/lambda/iterators/FlatteningIterator.java b/src/main/java/com/jnape/palatable/lambda/iterators/FlatteningIterator.java new file mode 100644 index 000000000..7938ce880 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/iterators/FlatteningIterator.java @@ -0,0 +1,29 @@ +package com.jnape.palatable.lambda.iterators; + +import java.util.Iterator; +import java.util.NoSuchElementException; + +public final class FlatteningIterator extends ImmutableIterator { + private final Iterator> xss; + private Iterator xs; + + public FlatteningIterator(Iterator> xss) { + this.xss = xss; + } + + @Override + public boolean hasNext() { + while (xss.hasNext() && (xs == null || !xs.hasNext())) + xs = xss.next().iterator(); + + return xs != null && xs.hasNext(); + } + + @Override + public A next() { + if (!hasNext()) + throw new NoSuchElementException(); + + return xs.next(); + } +} 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 new file mode 100644 index 000000000..488822c9e --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/lens/lenses/IterableLens.java @@ -0,0 +1,43 @@ +package com.jnape.palatable.lambda.lens.lenses; + +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; +import static com.jnape.palatable.lambda.lens.Lens.lens; +import static com.jnape.palatable.lambda.lens.Lens.simpleLens; + +/** + * Lenses that operate on {@link Iterable}s. + */ +public final class IterableLens { + + private IterableLens() { + } + + /** + * A lens focusing on the head of a given {@link Iterable}. + * + * @param the Iterable element type + * @return a lens focusing on the head element of an {@link Iterable} + */ + public static Lens, Iterable, Optional, A> head() { + return lens(Head::head, Cons.cons().flip().compose(Tail.tail()).toBiFunction()); + } + + /** + * A lens focusing on the tail of an {@link Iterable}. + * + * @param the Iterable element type + * @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()); + } +} 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 new file mode 100644 index 000000000..9341b05d3 --- /dev/null +++ b/src/main/java/com/jnape/palatable/lambda/monoid/builtin/Xor.java @@ -0,0 +1,38 @@ +package com.jnape.palatable.lambda.monoid.builtin; + +import com.jnape.palatable.lambda.functions.Fn1; +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. + * + * @see Or + * @see And + */ +public final class Xor implements Monoid { + + private static final Xor INSTANCE = new Xor(); + + @Override + public Boolean identity() { + return false; + } + + @Override + public Boolean apply(Boolean x, Boolean y) { + return x ? !y : y; + } + + public static Xor xor() { + return INSTANCE; + } + + public static Fn1 xor(Boolean x) { + return xor().apply(x); + } + + public static Boolean xor(Boolean x, Boolean y) { + return xor(x).apply(y); + } +} diff --git a/src/main/java/com/jnape/palatable/lambda/traversable/Traversable.java b/src/main/java/com/jnape/palatable/lambda/traversable/Traversable.java index 4e41513be..fd74b4502 100644 --- a/src/main/java/com/jnape/palatable/lambda/traversable/Traversable.java +++ b/src/main/java/com/jnape/palatable/lambda/traversable/Traversable.java @@ -2,6 +2,7 @@ import com.jnape.palatable.lambda.functor.Applicative; import com.jnape.palatable.lambda.functor.Functor; +import com.jnape.palatable.lambda.functor.builtin.Identity; import java.util.function.Function; @@ -46,5 +47,11 @@ Applicative, App> trave Function, ? extends Applicative, App>> pure); @Override - Traversable fmap(Function fn); + @SuppressWarnings({"Convert2MethodRef", "unchecked"}) + default Traversable fmap(Function fn) { + return traverse(a -> new Identity<>(fn.apply(a)), x -> new Identity<>(x)) + .fmap(x -> (Traversable) x) + .>>coerce() + .runIdentity(); + } } 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 878929a31..fdcc4b0cb 100644 --- a/src/test/java/com/jnape/palatable/lambda/functions/Fn1Test.java +++ b/src/test/java/com/jnape/palatable/lambda/functions/Fn1Test.java @@ -10,8 +10,6 @@ import java.util.function.Function; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.core.Is.is; import static org.junit.Assert.assertEquals; @RunWith(Traits.class) @@ -32,16 +30,8 @@ public void profunctorProperties() { } @Test - public void thenIsJustAnAliasForFmap() { - Fn1 add2 = integer -> integer + 2; - Fn1 toString = Object::toString; - - assertThat(add2.then(toString).apply(2), is(toString.apply(add2.apply(2)))); - } - - @Test - public void adapt() { + public void fn1() { Function parseInt = Integer::parseInt; - assertEquals((Integer) 1, Fn1.adapt(parseInt).apply("1")); + assertEquals((Integer) 1, Fn1.fn1(parseInt).apply("1")); } } diff --git a/src/test/java/com/jnape/palatable/lambda/functions/Fn2Test.java b/src/test/java/com/jnape/palatable/lambda/functions/Fn2Test.java index 6a845f11a..89cf70a03 100644 --- a/src/test/java/com/jnape/palatable/lambda/functions/Fn2Test.java +++ b/src/test/java/com/jnape/palatable/lambda/functions/Fn2Test.java @@ -8,6 +8,7 @@ import static org.hamcrest.core.Is.is; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; public class Fn2Test { @@ -29,6 +30,12 @@ public void uncurries() { assertThat(CHECK_LENGTH.uncurry().apply(tuple("abc", 3)), is(true)); } + @Test + @SuppressWarnings("ConstantConditions") + public void composePreservesTypeSpecificity() { + assertTrue(CHECK_LENGTH.compose(Object::toString) instanceof Fn2); + } + @Test public void toBiFunction() { BiFunction biFunction = CHECK_LENGTH.toBiFunction(); @@ -36,8 +43,11 @@ public void toBiFunction() { } @Test - public void adapt() { - BiFunction format = String::format; - assertEquals("foo bar", Fn2.adapt(format).apply("foo %s", "bar")); + public void fn2() { + BiFunction biFunction = String::format; + assertEquals("foo bar", Fn2.fn2(biFunction).apply("foo %s", "bar")); + + Fn1> curriedFn1 = (x) -> (y) -> String.format(x, y); + assertEquals("foo bar", Fn2.fn2(curriedFn1).apply("foo %s", "bar")); } } diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/DistinctTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/DistinctTest.java new file mode 100644 index 000000000..4755af103 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/DistinctTest.java @@ -0,0 +1,30 @@ +package com.jnape.palatable.lambda.functions.builtin.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.Distinct.distinct; +import static java.util.Arrays.asList; +import static org.junit.Assert.assertThat; +import static testsupport.matchers.IterableMatcher.iterates; + +@RunWith(Traits.class) +public class DistinctTest { + + @TestTraits({Laziness.class, InfiniteIterableSupport.class, EmptyIterableSupport.class, FiniteIteration.class, ImmutableIteration.class}) + public Distinct testSubject() { + return distinct(); + } + + @Test + public void producesIterableOfOnlySingleElementOccurrences() { + assertThat(distinct(asList(1, 2, 2, 3, 3, 3)), iterates(1, 2, 3)); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/FlattenTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/FlattenTest.java new file mode 100644 index 000000000..d428e7c17 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/FlattenTest.java @@ -0,0 +1,43 @@ +package com.jnape.palatable.lambda.functions.builtin.fn1; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.lambda.functions.builtin.fn2.Map; +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.InfiniteIterableSupport; +import testsupport.traits.Laziness; + +import java.util.Collection; +import java.util.Collections; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Flatten.flatten; +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.isEmpty; +import static testsupport.matchers.IterableMatcher.iterates; + +@RunWith(Traits.class) +public class FlattenTest { + + @TestTraits({Laziness.class, InfiniteIterableSupport.class, EmptyIterableSupport.class, FiniteIteration.class}) + public Fn1, Iterable> testSubject() { + return Flatten.flatten().compose(Map.>map(Collections::singletonList)); + } + + @Test + public void flattensIterableOfEmptyIterables() { + assertThat(flatten(asList(emptyList(), emptyList())), isEmpty()); + } + + @Test + public void flattensSparseIterableOfPopulatedIterables() { + assertThat(flatten(asList(emptyList(), asList(1, 2, 3), emptyList(), emptyList(), singleton(4), asList(5, 6), emptyList())), + iterates(1, 2, 3, 4, 5, 6)); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/SortTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/SortTest.java new file mode 100644 index 000000000..61181d07c --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn1/SortTest.java @@ -0,0 +1,27 @@ +package com.jnape.palatable.lambda.functions.builtin.fn1; + +import com.jnape.palatable.lambda.functions.Fn1; +import com.jnape.palatable.traitor.annotations.TestTraits; +import org.junit.Test; +import testsupport.traits.EmptyIterableSupport; +import testsupport.traits.FiniteIteration; + +import java.util.List; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Sort.sort; +import static java.util.Arrays.asList; +import static org.junit.Assert.assertThat; +import static testsupport.matchers.IterableMatcher.iterates; + +public class SortTest { + + @TestTraits({FiniteIteration.class, EmptyIterableSupport.class}) + public Fn1, List> testSubject() { + return sort(); + } + + @Test + public void sortsIterable() { + assertThat(sort(asList(2, 1, 3)), iterates(1, 2, 3)); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/ReplicateTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/ReplicateTest.java new file mode 100644 index 000000000..66848b16e --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/ReplicateTest.java @@ -0,0 +1,21 @@ +package com.jnape.palatable.lambda.functions.builtin.fn2; + +import org.junit.Test; + +import static com.jnape.palatable.lambda.functions.builtin.fn2.Replicate.replicate; +import static org.junit.Assert.assertThat; +import static testsupport.matchers.IterableMatcher.isEmpty; +import static testsupport.matchers.IterableMatcher.iterates; + +public class ReplicateTest { + + @Test + public void replicate0TimesProducesEmptyIterable() { + assertThat(replicate(0, 1), isEmpty()); + } + + @Test + public void replicateMoreThan0TimesProducesPopulatedIterable() { + assertThat(replicate(3, '1'), iterates('1', '1', '1')); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/SortByTest.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/SortByTest.java new file mode 100644 index 000000000..bae0d1526 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn2/SortByTest.java @@ -0,0 +1,31 @@ +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 java.util.List; + +import static com.jnape.palatable.lambda.functions.builtin.fn1.Id.id; +import static com.jnape.palatable.lambda.functions.builtin.fn2.SortBy.sortBy; +import static java.util.Arrays.asList; +import static org.junit.Assert.assertThat; +import static testsupport.matchers.IterableMatcher.iterates; + +@RunWith(Traits.class) +public class SortByTest { + + @TestTraits({FiniteIteration.class, EmptyIterableSupport.class}) + public Fn1, List> testSubject() { + return sortBy(id()); + } + + @Test + public void sortsIterable() { + assertThat(sortBy(id(), asList(2, 1, 3)), iterates(1, 2, 3)); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/LiftA2Test.java b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/LiftA2Test.java new file mode 100644 index 000000000..159878c0c --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/functions/builtin/fn3/LiftA2Test.java @@ -0,0 +1,22 @@ +package com.jnape.palatable.lambda.functions.builtin.fn3; + +import com.jnape.palatable.lambda.functor.builtin.Identity; +import org.junit.Test; + +import java.util.function.BiFunction; + +import static com.jnape.palatable.lambda.adt.Either.right; +import static com.jnape.palatable.lambda.adt.hlist.HList.tuple; +import static com.jnape.palatable.lambda.functions.builtin.fn3.LiftA2.liftA2; +import static org.junit.Assert.assertEquals; + +public class LiftA2Test { + + @Test + public void liftsAndAppliesDyadicFunctionToTwoApplicatives() { + BiFunction add = (x, y) -> x + y; + assertEquals(right(3), liftA2(add, right(1), right(2)).coerce()); + assertEquals(tuple(2, 5), liftA2(add, tuple(1, 2), tuple(2, 3)).coerce()); + assertEquals(new Identity<>(3), liftA2(add, new Identity<>(1), new Identity<>(2))); + } +} \ No newline at end of file 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 f60bbcb3c..116e738fb 100644 --- a/src/test/java/com/jnape/palatable/lambda/iterators/ConcatenatingIteratorTest.java +++ b/src/test/java/com/jnape/palatable/lambda/iterators/ConcatenatingIteratorTest.java @@ -8,13 +8,14 @@ import java.util.Collections; import java.util.Iterator; +import java.util.Optional; -import static com.jnape.palatable.lambda.functions.builtin.fn2.Drop.drop; +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; import static com.jnape.palatable.lambda.functions.builtin.fn2.Map.map; import static com.jnape.palatable.lambda.functions.builtin.fn2.Take.take; import static com.jnape.palatable.lambda.functions.builtin.fn3.FoldRight.foldRight; -import static java.util.Arrays.asList; import static org.hamcrest.core.Is.is; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -26,9 +27,10 @@ @RunWith(MockitoJUnitRunner.class) public class ConcatenatingIteratorTest { - @Mock private Iterator xs; - @Mock private Iterator ys; - private ConcatenatingIterator concatenatingIterator; + @Mock private Iterator xs; + @Mock private Iterator ys; + + private ConcatenatingIterator concatenatingIterator; @Before public void setUp() throws Exception { @@ -82,28 +84,16 @@ public void nextPullsFromXsFirstThenFromYs() { } @Test - public void concatenatingIteratorInXsPosition() { - ConcatenatingIterator iterator = - new ConcatenatingIterator<>(() -> new ConcatenatingIterator<>(asList(1, 2), asList(3, 4)), - asList(5, 6)); - - assertThat(iterator.next(), is(1)); - assertThat(iterator.next(), is(2)); - assertThat(iterator.next(), is(3)); - assertThat(iterator.next(), is(4)); - assertThat(iterator.next(), is(5)); - assertThat(iterator.next(), is(6)); - } - - @Test - public void stackSafetyInYsPosition() { - Integer stackBlowingNumber = 100000; + public void stackSafety() { + Integer stackBlowingNumber = 50000; Iterable> xss = map(Collections::singleton, take(stackBlowingNumber, iterate(x -> x + 1, 1))); - Iterable ints = foldRight((xs, ys) -> () -> new ConcatenatingIterator<>(xs, ys), - (Iterable) Collections.emptySet(), - xss); + Iterable deeplyNestedConcat = foldRight((xs, ys) -> () -> new ConcatenatingIterator<>(xs, ys), + (Iterable) Collections.emptySet(), + xss); + + Iterable ints = () -> new ConcatenatingIterator<>(deeplyNestedConcat, deeplyNestedConcat); - assertEquals(stackBlowingNumber, - take(1, drop(stackBlowingNumber - 1, ints)).iterator().next()); + assertEquals(Optional.of(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/FlatteningIteratorTest.java b/src/test/java/com/jnape/palatable/lambda/iterators/FlatteningIteratorTest.java new file mode 100644 index 000000000..e33cb656b --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/iterators/FlatteningIteratorTest.java @@ -0,0 +1,34 @@ +package com.jnape.palatable.lambda.iterators; + +import org.junit.Test; + +import static java.util.Arrays.asList; +import static java.util.Collections.emptyIterator; +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; + +public class FlatteningIteratorTest { + + @Test + public void doesNotHaveNextForEmptyIterable() { + FlatteningIterator iterator = new FlatteningIterator<>(emptyIterator()); + assertFalse(iterator.hasNext()); + } + + @Test + public void doesNotHaveNextForIterableOfEmptyIterables() { + FlatteningIterator iterator = new FlatteningIterator<>(singletonList(emptyList()).iterator()); + assertFalse(iterator.hasNext()); + } + + @Test + public void iteratesNestedIterables() { + FlatteningIterator iterator = new FlatteningIterator<>(asList(singletonList(1), asList(2,3), emptyList()).iterator()); + assertEquals(1, iterator.next()); + assertEquals(2, iterator.next()); + assertEquals(3, iterator.next()); + assertFalse(iterator.hasNext()); + } +} \ 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 new file mode 100644 index 000000000..c6f0dd294 --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/lens/lenses/IterableLensTest.java @@ -0,0 +1,51 @@ +package com.jnape.palatable.lambda.lens.lenses; + +import com.jnape.palatable.lambda.lens.Lens; +import org.junit.Test; + +import java.util.Optional; + +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; +import static com.jnape.palatable.lambda.lens.functions.View.view; +import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; +import static testsupport.matchers.IterableMatcher.isEmpty; +import static testsupport.matchers.IterableMatcher.iterates; + +public class IterableLensTest { + + @Test + public void head() { + Lens, Iterable, Optional, Integer> head = IterableLens.head(); + + assertEquals(Optional.of(1), view(head, asList(1, 2, 3))); + assertEquals(Optional.empty(), view(head, emptyList())); + + assertThat(set(head, 1, emptyList()), iterates(1)); + assertThat(set(head, 1, asList(2, 2, 3)), iterates(1, 2, 3)); + + assertThat(over(head, x -> x.orElse(0) + 1, emptyList()), iterates(1)); + assertThat(over(head, x -> x.orElse(0) + 1, asList(1, 2, 3)), iterates(2, 2, 3)); + } + + @Test + public void tail() { + Lens.Simple, Iterable> tail = IterableLens.tail(); + + assertThat(view(tail, asList(1, 2, 3)), iterates(2, 3)); + assertThat(view(tail, emptyList()), isEmpty()); + + assertThat(set(tail, asList(2, 3), singletonList(1)), iterates(1, 2, 3)); + assertThat(set(tail, emptyList(), asList(1, 2, 3)), iterates(1)); + assertThat(set(tail, asList(1, 2, 3), emptyList()), iterates(1, 2, 3)); + assertThat(set(tail, emptyList(), emptyList()), isEmpty()); + + assertThat(over(tail, map(x -> x + 1), emptyList()), isEmpty()); + assertThat(over(tail, map(x -> x + 1), asList(1, 2, 3)), iterates(1, 3, 4)); + } +} \ No newline at end of file diff --git a/src/test/java/com/jnape/palatable/lambda/monoid/builtin/XorTest.java b/src/test/java/com/jnape/palatable/lambda/monoid/builtin/XorTest.java new file mode 100644 index 000000000..0f7aefeac --- /dev/null +++ b/src/test/java/com/jnape/palatable/lambda/monoid/builtin/XorTest.java @@ -0,0 +1,27 @@ +package com.jnape.palatable.lambda.monoid.builtin; + +import org.junit.Test; + +import static com.jnape.palatable.lambda.monoid.builtin.Xor.xor; +import static java.util.Arrays.asList; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class XorTest { + + @Test + public void identity() { + assertFalse(xor().identity()); + } + + @Test + public void monoid() { + assertFalse(xor(false, false)); + assertTrue(xor(true, false)); + assertTrue(xor(false, true)); + assertFalse(xor(true, true)); + + assertTrue(xor().reduceLeft(asList(true, false, false, true, true))); + assertFalse(xor().reduceLeft(asList(true, false, true, false, false))); + } +} \ No newline at end of file diff --git a/src/test/java/spike/TraversableIterableTest.java b/src/test/java/com/jnape/palatable/lambda/traversable/TraversableIterableTest.java similarity index 89% rename from src/test/java/spike/TraversableIterableTest.java rename to src/test/java/com/jnape/palatable/lambda/traversable/TraversableIterableTest.java index 1f179eb62..21a740b29 100644 --- a/src/test/java/spike/TraversableIterableTest.java +++ b/src/test/java/com/jnape/palatable/lambda/traversable/TraversableIterableTest.java @@ -1,6 +1,5 @@ -package spike; +package com.jnape.palatable.lambda.traversable; -import com.jnape.palatable.lambda.traversable.TraversableIterable; import com.jnape.palatable.traitor.annotations.TestTraits; import com.jnape.palatable.traitor.framework.Subjects; import com.jnape.palatable.traitor.runners.Traits; diff --git a/src/test/java/spike/TraversableOptionalTest.java b/src/test/java/com/jnape/palatable/lambda/traversable/TraversableOptionalTest.java similarity index 89% rename from src/test/java/spike/TraversableOptionalTest.java rename to src/test/java/com/jnape/palatable/lambda/traversable/TraversableOptionalTest.java index 85717ff43..13c330cb6 100644 --- a/src/test/java/spike/TraversableOptionalTest.java +++ b/src/test/java/com/jnape/palatable/lambda/traversable/TraversableOptionalTest.java @@ -1,6 +1,5 @@ -package spike; +package com.jnape.palatable.lambda.traversable; -import com.jnape.palatable.lambda.traversable.TraversableOptional; import com.jnape.palatable.traitor.annotations.TestTraits; import com.jnape.palatable.traitor.framework.Subjects; import com.jnape.palatable.traitor.runners.Traits;