import java.math.BigInteger; import java.util.Arrays; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.function.Function; import java.util.function.BiFunction; import java.util.stream.Collectors; @FunctionalInterface interface VarargFunction { public OUTPUT apply(List input); public default OUTPUT apply() { return apply(Collections.emptyList()); } public default OUTPUT apply(INPUT input) { return apply(Collections.singletonList(input)); } public default OUTPUT apply(INPUT input, INPUT input2) { return apply(Arrays.asList(input, input2)); } public default OUTPUT apply(INPUT input, INPUT input2, INPUT input3) { return apply(Arrays.asList(input, input2, input3)); } public default OUTPUT apply(Class type, Object... input) { List i = Collections.checkedList(new ArrayList<>(), type); for (Object object : input) { i.add(type.cast(object)); } return apply(i); } public default VarargFunction compose( VarargFunction after) { return input -> after.apply(apply(input)); } public default Function toFunction() { return input -> apply(input); } public default BiFunction toBiFunction() { return (input, input2) -> apply(input, input2); } public default VarargFunction transformArguments(Function transformer) { return input -> apply(input.parallelStream().map(transformer).collect(Collectors.toList())); } } @FunctionalInterface interface SelfApplicable { OUTPUT apply(SelfApplicable input); } class Utils { public static T input( List input, int index) { return input.size() > index ? input.get(index) : null; } /* Based on https://gist.github.com/aruld/3965968/#comment-604392 */ public static SelfApplicable, Function>, Function>> y(Class input, Class output) { return y -> f -> x -> f.apply(y.apply(y).apply(f)).apply(x); } public static Function, Function>, Function> fix(Class input, Class output) { return y(input, output).apply(y(input, output)); } public static SelfApplicable, VarargFunction>, VarargFunction>> yVararg(Class input, Class output) { return y -> f -> x -> f.apply(y.apply(y).apply(f)).apply(x); } public static Function, VarargFunction>, VarargFunction> fixVararg(Class input, Class output) { return yVararg(input, output).apply(yVararg(input, output)); } public static VarargFunction toVarargFunction(Function function) { return input -> function.apply(Utils.input(input, 0)); } public static VarargFunction toVarargFunction(BiFunction function) { return input -> function.apply(Utils.input(input, 0), Utils.input(input, 1)); } } public class Y { public static final BigInteger TWO = BigInteger.ONE.add(BigInteger.ONE); public static final Function toBigInteger = ((Function) Number::longValue).compose(BigInteger::valueOf); public static void main(String[] args) { VarargFunction fibonacci = Utils.fixVararg(Number.class, Number.class).apply( f -> Utils.toVarargFunction( toBigInteger.compose( n -> (n.compareTo(TWO) <= 0) ? 1 : new BigInteger(f.apply(n.subtract(BigInteger.ONE)).toString()) .add(new BigInteger(f.apply(n.subtract(TWO)).toString())) ) ) ); VarargFunction factorial = Utils.fixVararg(Number.class, Number.class).apply( f -> Utils.toVarargFunction( toBigInteger.compose( n -> (n.compareTo(BigInteger.ONE) <= 0) ? 1 : n.multiply(new BigInteger(f.apply(n.subtract(BigInteger.ONE)).toString())) ) ) ); VarargFunction ackermann = Utils.fixVararg(Number.class, Number.class).apply( f -> Utils.toVarargFunction( (BigInteger m, BigInteger n) -> m.equals(BigInteger.ZERO) ? n.add(BigInteger.ONE) : f.apply(m.subtract(BigInteger.ONE), n.equals(BigInteger.ZERO) ? BigInteger.ONE : f.apply(m, n.subtract(BigInteger.ONE))) ).transformArguments(toBigInteger) ); Map> functions = new HashMap<>(); functions.put("fibonacci", fibonacci); functions.put("factorial", factorial); functions.put("ackermann", ackermann); Map, List> arguments = new HashMap<>(); arguments.put(functions.get("fibonacci"), Arrays.asList(20)); arguments.put(functions.get("factorial"), Arrays.asList(10)); arguments.put(functions.get("ackermann"), Arrays.asList(3, 2)); functions.entrySet().parallelStream().map( entry -> entry.getKey() + arguments.get(entry.getValue()) + " = " + entry.getValue().apply(arguments.get(entry.getValue())) ).forEach(System.out::println); } }