Higher-order Functions, Functions Composition, and Currying in Java 8
Join the DZone community and get the full member experience.
Join For FreeThe main concept behind functional programming is that data and behaviors can be treated and manipulated uniformly and in the same way. In practical terms this means that it is possible to pass to a method both values and functions and in the same way the method itself can return either a value or a function. With this regard a function accepting one or more functions as argument and/or returning another function as result is called an higher-order function. The purpose of this article is explaining what functions composition and currying are demonstrating why they can be both seen as special cases of higher-order functions and showing how to use them with a practical example in Java 8.
Let's define a Converter as an object with a single method that takes as input a conversion rate and the value to be converted and returns the conversion's result that is obtained by simply multiplying these 2 arguments. Given this description the Converter can be seen as a function of 2 parameters and then defined as a particular implementation of the BiFunction interface taking 2 Doubles, the conversion rate and the value to be converted, as arguments and returning a third Double that is the converted value.
public class Converter implements BiFunction<Double, Double, Double> { @Override public Double apply(Double conversionRate, Double value) { return conversionRate * value; } }
In this way we could for instance convert 10, 20 or 50 miles in kilometers by instancing a new Converter and passing to it the conversion rate between miles and kilometers together with the number of miles to be converted.
Converter converter = new Converter(); double tenMilesInKm = converter.apply(1.609, 10.0); double twentyMilesInKm = converter.apply(1.609, 20.0); double fiftyMilesInKm = converter.apply(1.609, 50.0);
This works, but there is something not very practical with this approach: we are obliged to repeat the same conversion rate for all the invocation of our converter. It would be better if our converter could offer an easy way to obtain a specialized version of it just converting miles to kilometers. In other words we would like to create a Function out of the original BiFunction having set one of the 2 arguments of the BiFunction to a fixed value, that in our case is the miles/km conversion rate. In functional programming this particular operation is called currying.
Currying
Unfortunately the native Java 8 BiFunction interface doesn't provide currying out of the box. Nevertheless it is very easy to develop a custom extension of the original BiFunction interface having 2 additional default methods that allow to set one of the 2 arguments of the BiFunction to a fixed value.
@FunctionalInterface public interface ExtendedBiFunction<T, U, R> extends BiFunction<T, U, R> { default Function<U, R> curry1(T t) { return u -> apply(t, u); } default Function<T, R> curry2(U u) { return t -> apply(t, u); } }
The methods curry1 and curry2 fix respectively the first and the second argument of the BiFunction and return a Function having as single argument the one of the two that is still remained unfixed. If we now change our Converter just making it to implement this ExtendedBiFunction interface
public class Converter implements ExtendedBiFunction<Double, Double, Double>
it's possible to obtain a Function converting miles to kilometers fixing the first argument of the converter to the appropriate conversion rate.
Function<Double, Double> mi2kmConverter = converter.curry1(1.609); double tenMilesInKm = mi2kmConverter.apply(10.0); double twentyMilesInKm = mi2kmConverter.apply(20.0); double fiftyMilesInKm = mi2kmConverter.apply(50.0);
Of course we can repeat this specialization process how many times we want for example to obtain a converter from ounces to grams.
Function<Double, Double> ou2grConverter = converter.curry1(28.345); double tenOuncesInGr = ou2grConverter.apply(10.0); double twentyOuncesInGr = ou2grConverter.apply(20.0); double fiftyOuncesInGr = ou2grConverter.apply(50.0);
More formally currying is a technique where a function f of two arguments (x and y say) is seen instead as a function g of one argument which returns a function also of one argument. The value returned by the latter function is the same as the value of the original function.
f(x,y) = (g(x))(y)
Of course the same principle can be generalized to transform a function of n arguments in one of n-1 arguments having fixed the remaining one. It's also easy to see that the curry1 and curry2 methods are examples of higher-order function since they returns a Function as result.
Function composition
In the biggest part of cases a multiplication, as the one performed by our Converter class, is all you need to do to convert a unit measure into another. However there are some situations for which this couldn't be enough. For instance the formula to convert a Celsius temperature in a Farenheit one is:
F = C * 9/5 + 32
After having multiplied the Celsius temperature by the 9/5 factor, you still have to add 32 in order to obtain the corresponding Farenheit value. Is there a way to obtain a Function converting Celsius to Farenheit degrees from our generic Converter? This is a case where functions composition can come to the rescue. In fact the native Java 8 Function interface already provides a default method named andThen accepting another Function as argument and returning a third Function as result (another higher-order function) that is the composition of the first 2. The resulting composed function first applies the original function to its input, and then applies the function passed as argument to the result. In this way the Celsius to Farenheit conversion function can be obtained currying the generic converter with the 9/5 factor and then applying a further function that increments the result of the first one of 32.
Function<Double, Double> celsius2farenheitConverter = converter.curry1(9.0/5).andThen(n -> n + 32); double tenCInF = celsius2farenheitConverter.apply(10.0); double twentyCInF = celsius2farenheitConverter.apply(20.0); double fiftyCInF = celsius2farenheitConverter.apply(50.0);
What about the opposite conversion? We have to use the formula:
C = (F - 32) * 5/9
meaning that this time the subtraction has to be performed before the multiplication by the conversion rate. In other words we need to compose the original conversion function with another function (the subtraction) that has to be applied before it and not after as we did above. The native Java 8 Function interface already provides a compose method allowing to achieve this, but for some reason the same method is not available also on the BiFunction interface. This is not too bad since we can add it (actually we need 2 of them, one for each argument) to our ExtendedBiFunction interface.
@FunctionalInterface public interface ExtendedBiFunction<T, U, R> extends BiFunction<T, U, R> { default Function<U, R> curry1(T t) { return u -> apply(t, u); } default Function<T, R> curry2(U u) { return t -> apply(t, u); } default <V> ExtendedBiFunction<V, U, R> compose1(Function<? super V, ? extends T> before) { return (v, u) -> apply(before.apply(v), u); } default <V> ExtendedBiFunction<T, V, R> compose2(Function<? super V, ? extends U> before) { return (t, v) -> apply(t, before.apply(v)); } }
Here the compose1 higher-order function composes the BiFuction with the before Function and returns another BiFunction that, when applied, first transform its first argument with the before Function and then applies the original BiFunction with the transformed first argument and the unchanged second one. The compose2 method implements exactly the same principle to the second argument of the BiFunction leaving unchanged the first one. We can now obtain the Farenheit to Celsius converter composing the generic Converter with a Function that subtract 32 from the value to be converted before before to currying the first argument fixing it to 5/9.
Function<Double, Double> farenheit2celsiusConverter = converter.compose2((Double n) -> n - 32).curry1(5.0/9); double tenFInC = farenheit2celsiusConverter.apply(10.0); double twentyFInC = farenheit2celsiusConverter.apply(20.0); double fiftyFInC = farenheit2celsiusConverter.apply(50.0);
If you want you can both deepen your knowledge of the new API introduced into Java in its 8th major release and learn how to leverage the new functional features of the language reading the Java 8 in Action book that I just finished to write together with Raoul-Gabriel Urma and Alan Mycroft.
Opinions expressed by DZone contributors are their own.
Comments