DulcetAirman icon

Java 8 Currying

DulcetAirman | PRO | 10/21/13 11:46:10 AM UTC | 0 ⭐ | 971 👁️ | Never ⏰ | []
Java |

8.93 KB

|

None

|

0 👍

/

0 👎

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!
 * <p>
 * Java isn't as elegant as Haskell when it comes to Currying, but here are some
 * examples.
 * <p>
 * Read the <a
 * href="http://www.haskell.org/haskellwiki/Currying">HaskellWiki</a> for more
 * info.
 * <p>
 * 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<A, B> {
        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<A, B, C> extends Pair<A, B> {
        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<A, B, C, D> extends Triple<A, B, C> {
        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<Integer, Function<Integer, Integer>> 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<Pair<Integer, Integer>, 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<Integer, Function<Integer, Integer>> 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<Integer, Function<Integer, Function<Integer, Integer>>> 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<Integer, Integer> 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<Quad<Integer, String, Boolean, Integer>, 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<Integer> list = Arrays.asList(1, 2, 3, 4);
            final Function<Integer, Function<Integer, Integer>> add = i -> j -> i + j;
            // partially apply all integers to add:
            final Stream<Function<Integer, Integer>> list2 = list.stream().map(add);
            // Now apply 42 as the "second" parameter:
            final Stream<Integer> 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<Integer> listA = Arrays.asList(1, 2, 3, 4);
            final Collection<Integer> listB = Arrays.asList(5, 6, 7, 8);
            final Function<Integer, Function<Integer, Integer>> add = i -> j -> i + j;
 
            // Where did Stream::zip go?! Not available in this beta release...
            // listA.stream().zip(listB.stream()).map(...
 
            final Collection<Pair<Integer, Integer>> zip = new LinkedList<>();
            final Iterator<Integer> iteratorA = listA.iterator();
            final Iterator<Integer> iteratorB = listB.iterator();
            while (iteratorA.hasNext() && iteratorB.hasNext())
                zip.add(new Pair<>(iteratorA.next(), iteratorB.next()));
 
            final List<Integer> results = zip.stream().map(uncurry2(add)).collect(Collectors.<Integer> 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, X> T apply(final Function<? super X, ?> 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, X, Y> T applyR(final Function<? super X, ?> 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 <A, B, C> Function<A, Function<B, C>> curry2(final Function<Pair<A, B>, C> f) {
        return a -> b -> f.apply(new Pair<>(a, b));
    }
 
    /** Converts a curried function to a function on pairs. */
    static <A, B, C> Function<Pair<A, B>, C> uncurry2(final Function<A, Function<B, C>> f) {
        return (Pair<A, B> p) -> f.apply(p._1).apply(p._2);
    }
 
    /** Converts an uncurried function to a curried function. */
    static <A, B, C, D> Function<A, Function<B, Function<C, D>>> curry3(final Function<Triple<A, B, C>, D> f) {
        return a -> b -> c -> f.apply(new Triple<>(a, b, c));
    }
 
    /** Converts a curried function to a function on triples. */
    static <A, B, C, D> Function<Triple<A, B, C>, D> uncurry3(final Function<A, Function<B, Function<C, D>>> f) {
        return (Triple<A, B, C> p) -> f.apply(p._1).apply(p._2).apply(p._3);
    }
 
    /** Converts an uncurried function to a curried function. */
    static <A, B, C, D, E> Function<A, Function<B, Function<C, Function<D, E>>>> curry4(final Function<Quad<A, B, C, D>, 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 <A, B, C, D, E> Function<Quad<A, B, C, D>, E> uncurry4(final Function<A, Function<B, Function<C, Function<D, E>>>> f) {
        return (Quad<A, B, C, D> p) -> f.apply(p._1).apply(p._2).apply(p._3).apply(p._4);
    }
 
}

Comments