What's Wrong with Java 8: Currying vs Closures
There are many false ideas around about Java 8. Among these is the idea that Java 8 brings closures to Java.
Join the DZone community and get the full member experience.
Join For FreeThere are many false ideas around about Java 8. Among these is the idea that Java 8 brings closures to Java. This is wrong. Closures have existed in Java since the beginning. But closures are evil. And while Java 8 seems to go toward functional programming, Java closures should be avoided as much as possible. But Java 8 does not help us very much in this area.
One huge difference between using a method and using a function is parameter evaluation time. In Java, one can write a method taking some arguments and returning a value. Is this a function? Not at all. A method can't be manipulated in any other way than calling it, and this implies that its arguments will be evaluated before the method is executed. This is a consequence of arguments being passed by value in Java.
Functions are different. One may manipulate functions without evaluating them. And one has complete control over when the arguments are evaluated. And if a function has several arguments, they may be evaluated at different time. This may be done through the use of currying. But first, we will see how this may be done with closures.
Examples of closures
A closure is the fact, for a function, to be able to access something in the enclosing context. In functional programming, the result of a function should only depend upon its arguments. Closures clearly break this rule.
Let's see an example in Java 5/6/7:
private Integer b = 2;
List list = Arrays.asList(1, 2, 3, 4, 5);
System.out.println(calculate(list.stream(), 3).collect(toList()));
private Stream calculate(Stream stream, Integer a) {
return stream.map(new Function() {
@Override
public Integer apply(Integer t) {
return t * a + b;
}
});
}
public interface Function<T, U> {
U apply(T t);
}
This code will produce the following result:
[5, 8, 11, 14, 17]
[1, 2, 3, 4, 5]
. So far, so good. But wait..., could 3 and 2 be replaced with some other values? In other words, isn't it rather the mapping for function f(x, a, b) = x * a + b onto the list?Well, yes and no. No, because a
and b
are implicitly final
, so they act as constants at the time the function is evaluated. But sure, they may vary. The fact that they are final
(implicitly in Java 8, explicitly in previous versions) is only a way for the compiler to optimize compilation. The compiler does not care at all about the potentially changing values. What it cares about is that the references do not change. In other word, it wants the reference to the Integer
objects a
and b
not to change, but it does not care about the values. This appears in the following version:
private Integer b = 2;
private Integer getB() {
return this.b;
}
List list = Arrays.asList(1, 2, 3, 4, 5);
System.out.println(calculator.calculate(list.stream(), new Int(3)).collect(toList()));
private Stream<Integer> calculate00(Stream<Integer> stream, final Int a) {
return stream.map(new Function<Integer, Integer>() {
@Override
public Integer apply(Integer t) {
return t * a.value + getB();
}
});
}
-
static private class Int {
public int value;
public Int(int value) {
this.value = value;
}
}
Here, we use a mutable object for a
(of class Int
instead of Integer
, which is immutable), and a method to access b
. We now simulate a function of three variables, but we still use a function of one variable and two closures to replace the two other variables. This is clearly non functional, because it breaks the rule about depending only upon the function's arguments.
One consequence is that we may not reuse the function somewhere else, should we need to, since it depends upon the context and not only upon its argument(s). We will have to duplicate the code. Another consequence is that we can't test our function in isolation, since we need the context to make it work.
So, should we use a function of three arguments? We may think that this is not possible. The reason for this is related to where arguments are being evaluated. All three arguments are being evaluated in different places. If we were using a function of three arguments, they would have to be evaluated at the same time. This is not possible since the map method will map a function of one argument to the stream, not a function of three arguments. So, the two other arguments must have been already evaluated when the function is binded (passed to map
). The solution is to evaluate the other two arguments first.
We get this effect with closures, but this results in non testable code and potential duplication.
Using Java 8 syntax (lambdas) will not change anything to this:
private Integer b = 2;
private Stream<Integer> calculate(Stream<Integer> stream, Integer a) {
return stream.map(t -> t * a + b);
}
What we need is a way to apply all three arguments at different times. This is called currying (despite the fact that it was invented by Moses Shönfinkel!).
Using currying
Currying is the fact of evaluating function arguments one by one, producing a new function with one argument less on each step. For example, if we have the following function:
f(x, y, z) = x * y + z
we may apply the arguments 2, 4, 5 at the same time and get:
f(3, 4, 5) = 3 * 4 + 5 = 17
But we may also apply only 3 and get:
f(3, y, z) = g(y, z) = 3 * y + z
We have now a new function g, taking only two arguments. We can curry again this function, applying 4 to y:
g(4, z) = h(z) = 3 * 4 + z
The order in which we apply the arguments is irrelevant. We are not performing partial calculation. (We would have to respect operators precedence). We are performing partial application of the function.
How can we do this is Java? Here is what we can do in Java 5/6/7:
private static List<Integer> calculate(List<Integer> list, Integer a) {
return list.map(new Function<Integer, Function<Integer, Function<Integer, Integer>>>() {
@Override
public Function<Integer, Function<Integer, Integer>> apply(final Integer x) {
return new Function<Integer, Function<Integer, Integer>>() {
@Override
public Function<Integer, Integer> apply(final Integer y) {
return new Function<Integer, Integer>() {
@Override
public Integer apply(Integer t) {
return x + y * t;
}
};
}
};
}
}.apply(b).apply(a));
}
This definitely does the Job, but I guess it will be difficult to convince developers that they should code using this style! Hopefully, the lambda syntax of Java 8 will help us:
private Stream<Integer> calculate(Stream<Integer> stream, Integer a) {
return stream.map(((Function<Integer, Function<Integer, Function<Integer, Integer>>>)
x -> y -> t -> x + y * t).apply(b).apply(a));
}
Hugh? Shouldn't it be simply:
private Stream<Integer> calculate(Stream<Integer> stream, Integer a) {
return stream.map((x -> y -> t -> x + y * t).apply(b).apply(a));
}
Yes, it should, but Java 8 is not able to infer the type, so we have to help it by using “manifest” type (“manifest” is the word used in the Java specification for “explicit”). In order to make code cleaner, we may use some tricks:
interface F3 extends Function<Integer, Function<Integer, Function<Integer, Integer>>> {}
private Stream<Integer> calculate(Stream<Integer> stream, Integer a) {
return stream.map(((F3) x -> y -> z -> x + y * z).apply(b).apply(a));
}
We may now name our function and reuse it when necessary:
private Stream<Integer> calculate(Stream<Integer> stream, Integer a) {
F3 calculation = x -> y -> z -> x + y * z;
return stream.map(calculation.apply(b).apply(a));
}
We may even declare the calculation function as a static member of a helper class and use static import to make the code event cleaner:
public class Functions {
static Function<Integer, Function<Integer, Function<Integer, Integer>>> calculation =
x -> y -> z -> x + y * z;
}
...
import static Functions.calculation;
private Stream<Integer> calculate(Stream<Integer> stream, Integer a) {
return stream.map(calculation.apply(b).apply(a));
}
Unfortunately, Java 8 promotes using closures. I would have been much better to offer functional syntactic sugar to ease the use of currying. In Scala, for example, the example above would be written as:
stream.map(calculation(b)(a))
Well, we can't do this in Java. However, we can come close to it by defining the following static method:
static Function<Integer, Function<Integer, Function<Integer, Integer>>> calculation
= x -> y -> z -> x + y * z;
static Function<Integer, Integer> calculation(Integer x, Integer y) {
return calculation.apply(x).apply(y);
}
We can now write:
private Stream<Integer> calculate(Stream<Integer> stream, Integer a) {
return stream.map(calculation(b, a));
}
Note that calculation(b, a)
is not a function of two arguments. It is only a method returning a function of one argument after having partially applied two arguments (one by one) to a function of three arguments, resulting in a function of one argument ready to be passed to the map function.
The calculation
method may now be tested in isolation.
Automatic currying
In the former example, we have applied currying by hand. However, as we are using a computer, we can ask it to do this for us. We can write a method that will take a two arguments function and return the curried version of it. This is very simple:
public <A, B, C> Function<A, Function<B, C>> curry(final BiFunction<A, B, C> f) {
return (A a) -> (B b) -> f.apply(a, b);
}
Should we need it, we can write a method reversing the process. It will take as argument a Function
of A
returning a Function
of B
returning a C
, and return a BiFunction
of A, B
returning a C
:
public <A, B, C> BiFunction<A, B, C> uncurry(Function<A, Function<B, C>> f) {
return (A a, B b) -> f.apply(a).apply(b);
}
Other applications of currying
There are other applications of currying. One of most important is simulating functions of more than one argument. In Java 8, there are functions of one argument (java.util.functions.Function
) and functions of two arguments (java.util.functions.BiFunction
). There are no functions of three, four, five or more arguments, like in some other functional languages. But these are not necessary. They are only like syntactic sugar for cases where all arguments may be evaluated at the same time. And in fact, this is the reason why BiFunction
exists in Java 8: one frequent use of functions is to simulate binary operators. (Note: there is a BinaryOperator
interface in Java 8, but it is used for very specific cases where all two parameters and the return value are of the same type. We will talk about this in a next article).
Currying is very useful when arguments of a function must be evaluated in different places. Using currying, one may evaluate one argument in some component, then pass the result to another component to evaluate another argument, and so on until all arguments are evaluated.
In short
Java 8 is far from being a functional language (and it will probably never be). However, it is possible to code in Java (even in Java < 8) using the functional paradigm. It simply has a cost. And this cost is much reduced in Java 8. But the developer who wants to write functional code has still intellectual efforts to do in order not to fall out of the paradigm. Using currying is one of such efforts.
Remember:
(A, B, C) -> D
may always be replaced with:
A -> B -> C -> D
even if Java 8 is not able to infer the type of this expression. You simply have to specify the type by yourself. This is currying, and it will always been safer that using closures.
In a next article, we will talk about Java 8 functional interfaces and how to deal with primitives.
Opinions expressed by DZone contributors are their own.
Comments