Anyone fancy a curry?

chicken-curry-naan-indian-dish-bread-61880409

For no reason other than it’s interesting, here’s a take on Currying in Java 8. The idea of currying is to convert a function that takes n parameters into one which can receive them one by one, or indeed all at once.

By using partial application to provide some of the inputs to a function we provided a pre-loaded function that just needs the input that the recipient most cares about, in order to do its job. For example, if streaming and mapping, you really only care about the object next in the stream in order to transform it, not any other parameters kicking around that will be the same each time in the transformation function.

Here lies my attempt at a currying library:

package uk.org.webcompere;

import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Supplier;

/**
 * Java implementation of currying
 */
public interface Curry {
   /**
    * Tagging interface for the {@link #curry(Curried)} function
    */
   interface Curried {
   }

   /**
    * A nullary function - takes no parameters, returns a value. Also a supplier
    * with a default for {@link Supplier#get} but requires the apply function
    * to make it consistent with partial application.
    * @param  type of return
    */
   @FunctionalInterface
   interface NullaryFunction extends Curried, Supplier {
      R apply();

      default R get() {
         return apply();
      }
   }

   /**
    * Unary function - takes one parameter
    * @param  return type
    * @param  parameter type
    */
   @FunctionalInterface
   interface UnaryFunction extends Curried, Function {
      /**
       * Can be converted to nullary function by full application of a parameter.
       * @param t input
       * @return a function that evaluates using the given parameter against the UnaryFunction
       */
      default NullaryFunction asNullary(T t) {
         return () -> apply(t);
      }
   }

   /**
    * Binary function - takes two parameters.
    * @param  return type
    * @param <U> first parameter type
    * @param  second parameter type
    */
   @FunctionalInterface
   interface BinaryFunction extends Curried, BiFunction<U> {
      /**
       * Partial application of binary function to yield unary function where the
       * first parameter of the binary function has been supplied already.
       * @param u first input parametr for partial application
       * @return unary function which takes next input parameter for full application
       */
      default UnaryFunction apply(U u) {
         return t -&gt; apply(u, t);
      }

      /**
       * Supply all values to return a supplier/nullary function
       * @param u first parameter
       * @param t second parameter
       * @return a nullary function that returns the equivalent of calling the binary function
       * with all its inputs
       */
      default NullaryFunction asNullary(U u, T t) {
         return () -&gt; apply(u, t);
      }
   }

   /**
    * A ternary function, which takes three inputs and returns a value.
    * @param  return type
    * @param  first input parameter type
    * @param <U> second input parameter type
    * @param  third input parameter type
    */
   @FunctionalInterface
   interface TernaryFunction extends Curried {
      /**
       * The function that's being wrapped for partial application
       * @param v input 1
       * @param u input 2
       * @param t input 3
       * @return the result of applying the function
       */
      R apply(V v, U u, T t);

      /**
       * Partially apply the first two parameters to get a Unary function for the third
       * @param v first parameter
       * @param u second parameter
       * @return a function that can be called with one parameter
       */
      default UnaryFunction apply(V v, U u) {
         return t -&gt; apply(v, u, t);
      }

      /**
       * Partially apply the first parameter to get a Binary function for the third
       * @param v first parameter
       * @return a function that can be called with the remaining parameters
       */
      default BinaryFunction apply(V v) {
         return (u, t) -&gt; apply(v, u, t);
      }

      /**
       * Supply all values to return a supplier/nullary function
       * @param v first parameter
       * @param u second parameter
       * @param t third parameter
       * @return a nullary function that returns the equivalent of calling the function
       * with all its inputs
       */
      default NullaryFunction asNullary(V v, U u, T t) {
         return () -&gt; apply(v, u, t);
      }
   }

   /**
    * A bit of syntactic sugar to convert a plain old function into one of the above types
    * @param t a functional interface implementation - probably a method reference
    * @param  type of function we're going to return
    * @return a function cast as one of the partially applicable types
    */
   static  T curry(T t) {
      return t;
   }
}

And here are some unit tests that give you a taste of using it.

package uk.org.webcompere;

import static uk.org.webcompere.Curry.curry;
import static java.util.stream.Collectors.toList;
import static org.assertj.core.api.Assertions.assertThat;

import java.util.stream.Stream;

import org.junit.Test;

public class CurryTest {
	public static String identity(String a) {
		return a;
	}

	public static String concat(String a, String b) {
		return a + b;
	}

	public static String concat(String a, String b, String c) {
		return a + b + c;
	}

	@Test
	public void curryTernary() {
		Curry.TernaryFunction function = curry(CurryTest::concat);

		assertThat(function.apply("a").apply("b").apply("c")).isEqualTo("abc");

		assertThat(function.apply("a", "b").apply("c")).isEqualTo("abc");

		assertThat(function.apply("a", "b", "c")).isEqualTo("abc");

		assertThat(function.asNullary("a","b","c").apply()).isEqualTo("abc");
	}

	@Test
	public void rightHon() {
		Curry.TernaryFunction function = curry(CurryTest::concat);

		Stream names = Stream.of("Bill", "Bob", "Ben");

		assertThat(names.map(function.apply("Right", " hon "))
			.collect(toList())).containsExactly("Right hon Bill", "Right hon Bob", "Right hon Ben");

	}
}

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s