From f7d96d88798c904f69f0b5306a5ddecd0456b377 Mon Sep 17 00:00:00 2001 From: Ben Culkin Date: Sat, 24 Sep 2022 12:34:30 -0400 Subject: Add some additional combinators, of various sorts --- src/main/java/bjc/data/Either.java | 144 +++++++------- src/main/java/bjc/functypes/CombinatorBirds.java | 212 +++++++++++++++++++++ src/main/java/bjc/functypes/Combinators.java | 231 +++++++++++------------ src/main/java/bjc/functypes/ElseFunction.java | 68 +++++++ src/main/java/bjc/functypes/ForEach.java | 115 +++++++++++ src/main/java/bjc/functypes/TriFunction.java | 91 +++++++++ src/main/java/bjc/functypes/Unit.java | 17 ++ 7 files changed, 688 insertions(+), 190 deletions(-) create mode 100644 src/main/java/bjc/functypes/CombinatorBirds.java create mode 100644 src/main/java/bjc/functypes/ElseFunction.java create mode 100644 src/main/java/bjc/functypes/ForEach.java create mode 100644 src/main/java/bjc/functypes/TriFunction.java create mode 100644 src/main/java/bjc/functypes/Unit.java (limited to 'src') 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 - * The type that could be on the left. + * @param The type that could be on the left. * - * @param - * The type that could be on the right. + * @param The type that could be on the right. * */ public class Either { /** * Create a new either with the left value occupied. * - * @param - * The type of the left value. + * @param The type of the left value. * - * @param - * The type of the empty right value. + * @param 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 Either - left(final LeftType left) { + public static Either left(final LeftType left) { return new Either<>(left, null); } /** * Create a new either with the right value occupied. * - * @param - * The type of the empty left value. + * @param The type of the empty left value. * - * @param - * The type of the right value. + * @param 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 Either - right(final RightType right) { + public static Either right(final RightType right) { return new Either<>(null, right); } @@ -75,56 +67,50 @@ public class Either { /** * Perform a mapping over this either. * - * @param The new left type. + * @param The new left type. * @param 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 Either map( - Function leftFunc, - Function rightFunc) - { - if (isLeft) return left(leftFunc.apply(leftVal)); - else return right(rightFunc.apply(rightVal)); + public Either map(Function leftFunc, + Function rightFunc) { + return isLeft ? left(leftFunc.apply(leftVal)) : right(rightFunc.apply(rightVal)); } - + /** * Extract the value from this Either. * - * @param The common type to extract. + * @param 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 extract( - Function leftHandler, - Function rightHandler) - { - if (isLeft) return leftHandler.apply(leftVal); - else return rightHandler.apply(rightVal); + public Common extract(Function leftHandler, Function 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 leftHandler, Consumer rightHandler) - { - if (isLeft) leftHandler.accept(leftVal); - else rightHandler.accept(rightVal); + public void pick(Consumer leftHandler, Consumer 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 { public Optional 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 { public Optional 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 { * @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 Either newRight() { + if (isLeft) return (Either) this; + + throw new NoSuchElementException("Can't replace right type on right Either"); } - // Misc. overrides + @SuppressWarnings("unchecked") + public Either newLeft() { + if (isLeft) + throw new NoSuchElementException("Can't replace left type on left Either"); + return (Either) this; + } + public static T collapse(Either 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 { @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 this + * site 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 type + * + * @return A function that returns its argument + */ + public static UnaryOperator idiot() { + return (a) -> a; + } + + public static Function idiotOnceRemoved(Function 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 Function> idiotTwiceRemoved(Function> func) { + return func; + } + + // 'true' in lambda calculus + public static Function kestrel(A val) { + return (vl) -> val; + } + + // 'false' in lambda calculus + public static Function kite(A val) { + return (vl) -> vl; + } + + public static Function bluebird(Function left, Function right) { + return (vl) -> right.apply(left.apply(vl)); + } + + public static Function> blackbird(Function left, + Function> right) { + return (first) -> (second) -> left.apply(right.apply(first).apply(second)); + } + + public static Function>> bunting(Function left, + Function>> right) { + return (first) -> (second) -> (third) -> left.apply(right.apply(first).apply(second).apply(third)); + } + + public static Function becard(Function f, Function g, Function h) { + return (vl) -> f.apply(g.apply(h.apply(vl))); + } + + public static Function starling(Function> f, Function g) { + return (vl) -> f.apply(vl).apply(g.apply(vl)); + } + + public static Function missingBird(Function> f, Function g) { + return (vl) -> f.apply(g.apply(vl)).apply(vl); + } + + public static Function phoenix(Function> f, Function g, + Function h) { + return (vl) -> f.apply(g.apply(vl)).apply(h.apply(vl)); + } + + public static Function dove(Function> f, A x, Function g) { + return (vl) -> f.apply(x).apply(g.apply(vl)); + } + + public static Function> cardinalPrime(Function> f, + Function g) { + return (x) -> (y) -> f.apply(g.apply(y)).apply(x); + } + + public static Function> eagle(Function> f, A x, + Function> g) { + return (y) -> (z) -> f.apply(x).apply(g.apply(y).apply(z)); + } + + public static Function dickcissel(Function>> f, A x, B y, + Function g) { + return (z) -> f.apply(x).apply(y).apply(g.apply(z)); + } + + public static Function> psi(Function> f, Function g) { + return (x1) -> (x2) -> f.apply(g.apply(x1)).apply(g.apply(x2)); + } + + public static Function dovekie(Function> f, Function g, A x, + Function h) { + return (y) -> f.apply(g.apply(x)).apply(h.apply(y)); + } + + public static Function> baldEagle(Function> f, + Function> g, A x, B y, Function> h) { + return (z) -> (zz) -> f.apply(g.apply(x).apply(y)).apply(h.apply(z).apply(zz)); + } + + public static Function warbler(Function> f) { + return (x) -> f.apply(x).apply(x); + } + + public static Function> warblerOnceRemoved(Function>> f) { + return (x) -> (y) -> f.apply(x).apply(y).apply(y); + } + + public static Function>> warblerTwiceRemoved( + Function>>> f) { + return (x) -> (y) -> (z) -> f.apply(x).apply(y).apply(z).apply(z); + } + + public static Function> hummingbird(Function>> f) { + return (x) -> (y) -> f.apply(x).apply(y).apply(x); + } + + public static Function>> jay(Function> f) { + return (x1) -> (y) -> (x2) -> f.apply(x1).apply(f.apply(x2).apply(y)); + } + + public static Function> jalt(Function f) { + return (x) -> (y) -> f.apply(x); + } + + public static Function>> jaltPrime(Function> f) { + return (x) -> (y) -> (z) -> f.apply(x).apply(y); + } + + public static Function> gamma( + Function>, Function>> f, Function> g, + Function 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 Function, B> owl(Function, 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 sage(Function, A> f) { + return f.apply(() -> sage(f)); + } + + public static Function> goldfinch(Function> f, Function h) { + return (x) -> (y) -> f.apply(y).apply(h.apply(x)); + } + + public static Function, B> thrush(A x) { + return (f) -> f.apply(x); + } + + public static Function> cardinal(Function> f) { + return (y) -> (x) -> f.apply(x).apply(y); + } + + public static Function>, C> finch(B y, A x) { + return (f) -> f.apply(x).apply(y); + } + + public static Function robin(B y, Function> 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 Function>, 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 Function queer(Function f, Function g) { + return (x) -> g.apply(f.apply(x)); + } + + public static Function, C> quixotic(Function f, A x) { + return (g) -> f.apply(g.apply(x)); + } + + public static Function, C> quizzical(A x, Function f) { + return (g) -> f.apply(g.apply(x)); + } + + public static Function, C> quirky(Function f, A x) { + return (g) -> g.apply(f.apply(x)); + } + + public static Function, C> quacky(A x, Function f) { + return (g) -> g.apply(f.apply(x)); + } + + public static Function>, 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 The input type. + * @param The input type. * @param 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 Function iftt( - Predicate in, - Function ifTrue, - Function ifFalse) - { + public static Function iftt(Predicate in, Function ifTrue, + Function ifFalse) { return arg -> in.test(arg) ? ifTrue.apply(arg) : ifFalse.apply(arg); } - + /** * If-then-else expression. * * @param 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 iftt( - boolean in, - Supplier ifTrue, - Supplier ifFalse) - { + public static Output iftt(boolean in, Supplier ifTrue, Supplier ifFalse) { return in ? ifTrue.get() : ifFalse.get(); } - + /** * Execute an action before calling a function. * - * @param The input to the function. + * @param The input to the function. * @param 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 Function beforeThis( - Consumer action, - Function terminal) - { + public static Function beforeThis(Consumer action, + Function terminal) { return (arg) -> { action.accept(arg); return terminal.apply(arg); }; } - + /** * Execute an action after calling a function. * - * @param The input to the function. + * @param The input to the function. * @param 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 Consumer andThen( - Function initial, - Consumer action) - { + public static Consumer andThen(Function initial, Consumer action) { return (arg) -> action.accept(initial.apply(arg)); } - + /** * Standalone function composer. * - * @param The input type of the initial function. + * @param The input type of the initial function. * @param The shared input/output type. - * @param The output type of the terminal function. + * @param 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 Function compose( - Function initial, - Function terminal) - { + public static Function compose(Function initial, + Function terminal) { return (arg) -> terminal.apply(initial.apply(arg)); } - + /** * Execute a function with some internal state. * - * @param The input type of the function. + * @param The input type of the function. * @param The output type of the function. - * @param The type of the internal state. + * @param 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 Function introducing( - Supplier source, - BiFunction action) - { + public static Function introducing(Supplier source, + BiFunction action) { return (input) -> action.apply(source.get(), input); } - + /** * Invoke a given function with null to produce its result. * - * @param The input type of the function. + * @param The input type of the function. * @param 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 Output invoke(Function action) - { + public static Output invoke(Function 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 action) - { - for (int i = 0; i < times; i++) action.accept(i); + public static void times(int times, Consumer 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 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 UnaryOperator times(int times, UnaryOperator 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 The input type to the function. + * @param The input type to the function. * @param 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 Function around( - Function function, - BiFunction, Output> wrapper) - { + public static Function around(Function function, + BiFunction, Output> wrapper) { return (input) -> wrapper.apply(input, function); } - + /** * Only run a given function when the argument satisfies a condition. * - * @param The input type of the function. + * @param The input type of the function. * @param 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 Function> provided( - Function function, - Predicate guard) - { - return iftt(guard, - (arg) -> Optional.ofNullable(function.apply(arg)), - (ignored) -> Optional.empty() - ); + public static Function> provided(Function function, + Predicate 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 The type of the input. + * @param The type of the input. * @param The type of the first output. * @param 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 - Function> - concat(Function funcA, Function funcB) - { + public static Function> concat( + Function funcA, Function funcB) { return (arg) -> Pair.pair(funcA.apply(arg), funcB.apply(arg)); } - + /** * Concatenate a series of functions together, returning a list of their * results. * - * @param The input type for the functions. + * @param The input type for the functions. * @param 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 - Function> - concat(Function... funcs) - { + public static Function> concat(Function... funcs) { List> 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, Output>) - (func) -> func.apply(arg)) - .collect(toList()); + + return (arg) -> funcList.stream().map((Function, Output>) (func) -> func.apply(arg)) + .collect(toList()); } - + /** * Return a function that does a series of actions upon a value, then returns * that value. * - * @param The type given as an argument + * @param 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 Function doWith(Consumer... consumers) - { + public static Function doWith(Consumer... consumers) { return (arg) -> { - for (Consumer consumer : consumers) consumer.accept(arg); + for (Consumer consumer : consumers) + consumer.accept(arg); return arg; }; } - + /** * Perform a series of actions upon a value, then return that value. * - * @param The type given as an argument + * @param 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 with(Type input, Consumer... consumers) - { + public static Type with(Type input, Consumer... consumers) { return doWith(consumers).apply(input); } - + /** * Convert a function into a consumer, ignoring its output. * * @param 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 Consumer ignore(Function func) { return (inp) -> func.apply(inp); } + + public static Function, Output> lazify(Function f) { + return (supp) -> f.apply(supp.get()); + } + + public static Function strictify(Function, 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 The input type + * @param The output type + */ +@FunctionalInterface +public interface ElseFunction extends Function { + /** + * 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 The type contained in the optional. + * + * @param val The optional + * + * @return A function for processing the optional + */ + public static ElseFunction, ElseFunction> ifPresent(Optional 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 The type contained in the optional. + * + * @param val The optional + * + * @return A function for processing the optional + */ + public static ElseFunction, Void>> ifNotPresent(Optional 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 The type output by each iteration + * @param The type indexing each iteration + * @param The type fed to each iteration + */ +public abstract class ForEach { + 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 loop(Index idx, Input in) { + return () -> block(idx, in); + } + + /** + * Create a ForEach from a function + * + * @param The type output by each iteration + * @param The type indexing each iteration + * @param The type fed to each iteration + * + * @param func The function for an iteration + * + * @return A ForEach backed by the given function + */ + public static ForEach from(BiFunction func) { + return new FunctionalForEach<>(func); + } + + /** + * Execute a parallel forEach. + * + * @param The type output by each iteration + * @param The type indexing each iteration + * @param 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 LinkedHashMap forEach(ExecutorService pool, + Map in, ForEach block) { + int size = in.size(); + + final Map> futures = new LinkedHashMap<>(size); + for (Entry entry : in.entrySet()) { + Index key = entry.getKey(); + futures.put(key, pool.submit(block.loop(key, entry.getValue()))); + } + final LinkedHashMap results = new LinkedHashMap<>(size); + for (Entry> 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 extends ForEach { + private final BiFunction func; + + FunctionalForEach(BiFunction 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 The first input type + * @param The second input type + * @param The third input type + * @param The output type + */ +@FunctionalInterface +public interface TriFunction { + /** + * 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> 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> curryLast() { + return (x, y) -> (z) -> apply(x, y, z); + } + + /** + * Fully curry this function + * + * @return The fully curried function + */ + public default Function>> 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 partialLast(In3 z) { + return (x, y) -> apply(x, y, z); + } + + public default BiFunction partialMiddle(In2 y) { + return (x, z) -> apply(x, y, z); + } + + public default BiFunction partialFirst(In1 x) { + return (y, z) -> apply(x, y, z); + } + + /** + * Apply a transform to the output of this function + * + * @param The new output type + * + * @param f The mapping function + * + * @return The function with its outputs transformed + */ + public default TriFunction mapOutput(Function 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 + } +} -- cgit v1.2.3