Pages

Tuesday, August 22, 2006

Some more remarks on closures in Java

First: They already exists. Inner classes ARE closures. Really. I can't say that often enough, but everybody who don't recognize anonymous-inner-classes as closures simply hasn't understood the concept of either anonymous-inner-classes or closures.

But inner classes have an additional feature: Multiple entry-points.

Lets have a little, somewhat silly example (I use the proposed closure-syntax):

void showIncr(int(int) incr) {
int val = 1;
for(int i = 0; i < 4; i++) {
if (i > 0) System.out.print(", ");
System.out.print(val);
val = incr(val);
}
}

void useIt() {
int(int) add2 = (int x) { return x + 2; }
int(int) mul2 = (int x) { return x * 2; }

showIncr(add2); // will print: 1, 3, 5, 7
showIncr(mul2); // will print: 1, 2, 4, 8
}

So we have a method 'showIncr' which shows the effect of a 'incr'-function we supply as a parameter. In a real example the function would of course do something useful, like calculating the numeric derivative or building some kind of data structure etc.

While it looks interesting, lets try it in Java 5. In fact it's nearly as easy:

interface IncrFunc {
int eval(int x);
}

void showIncr(IncrFunc incr) {
int val = 1;
for(int i = 0; i < 4; i++) {
if (i > 0) System.out.print(", ");
System.out.print(val);
val = incr.eval(val);
}
}

void useIt() {
IncrFunc add2 = new IncrFunc() { int eval(int x) { return x + 2; }}
IncrFunc mul2 = new IncrFunc() { int eval(int x) { return x * 2; }}

showIncr(add2); // will print: 1, 3, 5, 7
showIncr(mul2); // will print: 1, 2, 4, 8
}

Not a big difference. Sure you have to type some more characters, but in principle it's identical. Also if you use a modern IDE it can do most of the work itself. In IDEA for example you can type:

IncrFunc add2 = new

and then hit shift+ctrl+space, the IDE generates

IncrFunc add2 = new IncrFunc() {
int eval(int x) {
return 0;
}
}

automatically and you only have to fill out the body and replace the '0' by 'x + 2'. So closures are really well supported for some time now.

But can the new closure syntax do this:

Imagine after a year in production your nice little program from above should get a new feature: It should print out the name of the increment-operation. So I simply modify the latter code to:

interface IncrFunc {
int eval(int x);
String getName();
}

void showIncr(IncrFunc incr) {
int val = 1;
System.out.print(incr.getName() + ": ");
for(int i = 0; i < 4; i++) {
if (i > 0) System.out.print(", ");
System.out.print(val);
val = incr.eval(val);
}
}

void useIt() {
IncrFunc add2 = new IncrFunc() {
int eval(int x) { return x + 2; }
String getName() { return "add 2"; }
}
IncrFunc mul2 = new IncrFunc() {
int eval(int x) { return x * 2; }
String getName() { return "mul 2"; }
}

showIncr(add2); // will print: "add 2: 1, 3, 5, 7" now
showIncr(mul2); // will print: "mul 2: 1, 2, 4, 8" now
}


It's only logical to let the operation itself store it's name, so the above is the most logical extension of the existing program. But how do you modify the version with the new closure proposal in this way? Closures only have one entry-point, like the old 'IncrFunc' has only one 'eval' method. But because of this, it's not possible to update the first program the same way it was possible in the second one. Instead the first program would require a complete rewrite.

Now imagine you used those 'new closures' extensive in your huge application and decide to change it in the way similar to the above. Do you still think that those 'new closures' are a good idea?

But what's the point of the proposal then? While I'm a bit unsure myself I suspect that the proponents want to make closures more easy to use as it's possible now. And with more easy use, they hope it will be used more often and could lead to more easy programming. But: Certain kinds of refactoring would in fact get more complicating (look above). And as a programmer you would always have to choose and decide which kind of closures you want to use, the new ones (with some limitations) or the older (with a little bit more typing).

Also Java simply isn't a functional programming language. In functional programming there are other means to accomplish the same as in Java, but they only work if they are used with knowledge and all over the program in a consistent way. But Java is a OO-language, and while I have my doubts about OO in general, I have much bigger doubts in multi-paradigm programming. Java has lots of success in building large applications and there is now a 'Java-way' of programming which works fine and has it's own advantages. Trying to change this way to a little bit more functional one simply wouldn't work. You have to do it fully or stay on the old track. If you mix two of your favorite foods with a blender you wouldn't expect that it would really taste better, would you?

Don't get me wrong: Java can use lots of little tweaks and extensions to the language, but those extensions should complement the 'Java-way' of programming instead of trying to force Java programmers to move over to some kind of half-way functional programming.

4 comments:

Neal Gafter said...

You can't simulate closures using interfaces and inner classes because you can only write APIs that provide exception transparency using closures. I explain this, and the importance of APIs with excpetion tranparency, in my blog post http://gafter.blogspot.com/2006/08/use-cases-for-closures.html

Karsten Wagner said...

I don't see, why. If you have to extend Javas generics for closures to support full exception transparency, those extension would work with inner classes too. So I still don't see the difference.

But how do you solve parallel iteration with closures (except write a new forEach-method for every number of parallel iterations)? I think that's a real stopper for closures as a general means for iteration in a non-lasy language.

Neal Gafter said...

I think you're contrasting function types to interfaces, not closures versus inner classes. By definition a closure has access to enclosing scopes, but inner classes are restricted to final variables only, so clearly they are not the same thing. I agreee it might be possible to specify closures in terms of interfaces (rather than function types), and we are working on a revision along those lines.

Karsten Wagner said...

The restriction of allowing only final-variables to be captures by closures is a very irrelevant one for the following reasons:
- In most cases you can simply define a variable as 'final' because only read access is needed
- In the remaining cases (where you really need mutation) you can always box the variable in an array. While this isn't really pretty, it can simply be overcome with a new syntax (for example using the 'transient'-keyword to force autoboxing for locals like I described) or simply by lifting the existing restriction and allow it always.

In fact in functional programming it's quite common that closures can't change outer values, simply because mutation isn't generally allowed. To allow it, you have to use some sort of trick which is in the case of the ml-language-family boxing the real value into a mutable container - that's identically to way you have to do it in Java right now. So if you say that Java haven't closures right now, because of the boxing-requirement to allow mutation, you would also say that ML has no closures.