diff options
| author | Ben Culkin <scorpress@gmail.com> | 2022-09-24 12:34:30 -0400 |
|---|---|---|
| committer | Ben Culkin <scorpress@gmail.com> | 2022-09-24 12:34:30 -0400 |
| commit | f7d96d88798c904f69f0b5306a5ddecd0456b377 (patch) | |
| tree | 5a41a891a629322f19d649c938a92ac293b19c0e /src/main/java/bjc | |
| parent | e3501bcc50d6579dd16ce678436fa93d6e528e5f (diff) | |
Add some additional combinators, of various sorts
Diffstat (limited to 'src/main/java/bjc')
| -rw-r--r-- | src/main/java/bjc/data/Either.java | 144 | ||||
| -rw-r--r-- | src/main/java/bjc/functypes/CombinatorBirds.java | 212 | ||||
| -rw-r--r-- | src/main/java/bjc/functypes/Combinators.java | 231 | ||||
| -rw-r--r-- | src/main/java/bjc/functypes/ElseFunction.java | 68 | ||||
| -rw-r--r-- | src/main/java/bjc/functypes/ForEach.java | 115 | ||||
| -rw-r--r-- | src/main/java/bjc/functypes/TriFunction.java | 91 | ||||
| -rw-r--r-- | src/main/java/bjc/functypes/Unit.java | 17 |
7 files changed, 688 insertions, 190 deletions
diff --git a/src/main/java/bjc/data/Either.java b/src/main/java/bjc/data/Either.java index 75447f2..90efba7 100644 --- a/src/main/java/bjc/data/Either.java +++ b/src/main/java/bjc/data/Either.java @@ -3,54 +3,46 @@ package bjc.data; import java.util.*; import java.util.function.*; +import bjc.functypes.ID; + /** * Represents a pair where only one side has a value. * * @author ben * - * @param <LeftType> - * The type that could be on the left. + * @param <LeftType> The type that could be on the left. * - * @param <RightType> - * The type that could be on the right. + * @param <RightType> The type that could be on the right. * */ public class Either<LeftType, RightType> { /** * Create a new either with the left value occupied. * - * @param <LeftType> - * The type of the left value. + * @param <LeftType> The type of the left value. * - * @param <RightType> - * The type of the empty right value. + * @param <RightType> The type of the empty right value. * - * @param left - * The value to put on the left. + * @param left The value to put on the left. * * @return An either with the left side occupied. */ - public static <LeftType, RightType> Either<LeftType, RightType> - left(final LeftType left) { + public static <LeftType, RightType> Either<LeftType, RightType> left(final LeftType left) { return new Either<>(left, null); } /** * Create a new either with the right value occupied. * - * @param <LeftType> - * The type of the empty left value. + * @param <LeftType> The type of the empty left value. * - * @param <RightType> - * The type of the right value. + * @param <RightType> The type of the right value. * - * @param right - * The value to put on the right. + * @param right The value to put on the right. * * @return An either with the right side occupied. */ - public static <LeftType, RightType> Either<LeftType, RightType> - right(final RightType right) { + public static <LeftType, RightType> Either<LeftType, RightType> right(final RightType right) { return new Either<>(null, right); } @@ -75,56 +67,50 @@ public class Either<LeftType, RightType> { /** * Perform a mapping over this either. * - * @param <NewLeft> The new left type. + * @param <NewLeft> The new left type. * @param <NewRight> The new right type. * - * @param leftFunc The function to apply if this is a left either. - * @param rightFunc The function to apply if this is a right either. + * @param leftFunc The function to apply if this is a left either. + * @param rightFunc The function to apply if this is a right either. * - * @return A new either, containing a value transformed by the appropriate function. + * @return A new either, containing a value transformed by the appropriate + * function. */ - public <NewLeft, NewRight> Either<NewLeft, NewRight> map( - Function<LeftType, NewLeft> leftFunc, - Function<RightType, NewRight> rightFunc) - { - if (isLeft) return left(leftFunc.apply(leftVal)); - else return right(rightFunc.apply(rightVal)); + public <NewLeft, NewRight> Either<NewLeft, NewRight> map(Function<LeftType, NewLeft> leftFunc, + Function<RightType, NewRight> rightFunc) { + return isLeft ? left(leftFunc.apply(leftVal)) : right(rightFunc.apply(rightVal)); } - + /** * Extract the value from this Either. * - * @param <Common> The common type to extract. + * @param <Common> The common type to extract. * - * @param leftHandler The function to handle left-values. + * @param leftHandler The function to handle left-values. * @param rightHandler The function to handle right-values. * * @return The result of applying the proper function. */ - public <Common> Common extract( - Function<LeftType, Common> leftHandler, - Function<RightType, Common> rightHandler) - { - if (isLeft) return leftHandler.apply(leftVal); - else return rightHandler.apply(rightVal); + public <Common> Common extract(Function<LeftType, Common> leftHandler, Function<RightType, Common> rightHandler) { + return isLeft ? leftHandler.apply(leftVal) : rightHandler.apply(rightVal); } - + /** * Perform an action on this either. * - * @param leftHandler The handler of left values. + * @param leftHandler The handler of left values. * @param rightHandler The handler of right values. */ - public void pick( - Consumer<LeftType> leftHandler, Consumer<RightType> rightHandler) - { - if (isLeft) leftHandler.accept(leftVal); - else rightHandler.accept(rightVal); + public void pick(Consumer<LeftType> leftHandler, Consumer<RightType> rightHandler) { + if (isLeft) + leftHandler.accept(leftVal); + else + rightHandler.accept(rightVal); } /** - * Check if this either is left-aligned (has the left value filled, - * not the right value). + * Check if this either is left-aligned (has the left value filled, not the + * right value). * * @return Whether this either is left-aligned. */ @@ -140,23 +126,21 @@ public class Either<LeftType, RightType> { public Optional<LeftType> getLeft() { return Optional.ofNullable(leftVal); } - + /** - * Get the left value of this either, or get a {@link NoSuchElementException} - * if there isn't one. + * Get the left value of this either, or get a {@link NoSuchElementException} if + * there isn't one. * * @return The left value of this either. * * @throws NoSuchElementException If this either doesn't have a left value. */ public LeftType forceLeft() { - if (isLeft) - { + if (isLeft) { return leftVal; - } else - { - throw new NoSuchElementException("Either has no left value, is right value"); } + + throw new NoSuchElementException("Either has no left value, is right value"); } /** @@ -167,7 +151,7 @@ public class Either<LeftType, RightType> { public Optional<RightType> getRight() { return Optional.ofNullable(rightVal); } - + /** * Get the right value of this either, or get a {@link NoSuchElementException} * if there isn't one. @@ -177,17 +161,32 @@ public class Either<LeftType, RightType> { * @throws NoSuchElementException If this either doesn't have a right value. */ public RightType forceRight() { - if (isLeft) - { + if (isLeft) { throw new NoSuchElementException("Either has no right value, has left value"); - } else - { - return rightVal; } + + return rightVal; + } + + @SuppressWarnings("unchecked") + public <T> Either<LeftType, T> newRight() { + if (isLeft) return (Either<LeftType, T>) this; + + throw new NoSuchElementException("Can't replace right type on right Either"); } - // Misc. overrides + @SuppressWarnings("unchecked") + public <T> Either<T, RightType> newLeft() { + if (isLeft) + throw new NoSuchElementException("Can't replace left type on left Either"); + return (Either<T, RightType>) this; + } + public static <T> T collapse(Either<T, T> eth) { + return eth.extract(ID.id(), ID.id()); + } + // Misc. overrides + @Override public int hashCode() { return Objects.hash(isLeft, leftVal, rightVal); @@ -195,20 +194,21 @@ public class Either<LeftType, RightType> { @Override public boolean equals(Object obj) { - if (this == obj) return true; - if (obj == null) return false; - if (getClass() != obj.getClass()) return false; - + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + Either<?, ?> other = (Either<?, ?>) obj; - - return isLeft == other.isLeft - && Objects.equals(leftVal, other.leftVal) + + return isLeft == other.isLeft && Objects.equals(leftVal, other.leftVal) && Objects.equals(rightVal, other.rightVal); } @Override public String toString() { - return String.format("Either [leftVal='%s', rightVal='%s', isLeft=%s]", leftVal, - rightVal, isLeft); + return String.format("Either [leftVal='%s', rightVal='%s', isLeft=%s]", leftVal, rightVal, isLeft); } } diff --git a/src/main/java/bjc/functypes/CombinatorBirds.java b/src/main/java/bjc/functypes/CombinatorBirds.java new file mode 100644 index 0000000..d939a5b --- /dev/null +++ b/src/main/java/bjc/functypes/CombinatorBirds.java @@ -0,0 +1,212 @@ +package bjc.functypes; + +import java.util.function.*; + +/** + * Assorted functional combinators, named after birds as in To Mock a + * Mockingbird + * + * Consult <a href="https://blog.lahteenmaki.net/combinator-birds.html">this + * site</a> for more info + * + * @author bjcul + * + */ +@SuppressWarnings("javadoc") +public class CombinatorBirds { + // Note that a good number of these birds can be composed from others, but I've + // generally written them in the fully explicit style + + /** + * The 'idiot' or identity bird. + * + * @param <A> A type + * + * @return A function that returns its argument + */ + public static <A> UnaryOperator<A> idiot() { + return (a) -> a; + } + + public static <A, B> Function<A, B> idiotOnceRemoved(Function<A, B> func) { + return func; + } + + // should variants be written that take BiFunction or a TriFunction? + // They'd be simple to write, just a bit of a nuisance + public static <A, B, C> Function<A, Function<B, C>> idiotTwiceRemoved(Function<A, Function<B, C>> func) { + return func; + } + + // 'true' in lambda calculus + public static <A, B> Function<B, A> kestrel(A val) { + return (vl) -> val; + } + + // 'false' in lambda calculus + public static <A, B> Function<B, B> kite(A val) { + return (vl) -> vl; + } + + public static <A, B, C> Function<A, C> bluebird(Function<A, B> left, Function<B, C> right) { + return (vl) -> right.apply(left.apply(vl)); + } + + public static <A, B, C, D> Function<A, Function<B, D>> blackbird(Function<C, D> left, + Function<A, Function<B, C>> right) { + return (first) -> (second) -> left.apply(right.apply(first).apply(second)); + } + + public static <A, B, C, D, E> Function<A, Function<B, Function<C, E>>> bunting(Function<D, E> left, + Function<A, Function<B, Function<C, D>>> right) { + return (first) -> (second) -> (third) -> left.apply(right.apply(first).apply(second).apply(third)); + } + + public static <A, B, C, D> Function<A, D> becard(Function<C, D> f, Function<B, C> g, Function<A, B> h) { + return (vl) -> f.apply(g.apply(h.apply(vl))); + } + + public static <A, B, C> Function<A, C> starling(Function<A, Function<B, C>> f, Function<A, B> g) { + return (vl) -> f.apply(vl).apply(g.apply(vl)); + } + + public static <A, B, C> Function<A, C> missingBird(Function<B, Function<A, C>> f, Function<A, B> g) { + return (vl) -> f.apply(g.apply(vl)).apply(vl); + } + + public static <A, B, C, D> Function<A, D> phoenix(Function<B, Function<C, D>> f, Function<A, B> g, + Function<A, C> h) { + return (vl) -> f.apply(g.apply(vl)).apply(h.apply(vl)); + } + + public static <A, B, C, D> Function<B, D> dove(Function<A, Function<C, D>> f, A x, Function<B, C> g) { + return (vl) -> f.apply(x).apply(g.apply(vl)); + } + + public static <A, B, C, D> Function<A, Function<B, D>> cardinalPrime(Function<C, Function<A, D>> f, + Function<B, C> g) { + return (x) -> (y) -> f.apply(g.apply(y)).apply(x); + } + + public static <A, B, C, D, E> Function<B, Function<C, E>> eagle(Function<A, Function<D, E>> f, A x, + Function<B, Function<C, D>> g) { + return (y) -> (z) -> f.apply(x).apply(g.apply(y).apply(z)); + } + + public static <A, B, C, D, E> Function<C, E> dickcissel(Function<A, Function<B, Function<D, E>>> f, A x, B y, + Function<C, D> g) { + return (z) -> f.apply(x).apply(y).apply(g.apply(z)); + } + + public static <A, B, C> Function<A, Function<A, C>> psi(Function<B, Function<B, C>> f, Function<A, B> g) { + return (x1) -> (x2) -> f.apply(g.apply(x1)).apply(g.apply(x2)); + } + + public static <A, B, C, D, E> Function<B, E> dovekie(Function<C, Function<D, E>> f, Function<A, C> g, A x, + Function<B, D> h) { + return (y) -> f.apply(g.apply(x)).apply(h.apply(y)); + } + + public static <A, B, C, D, E, F, G> Function<C, Function<D, G>> baldEagle(Function<E, Function<F, G>> f, + Function<A, Function<B, E>> g, A x, B y, Function<C, Function<D, F>> h) { + return (z) -> (zz) -> f.apply(g.apply(x).apply(y)).apply(h.apply(z).apply(zz)); + } + + public static <A, B> Function<A, B> warbler(Function<A, Function<A, B>> f) { + return (x) -> f.apply(x).apply(x); + } + + public static <A, B, C> Function<A, Function<B, C>> warblerOnceRemoved(Function<A, Function<B, Function<B, C>>> f) { + return (x) -> (y) -> f.apply(x).apply(y).apply(y); + } + + public static <A, B, C, D> Function<A, Function<B, Function<C, D>>> warblerTwiceRemoved( + Function<A, Function<B, Function<C, Function<C, D>>>> f) { + return (x) -> (y) -> (z) -> f.apply(x).apply(y).apply(z).apply(z); + } + + public static <A, B, C> Function<A, Function<B, C>> hummingbird(Function<A, Function<B, Function<A, C>>> f) { + return (x) -> (y) -> f.apply(x).apply(y).apply(x); + } + + public static <A, B> Function<A, Function<B, Function<A, B>>> jay(Function<A, Function<B, B>> f) { + return (x1) -> (y) -> (x2) -> f.apply(x1).apply(f.apply(x2).apply(y)); + } + + public static <A, B, C> Function<A, Function<B, C>> jalt(Function<A, C> f) { + return (x) -> (y) -> f.apply(x); + } + + public static <A, B, C, D> Function<A, Function<B, Function<C, D>>> jaltPrime(Function<A, Function<B, D>> f) { + return (x) -> (y) -> (z) -> f.apply(x).apply(y); + } + + public static <A, B, C, D, E> Function<D, Function<E, C>> gamma( + Function<Function<A, Function<B, C>>, Function<D, Function<E, B>>> f, Function<A, Function<B, C>> g, + Function<D, A> h) { + return (dVal) -> (eVal) -> g.apply(h.apply(dVal)).apply(f.apply(g).apply(dVal).apply(eVal)); + } + + // Mockingbird/Lark aren't expressible easily in a static language + + public static <A, B> Function<Function<A, B>, B> owl(Function<Function<A, B>, A> f) { + return (g) -> g.apply(f.apply(g)); + } + + // Sage has the type-signature (a -> a) -> a, but we need to introduce the + // Supplier to add necessary laziness. Otherwise we'd recur without end + public static <A> A sage(Function<Supplier<A>, A> f) { + return f.apply(() -> sage(f)); + } + + public static <A, B, C, D> Function<A, Function<B, D>> goldfinch(Function<B, Function<C, D>> f, Function<A, C> h) { + return (x) -> (y) -> f.apply(y).apply(h.apply(x)); + } + + public static <A, B> Function<Function<A, B>, B> thrush(A x) { + return (f) -> f.apply(x); + } + + public static <A, B, C> Function<B, Function<A, C>> cardinal(Function<A, Function<B, C>> f) { + return (y) -> (x) -> f.apply(x).apply(y); + } + + public static <A, B, C> Function<Function<A, Function<B, C>>, C> finch(B y, A x) { + return (f) -> f.apply(x).apply(y); + } + + public static <A, B, C> Function<A, C> robin(B y, Function<A, Function<B, C>> f) { + return (x) -> f.apply(x).apply(y); + } + + // the merit of having these instead of just a general 'swap' combinator is a + // bit questionable + public static <A, B, C> Function<Function<A, Function<B, C>>, C> vireo(A x, B y) { + return (f) -> f.apply(x).apply(y); + } + + // not written here: robin/finch/cardinal/vireo once/twice removed + public static <A, B, C> Function<A, C> queer(Function<A, B> f, Function<B, C> g) { + return (x) -> g.apply(f.apply(x)); + } + + public static <A, B, C> Function<Function<A, B>, C> quixotic(Function<B, C> f, A x) { + return (g) -> f.apply(g.apply(x)); + } + + public static <A, B, C> Function<Function<A, B>, C> quizzical(A x, Function<B, C> f) { + return (g) -> f.apply(g.apply(x)); + } + + public static <A, B, C> Function<Function<B, C>, C> quirky(Function<A, B> f, A x) { + return (g) -> g.apply(f.apply(x)); + } + + public static <A, B, C> Function<Function<B, C>, C> quacky(A x, Function<A, B> f) { + return (g) -> g.apply(f.apply(x)); + } + + public static <A, B> Function<Function<A, Function<A, B>>, B> converseWarbler(A x) { + return (f) -> f.apply(x).apply(x); + } +} diff --git a/src/main/java/bjc/functypes/Combinators.java b/src/main/java/bjc/functypes/Combinators.java index fde13df..416230b 100644 --- a/src/main/java/bjc/functypes/Combinators.java +++ b/src/main/java/bjc/functypes/Combinators.java @@ -17,281 +17,276 @@ public class Combinators { /** * If-then-else combinator. * - * @param <Input> The input type. + * @param <Input> The input type. * @param <Output> The output type. * - * @param in The predicate to run. - * @param ifTrue The condition to run when it is true. - * @param ifFalse The condition to run when it is false. + * @param in The predicate to run. + * @param ifTrue The condition to run when it is true. + * @param ifFalse The condition to run when it is false. * - * @return A function which will invoke one or the other action, based on the predicate. + * @return A function which will invoke one or the other action, based on the + * predicate. */ @SuppressWarnings("unused") - public static <Input, Output> Function<Input, Output> iftt( - Predicate<Input> in, - Function<Input, Output> ifTrue, - Function<Input, Output> ifFalse) - { + public static <Input, Output> Function<Input, Output> iftt(Predicate<Input> in, Function<Input, Output> ifTrue, + Function<Input, Output> ifFalse) { return arg -> in.test(arg) ? ifTrue.apply(arg) : ifFalse.apply(arg); } - + /** * If-then-else expression. * * @param <Output> The output type. * - * @param in The boolean to use. - * @param ifTrue The condition to run when it is true. - * @param ifFalse The condition to run when it is false. + * @param in The boolean to use. + * @param ifTrue The condition to run when it is true. + * @param ifFalse The condition to run when it is false. * * @return A value, based on the provided parameter */ - public static <Output> Output iftt( - boolean in, - Supplier<Output> ifTrue, - Supplier<Output> ifFalse) - { + public static <Output> Output iftt(boolean in, Supplier<Output> ifTrue, Supplier<Output> ifFalse) { return in ? ifTrue.get() : ifFalse.get(); } - + /** * Execute an action before calling a function. * - * @param <Input> The input to the function. + * @param <Input> The input to the function. * @param <Output> The output from the function. * - * @param action The action to run on the input. + * @param action The action to run on the input. * @param terminal The function to call. * * @return A function that runs the provided action before calling the function. */ - public static <Input, Output> Function<Input, Output> beforeThis( - Consumer<Input> action, - Function<Input, Output> terminal) - { + public static <Input, Output> Function<Input, Output> beforeThis(Consumer<Input> action, + Function<Input, Output> terminal) { return (arg) -> { action.accept(arg); return terminal.apply(arg); }; } - + /** * Execute an action after calling a function. * - * @param <Input> The input to the function. + * @param <Input> The input to the function. * @param <Output> The output to the function. * - * @param initial The function to call. - * @param action The action to call after doing the function. + * @param initial The function to call. + * @param action The action to call after doing the function. * * @return A function that calls the provided action after the function. */ - public static <Input, Output> Consumer<Input> andThen( - Function<Input, Output> initial, - Consumer<Output> action) - { + public static <Input, Output> Consumer<Input> andThen(Function<Input, Output> initial, Consumer<Output> action) { return (arg) -> action.accept(initial.apply(arg)); } - + /** * Standalone function composer. * - * @param <Left> The input type of the initial function. + * @param <Left> The input type of the initial function. * @param <Middle> The shared input/output type. - * @param <Right> The output type of the terminal function. + * @param <Right> The output type of the terminal function. * - * @param initial The first function to call. + * @param initial The first function to call. * @param terminal The second function to call. * * @return A function that composes the provided functions together. */ - public static <Left, Middle, Right> Function<Left, Right> compose( - Function<Left, Middle> initial, - Function<Middle, Right> terminal) - { + public static <Left, Middle, Right> Function<Left, Right> compose(Function<Left, Middle> initial, + Function<Middle, Right> terminal) { return (arg) -> terminal.apply(initial.apply(arg)); } - + /** * Execute a function with some internal state. * - * @param <Input> The input type of the function. + * @param <Input> The input type of the function. * @param <Output> The output type of the function. - * @param <State> The type of the internal state. + * @param <State> The type of the internal state. * - * @param source The function which provides internal state. - * @param action The function to call. + * @param source The function which provides internal state. + * @param action The function to call. * * @return A function which hides the production of the internal state. */ - public static <Input, Output, State> Function<Input, Output> introducing( - Supplier<State> source, - BiFunction<State, Input, Output> action) - { + public static <Input, Output, State> Function<Input, Output> introducing(Supplier<State> source, + BiFunction<State, Input, Output> action) { return (input) -> action.apply(source.get(), input); } - + /** * Invoke a given function with null to produce its result. * - * @param <Input> The input type of the function. + * @param <Input> The input type of the function. * @param <Output> The output type of the function. * - * @param action The function to invoke. + * @param action The function to invoke. * * @return The result of calling the function with null. */ - public static <Input, Output> Output invoke(Function<Input, Output> action) - { + public static <Input, Output> Output invoke(Function<Input, Output> action) { return action.apply(null); } - + /** * Execute an action a given number of times. * - * @param times The number of times to execute the action. + * @param times The number of times to execute the action. * @param action The action to execute. */ - public static void times(int times, Consumer<Integer> action) - { - for (int i = 0; i < times; i++) action.accept(i); + public static void times(int times, Consumer<Integer> action) { + for (int i = 0; i < times; i++) + action.accept(i); } - + + /** + * Construct a function that applies the given operator a fixed number of times. + * + * @param <T> The type of the data + * + * @param times The number of times to execute + * @param func The function to apply + * + * @return A function that applies the given one the specified number of times. + */ + public static <T> UnaryOperator<T> times(int times, UnaryOperator<T> func) { + return (val) -> { + T retVal = val; + for (int i = 0; i < times; i++) + retVal = func.apply(retVal); + return retVal; + }; + } + /** * Invoke a wrapper instead of invoking a normal function. * - * @param <Input> The input type to the function. + * @param <Input> The input type to the function. * @param <Output> The output type to the function. * * @param function The function to apply. - * @param wrapper The wrapper around a function. + * @param wrapper The wrapper around a function. * * @return A function that invokes the wrapper instead. */ - public static <Input, Output> Function<Input, Output> around( - Function<Input, Output> function, - BiFunction<Input, Function<Input, Output>, Output> wrapper) - { + public static <Input, Output> Function<Input, Output> around(Function<Input, Output> function, + BiFunction<Input, Function<Input, Output>, Output> wrapper) { return (input) -> wrapper.apply(input, function); } - + /** * Only run a given function when the argument satisfies a condition. * - * @param <Input> The input type of the function. + * @param <Input> The input type of the function. * @param <Output> The output type of the function. * * @param function The function to run. - * @param guard The guard to use for checking the input. + * @param guard The guard to use for checking the input. * - * @return A function which takes the given input, and only calls the - * function if the guard returns true. Otherwise, it will return - * an empty optional. + * @return A function which takes the given input, and only calls the function + * if the guard returns true. Otherwise, it will return an empty + * optional. */ - public static <Input, Output> Function<Input, Optional<Output>> provided( - Function<Input, Output> function, - Predicate<Input> guard) - { - return iftt(guard, - (arg) -> Optional.ofNullable(function.apply(arg)), - (ignored) -> Optional.empty() - ); + public static <Input, Output> Function<Input, Optional<Output>> provided(Function<Input, Output> function, + Predicate<Input> guard) { + return iftt(guard, (arg) -> Optional.ofNullable(function.apply(arg)), (ignored) -> Optional.empty()); } - + /** * Concatenate two functions together, so that they run on the same argument. * - * @param <Input> The type of the input. + * @param <Input> The type of the input. * @param <Output1> The type of the first output. * @param <Output2> The type of the second output. * - * @param funcA The first function to call. - * @param funcB The second function to call. + * @param funcA The first function to call. + * @param funcB The second function to call. * * @return A function that returns a pair of the results of calling both * functions. */ - public static - <Input, Output1, Output2> Function<Input, Pair<Output1, Output2>> - concat(Function<Input, Output1> funcA, Function<Input, Output2> funcB) - { + public static <Input, Output1, Output2> Function<Input, Pair<Output1, Output2>> concat( + Function<Input, Output1> funcA, Function<Input, Output2> funcB) { return (arg) -> Pair.pair(funcA.apply(arg), funcB.apply(arg)); } - + /** * Concatenate a series of functions together, returning a list of their * results. * - * @param <Input> The input type for the functions. + * @param <Input> The input type for the functions. * @param <Output> The output type for the functions. * - * @param funcs The series of functions to call. + * @param funcs The series of functions to call. * - * @return A function that calls each of those functions, and returns a - * list of their results. + * @return A function that calls each of those functions, and returns a list of + * their results. */ @SafeVarargs - public static - <Input, Output> Function<Input, List<Output>> - concat(Function<Input, Output>... funcs) - { + public static <Input, Output> Function<Input, List<Output>> concat(Function<Input, Output>... funcs) { List<Function<Input, Output>> funcList = Arrays.asList(funcs); - + // Kind of a nuisance that Java can't properly guess the type of // our mapper function, but oh well. - - return (arg) -> - funcList.stream() - .map((Function<Function<Input, Output>, Output>) - (func) -> func.apply(arg)) - .collect(toList()); + + return (arg) -> funcList.stream().map((Function<Function<Input, Output>, Output>) (func) -> func.apply(arg)) + .collect(toList()); } - + /** * Return a function that does a series of actions upon a value, then returns * that value. * - * @param <Type> The type given as an argument + * @param <Type> The type given as an argument * * @param consumers The actions to perform on the value. * * @return A function that performs those arguments on a value. */ @SafeVarargs - public static <Type> Function<Type, Type> doWith(Consumer<Type>... consumers) - { + public static <Type> Function<Type, Type> doWith(Consumer<Type>... consumers) { return (arg) -> { - for (Consumer<Type> consumer : consumers) consumer.accept(arg); + for (Consumer<Type> consumer : consumers) + consumer.accept(arg); return arg; }; } - + /** * Perform a series of actions upon a value, then return that value. * - * @param <Type> The type given as an argument + * @param <Type> The type given as an argument * - * @param input The value to use. + * @param input The value to use. * @param consumers The actions to perform on the value. * * @return A function that performs those arguments on a value. */ @SafeVarargs - public static <Type> Type with(Type input, Consumer<Type>... consumers) - { + public static <Type> Type with(Type input, Consumer<Type>... consumers) { return doWith(consumers).apply(input); } - + /** * Convert a function into a consumer, ignoring its output. * * @param <Input> The input to the function. * - * @param func The function to convert. + * @param func The function to convert. * * @return A consumer which calls the function, and ignores the output. */ public static <Input> Consumer<Input> ignore(Function<Input, ?> func) { return (inp) -> func.apply(inp); } + + public static <Input, Output> Function<Supplier<Input>, Output> lazify(Function<Input, Output> f) { + return (supp) -> f.apply(supp.get()); + } + + public static <Input, Output> Function<Input, Output> strictify(Function<Supplier<Input>, Output> f) { + return (val) -> f.apply(() -> val); + } }
\ No newline at end of file diff --git a/src/main/java/bjc/functypes/ElseFunction.java b/src/main/java/bjc/functypes/ElseFunction.java new file mode 100644 index 0000000..b37fba8 --- /dev/null +++ b/src/main/java/bjc/functypes/ElseFunction.java @@ -0,0 +1,68 @@ +package bjc.functypes; + +import java.util.Optional; +import java.util.function.Consumer; +import java.util.function.Function; + +/** + * 'Grammar' type used for making certain types of conditional processing read + * easier. + * + * @author bjcul + * + * @param <Input> The input type + * @param <Output> The output type + */ +@FunctionalInterface +public interface ElseFunction<Input, Output> extends Function<Input, Output> { + /** + * Apply the alternative. + * + * Defaults to just applying this function + * + * @param x The input + * + * @return The result of applying the function + */ + public default Output elseApply(Input x) { + return apply(x); + } + + /** + * Handle a value present in an optional. + * + * @param <Input> The type contained in the optional. + * + * @param val The optional + * + * @return A function for processing the optional + */ + public static <Input> ElseFunction<Consumer<Input>, ElseFunction<Runnable, Void>> ifPresent(Optional<Input> val) { + return (f) -> (g) -> { + if (val.isPresent()) + f.accept(val.get()); + else + g.run(); + return null; + }; + } + + /** + * Handle a value not present in an optional. + * + * @param <Input> The type contained in the optional. + * + * @param val The optional + * + * @return A function for processing the optional + */ + public static <Input> ElseFunction<Runnable, ElseFunction<Consumer<Input>, Void>> ifNotPresent(Optional<Input> val) { + return (f) -> (g) -> { + if (val.isPresent()) + g.accept(val.get()); + else + f.run(); + return null; + }; + } +} diff --git a/src/main/java/bjc/functypes/ForEach.java b/src/main/java/bjc/functypes/ForEach.java new file mode 100644 index 0000000..5c39298 --- /dev/null +++ b/src/main/java/bjc/functypes/ForEach.java @@ -0,0 +1,115 @@ +package bjc.functypes; + +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Map.Entry; +import java.util.concurrent.*; +import java.util.function.BiFunction; + +/** + * Parallel forEach + * + * @author bjcul + * + * @param <Output> The type output by each iteration + * @param <Index> The type indexing each iteration + * @param <Input> The type fed to each iteration + */ +public abstract class ForEach<Output, Index, Input> { + protected static final Exception CONTINUE = new Exception("forEach Continue"); + // we don't actually care about the stack trace + static { + CONTINUE.setStackTrace(new StackTraceElement[0]); + }; + + /** + * Run a single instance of the loop + * + * Throw CONTINUE to cancel the instance without yielding a value + * + * @param idx The index for the loop + * @param in The value to process for the loop + * @return The output of the loop + * + * @throws Exception If something goes wrong in the loop. + */ + public abstract Output block(Index idx, Input in) throws Exception; + + Callable<Output> loop(Index idx, Input in) { + return () -> block(idx, in); + } + + /** + * Create a ForEach from a function + * + * @param <Output> The type output by each iteration + * @param <Index> The type indexing each iteration + * @param <Input> The type fed to each iteration + * + * @param func The function for an iteration + * + * @return A ForEach backed by the given function + */ + public static <Output, Index, Input> ForEach<Output, Index, Input> from(BiFunction<Index, Input, Output> func) { + return new FunctionalForEach<>(func); + } + + /** + * Execute a parallel forEach. + * + * @param <Output> The type output by each iteration + * @param <Index> The type indexing each iteration + * @param <Input> The type fed to each iteration + * + * @param pool The concurrent executor + * @param in The collection to iterate over + * @param block The block to run an iteration + * + * @return A map representing the results of each iteration. + */ + public static <Output, Index, Input> LinkedHashMap<Index, Output> forEach(ExecutorService pool, + Map<Index, Input> in, ForEach<Output, Index, Input> block) { + int size = in.size(); + + final Map<Index, Future<Output>> futures = new LinkedHashMap<>(size); + for (Entry<Index, Input> entry : in.entrySet()) { + Index key = entry.getKey(); + futures.put(key, pool.submit(block.loop(key, entry.getValue()))); + } + final LinkedHashMap<Index, Output> results = new LinkedHashMap<>(size); + for (Entry<Index, Future<Output>> entry : futures.entrySet()) { + Index key = entry.getKey(); + try { + results.put(key, entry.getValue().get()); + } catch (ExecutionException eex) { + Throwable cause = eex.getCause(); + if (cause != CONTINUE) { + // CONTINUE is used to exit a given loop, so ignore it + futures.values().forEach((f) -> f.cancel(true)); + throw new IllegalStateException( + "Exception thrown evaluating forEach index " + key + " of value " + in.get(key), cause); + } + } catch (CancellationException cex) { + // Ignore these + } catch (InterruptedException iex) { + // Carryover interrupt + Thread.currentThread().interrupt(); + } + } + + return results; + } +} + +final class FunctionalForEach<Output, Index, Input> extends ForEach<Output, Index, Input> { + private final BiFunction<Index, Input, Output> func; + + FunctionalForEach(BiFunction<Index, Input, Output> func) { + this.func = func; + } + + @Override + public Output block(Index idx, Input in) throws Exception { + return func.apply(idx, in); + } +} diff --git a/src/main/java/bjc/functypes/TriFunction.java b/src/main/java/bjc/functypes/TriFunction.java new file mode 100644 index 0000000..e93e32d --- /dev/null +++ b/src/main/java/bjc/functypes/TriFunction.java @@ -0,0 +1,91 @@ +package bjc.functypes; + +import java.util.function.BiFunction; +import java.util.function.Function; + +/** + * Represents a function which takes 3 input values. + * + * @author bjcul + * + * @param <In1> The first input type + * @param <In2> The second input type + * @param <In3> The third input type + * @param <Out> The output type + */ +@FunctionalInterface +public interface TriFunction<In1, In2, In3, Out> { + /** + * Apply this function. + * + * @param x The first input + * @param y The second input + * @param z The third input + * + * @return The output + */ + public Out apply(In1 x, In2 y, In3 z); + + // TODO: come up with some way to implement a uncurry that won't stack on top of a curry + + /** + * Curry the first argument for this function. + * + * @return The function with the first argument curried. + */ + public default Function<In1, BiFunction<In2, In3, Out>> curryFirst() { + return (x) -> (y, z) -> apply(x, y, z); + } + + /** + * Curry the third argument for this function. + * + * @return The function with the third argument curried. + */ + public default BiFunction<In1, In2, Function<In3, Out>> curryLast() { + return (x, y) -> (z) -> apply(x, y, z); + } + + /** + * Fully curry this function + * + * @return The fully curried function + */ + public default Function<In1, Function<In2, Function<In3, Out>>> fullCurry() { + return (x) -> (y) -> (z) -> apply(x, y, z); + } + + /** + * Partially apply the third argument. + * + * @param z The value for the third argument + * + * @return The function w/ the third argument partially applied + */ + public default BiFunction<In1, In2, Out> partialLast(In3 z) { + return (x, y) -> apply(x, y, z); + } + + public default BiFunction<In1, In3, Out> partialMiddle(In2 y) { + return (x, z) -> apply(x, y, z); + } + + public default BiFunction<In2, In3, Out> partialFirst(In1 x) { + return (y, z) -> apply(x, y, z); + } + + /** + * Apply a transform to the output of this function + * + * @param <Out2> The new output type + * + * @param f The mapping function + * + * @return The function with its outputs transformed + */ + public default <Out2> TriFunction<In1, In2, In3, Out2> mapOutput(Function<Out, Out2> f) { + return (x, y, z) -> f.apply(apply(x, y, z)); + } + // Possible additions: partial applications which take a Supplier, functions to + // determine one/two of the args based on the others, functions to map one of the args +} diff --git a/src/main/java/bjc/functypes/Unit.java b/src/main/java/bjc/functypes/Unit.java new file mode 100644 index 0000000..75bdf62 --- /dev/null +++ b/src/main/java/bjc/functypes/Unit.java @@ -0,0 +1,17 @@ +package bjc.functypes; + +/** + * The class that exists, but does nothing else. + * @author bjcul + * + */ +public class Unit { + /** + * The single instance of Unit that exists + */ + public static final Unit UNIT = new Unit(); + + private Unit() { + // Empty + } +} |
