package ch.claude_martin.java8; import java.util.Arrays; import java.util.Collection; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Objects; import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.Stream; /** * Currying is great to spice up your code! *

* Java isn't as elegant as Haskell when it comes to Currying, but here are some * examples. *

* Read the HaskellWiki for more * info. *

* This was made with JDK8 Beta 2013-oct-10 (1.8.0-ea-b111). Also tested with * the newer Beta 120. * * @author Claude Martin * */ public class Currying { /** A 2-Tuple with two values. */ static class Pair { public Pair(final A first, final B second) { super(); this._1 = first; this._2 = second; } final A _1; final B _2; } /** A 3-Tuple with three values. */ static class Triple extends Pair { public Triple(final A first, final B second, final C third) { super(first, second); this._3 = third; } final C _3; } /** A 4-Tuple with four values. */ static class Quad extends Triple { public Quad(final A first, final B second, final C third, final D fourth) { super(first, second, third); this._4 = fourth; } final D _4; } /** * Run this to see what it does. I recommend using a debugger for this. */ public static void main(final String... args) { { // A function always consumes only one parameter! // If you want more parameters you must return a function to consume more // parameters. // This is the "add"-function (addition): final Function> add = i -> j -> i + j; Integer result = add.apply(1).apply(2); // = apply(add, 1, 2); System.out.println(result);// =3 // But you can just claim that the two parameters are really just one. // Just create a Pair and pass it as one parameter: final Function, Integer> add2 = p -> add.apply(p._1).apply(p._2); result = add2.apply(new Pair<>(1, 2)); System.out.println(result);// =3 // Each function that consumes just one pair can be converted to a // function that returns a function: final Function> add3 = i -> j -> add2.apply(new Pair<>(i, j)); result = apply(add3, 1, 2); System.out.println(result);// =3 // curry2 is a function that "curries" a function that takes a pair: result = curry2(add2).apply(1).apply(2); System.out.println(result);// =3 // curry2(uncurry2(x)) = x result = curry2(uncurry2(curry2(uncurry2(curry2(uncurry2(curry2(add2))))))).apply(1).apply(2); System.out.println(result);// =3 // uncurry does the opposite: result = uncurry2(add).apply(new Pair<>(1, 2)); System.out.println(result);// =3 // The same is true for more than two parameter: final Function>> f = a -> b -> c -> a * b + c; result = uncurry3(f).apply(new Triple<>(1, 2, 3)); System.out.println(result); result = apply(f, 1, 2, 3); System.out.println(result);// =5 // You can only apply some parameters and get a function, then you can // later apply the rest. // partial application: Function part = apply(f, 1, 2); System.out.println(part); // ...$$Lambda$... // apply last argument: result = apply(part, 3); System.out.println(result); // =5 // applyR does exactly the same as apply, but it is recursive. part = applyR(f, 1); // only one argument this time System.out.println(part); // ...$$Lambda$... // apply last two arguments: result = applyR(part, 2, 3); System.out.println(result); // =5 // Some functions even consume a 4-Tuple! final Function, Integer> f2 = q -> q._1 * q._2.length() + (q._3 ? q._4 : 0); result = apply(curry4(f2), 1, "hallo", true, 4); System.out.println(result);// =9 result = curry4(f2).apply(1).apply("hallo").apply(true).apply(4); System.out.println(result);// =9 } { // Some fun with Collections: final Collection list = Arrays.asList(1, 2, 3, 4); final Function> add = i -> j -> i + j; // partially apply all integers to add: final Stream> list2 = list.stream().map(add); // Now apply 42 as the "second" parameter: final Stream list3 = list2.map(f2 -> f2.apply(42)); // Print to console: System.out.println(Arrays.toString(list3.toArray())); // = [43, 44, 45, 46] } { // More fun with Collections: final Collection listA = Arrays.asList(1, 2, 3, 4); final Collection listB = Arrays.asList(5, 6, 7, 8); final Function> add = i -> j -> i + j; // Where did Stream::zip go?! Not available in this beta release... // listA.stream().zip(listB.stream()).map(... final Collection> zip = new LinkedList<>(); final Iterator iteratorA = listA.iterator(); final Iterator iteratorB = listB.iterator(); while (iteratorA.hasNext() && iteratorB.hasNext()) zip.add(new Pair<>(iteratorA.next(), iteratorB.next())); final List results = zip.stream().map(uncurry2(add)).collect(Collectors. toList()); System.out.println(results.toString()); // = [6, 8, 10, 12] } } /** * Applies the first argument to the function and then the next to the * returned function. The application of all given parameters must result in a * complete application of the given function. * * @param f * Curried Function. * @param arg0 * The first argument is type checked. * @param args * More arguments. * @return Returns the value of the last function. * @throws IllegalArgumentException * Thrown when the arguments don't match. * @throws ClassCastException * The return value will be cast implicitly, which could cause a * ClassCastException. */ @SuppressWarnings({ "unchecked", "rawtypes" }) static T apply(final Function f, final X arg0, final Object... args) throws ClassCastException, IllegalArgumentException { Objects.requireNonNull(f, "The function argument must not be null."); Object result = f.apply(arg0); if (args != null && args.length > 0) { Function f2 = requireFunction(result); for (int i = 0; i < args.length - 1; i++) f2 = requireFunction(f2.apply(args[i])); result = f2.apply(args[args.length - 1]); } return (T) result; } /** Cast to function or throw IllegalArgumentException. */ @SuppressWarnings("rawtypes") private static Function requireFunction(final Object x) { if (!(x instanceof Function)) throw new IllegalArgumentException("Result was not a function, but more arguments remain."); return (Function) x; } /** The same as {@link #apply(Function, Object, Object...)}, but recursive. */ @SafeVarargs @SuppressWarnings("unchecked") static T applyR(final Function f, final X arg0, final Y... args) throws ClassCastException, IllegalArgumentException { if (args == null || args.length == 0) return (T) f.apply(arg0); else return (T) applyR(requireFunction(f.apply(arg0)), args[0], Arrays.copyOfRange(args, 1, args.length)); } /** Converts an uncurried function to a curried function. */ static Function> curry2(final Function, C> f) { return a -> b -> f.apply(new Pair<>(a, b)); } /** Converts a curried function to a function on pairs. */ static Function, C> uncurry2(final Function> f) { return (Pair p) -> f.apply(p._1).apply(p._2); } /** Converts an uncurried function to a curried function. */ static Function>> curry3(final Function, D> f) { return a -> b -> c -> f.apply(new Triple<>(a, b, c)); } /** Converts a curried function to a function on triples. */ static Function, D> uncurry3(final Function>> f) { return (Triple p) -> f.apply(p._1).apply(p._2).apply(p._3); } /** Converts an uncurried function to a curried function. */ static Function>>> curry4(final Function, E> f) { return a -> b -> c -> d -> f.apply(new Quad<>(a, b, c, d)); } /** Converts a curried function to a function on quads. */ static Function, E> uncurry4(final Function>>> f) { return (Quad p) -> f.apply(p._1).apply(p._2).apply(p._3).apply(p._4); } }