JavaFunctional streams

Currying

Returning functions

As a function may be considered as an object we can accept it as a method argument and return it as a value.

Let's consider the code below.

public static IntBinaryOperator sumF(IntUnaryOperator f) {
    return (a, b) -> f.applyAsInt(a) + f.applyAsInt(b);
} 

The method sumF accepts an operator f with an integer argument and returns another operator with two integer arguments. In the method body an two-arguments anonymous function is constructed and returned. This function applies f to each its argument and summarizes results.

What can we do now?
Let's write a function sumOfSquares in terms of sumF and then apply values.

// build a new sumOfSquares operator
IntBinaryOperator sumOfSquares = sumF(x -> x * x);

// the sum is equal to 125
long sum = sumOfSquares.applyAsInt(5, 10);

Other examples:

// sum of two identities: 0 + 10 = 10
long sumOfIdentities = sumF(x -> x).applyAsInt(0, 10);

// sum with coefficients: 10 * 2 + 11 * 2 = 42
long sumWithCoefficient = sumF(x -> x * 2).applyAsInt(10, 11);

// sum of two squares: 3 * 3 * 3 + 8 * 8 * 8 = 539
long sumOfCubes = sumF(x -> x * x * x).applyAsInt(3, 8);

As you can see, the possibility of returning functions opens an easy way to build more complex and generalized functions.

Currying functions

Currying is a technique of translating the evaluation of a function that takes multiple parameters into evaluating a sequence of functions, each with a single parameter.

IntBinaryOperator notCurriedFun = (x, y) -> x + y; // not a curried function

IntFunction<IntUnaryOperator> curriedFun = x -> y -> x + y; // a curried function

We can define curried function with three arguments and then apply arguments one by one.

// curried function
IntFunction<IntFunction<IntFunction<Integer>>> fff = x -> y -> z -> x * y + z;

// fff returns a curried function y -> z -> 2 * y + z
IntFunction<IntFunction<Integer>> ff = fff.apply(2);

// ff returns a curried function z -> 2 * 3 + z
IntFunction<Integer> f = ff.apply(3);

// f returns 7
int result = f.apply(1);

A more short example.

// the another result is equal to 153
int anotherResult = fff.apply(10).apply(15).apply(3);
Let's rewrite the considered sumF method. Instead of this method returning a function we can write a curried function and then use it in the same way.


Function<IntUnaryOperator, IntBinaryOperator> sumF = 
        (f) -> (a, b) -> f.applyAsInt(a) + f.applyAsInt(b);



// build a new sumOfSquares operator in terms of sumF
IntBinaryOperator sumOfSquares = sumF.apply(x -> x * x);

// the sum is equal to 125 again
long sum = sumOfSquares.applyAsInt(5, 10);

As you see, returning functions and currying are very close concepts and both are based on closures.

An example of currying

Let's consider another example.

We would like to say "Hi" to our friends and "Hello" to our partners. We can create a function that has two arguments: what and whom. The function will apply what in dependency on the context.

Function<String, Consumer<String>> say = what -> whom -> System.out.println(what + ", " + whom);

The friends' context:

List<String> friends = Arrays.asList("John", "Neal", "Natasha");
Consumer<String> sayHi = say.apply("Hi");

// too many lines of a code...

friends.forEach(sayHi);

The partner's context:

List<String> partners = Arrays.asList("Randolph Singleton", "Jessie James");
Consumer<String> sayHello = say.apply("Hello");

// somewhere in another method
partners.forEach(sayHello);

The result:

Hi, John
Hi, Neal
Hi, Natasha
Hello, Randolph Singleton
Hello, Jessie James

Of course, we could get the person list from a database and a passing the consumer as a method parameter from another part of our program or do something more complex.
How did you like the theory?
Report a typo