Control abstraction
All of the examples of synchronous use cases for closures that I've shown so far fall into the general category of control abstraction. The idea is that you have a common pattern appearing in your code, and you want to abstract the common parts while allowing the caller to provide the parts that differ. You can think of these use cases as satisfying a desire to extend the set of statements available in the language. Java is already pretty good at data abstraction, but not so good for control abstraction.
Thanks to a blogger whom I can't identify, I have another simple use case for closures of the synchronous variety (still control abstraction, though). I would love to be able to use this in the implementation of my project, Google Calendar to simplify the code in hundreds of places.
Many places throughout our code we have this pattern appearing:
void someOperation() throws SomeException { long t0 = System.nanoTime(); boolean success = false; try { someOperationInternal(); // may fail, throwing SomeException success = true; } finally { long elapsed = System.nanoTime() - t0; logElapsedTime("someOperation", success, elapsed); } }
The presence of this code helps us with realtime (i.e. elapsed time) performance measurement, but it clutters up our code and tends to obscure the logic of the application. We would prefer to write a timeOperation method once, thereby enabling us to write the above code as follows:
void someOperation() throws SomeException { timeOperation("someOperation", (){ someOperationInternal(); // may fail, throwing SomeException }); }
Or, better yet, (presuming the language supports the abbreviated invocation syntax that I'll describe in a future post)
void someOperation() throws SomeException { timeOperation("someOperation") { someOperationInternal(); // may fail, throwing SomeException } }
The timeOperation method can be written (once) as follows:
<E extends Exception> void timeOperation(String operationName, void() throws E block) throws E { long t0 = System.nanoTime(); boolean success = false; try { block(); success = true; } finally { long elapsed = System.nanoTime() - t0; logElapsedTime(operationName, success, elapsed); } }
This is the best way I've seen to remove this kind of redundant code that is currently repeated hundreds of times throughout our application. This example should help illustrate why closures will help make code much easier to read. Certainly timing support could instead be done with an additional extension to the Java language: a new statement for timing blocks of code. Do you also add a new statement for closing your streams at the end of a block, and one for locking and unlocking your java.util.concurrent locks around a block of code? Once you start down that path, where does it all end? We'd prefer to give ordinary programmers the ability to write these kinds of methods (and perhaps place some of them into the JDK) rather than adding a series of language extensions to address particular use cases.
Besides control abstraction, there are also important synchronous use cases for closures inspired by functional programming idioms with a very different feel, and I'll blog about that separately.
8 comments:
This use case is one where I've tended to use java.lang.Proxy and an InvocationHandler implementation, because usually I want to time all methods in an interface. Do you imagine that closures would largely replace this usage? More generally, how do you see closures interacting with aspect-oriented programming in Java (which tends to make use of Proxy where one is adding an aspect to a class that implements an interface).
Jeff: I think the techniques are complimentary. I would not be happy if I had to encapsulate everything I wanted to time in an interface, as that would warp our architecture and make it very difficult to move timing code around to narrow down on a hotspot.
About your AOP question: closures will make it possible to write a set of APIs in the form of a library that allows programmers to define and register cutpoints and advice in a typesafe way without resorting to reflection or preprocessing. I plan to write more about this, but I'm not sure when.
You could clean up that code in Google Calendar right now with anonymous inner classes:
void someOperation() throws SomeException {
timeOperation("someOperation", new TimedOperation<SomeException>(){
public void run() throws SomeException {
someOperationInternal(); // may fail, throwing SomeException
}
});
}
<E> void timeOperation(String label, TimedOperation<E> operation) throws E {
long t0 = System.nanoTime();
boolean success = false;
try {
operation.run();
success = true;
} finally {
long elapsed = System.nanoTime() - t0;
logElapsedTime(operationName, success, elapsed);
}
}
interface TimedOperation<E> {
void run() throws E;
}
Keith: that doesn't work if the timed operation throws more than one exception type (for example IOException and SomeException). Furthermore, the block can't use nonfinal local variables from enclosing scopes.
Admitttedly, the kind of API you suggest would work for some of the places where we time operations.
Would it be possible to extend this into multiblock control structures? Maybe something like:
attempt {
// do stuff
}
rollback {
// clean up after exception
// and rethrow
}
Attempt would create a closure that takes a second closure as an argument. That second closure would be created by rollback and applied to the attempt closure.
I would say that your timing example would be better with an inner class than a closure. One of the great things with inner classes is grouping of multiple-methods and fields. In the timing example the local, t0, can come from an abstract class and you can override toString so that you can report what it is you are timing.
You seem to be justifying closures vs. inner classes on issues like support for multiple checked exceptions, short syntax, and access to non-final locals. Surely these are seperate issues from the Closure vs. Inner class debate. Why not fix multiple checked exceptions, short syntax, and access to non-final locals seperately? Then inner classes will benifit and if closures are added some of the support is already in place.
howard: read my blog for the next few days; we'll present a variant of the spec along the lines you suggest, and then show how it falls short. A big part of the problem is that inner classes, as they are currently used, would be worse off with the features you suggest adding to them, because they are primarily used for asynchronous use cases (see my previous post about use cases).
In the example shown in the article, if all we wanted to do was to time a function, then there are much simpler ways to do it.
If Java had first class functions/methods, you could simply write it like this: (pseudo-code)
long timeIt(Function f) {
long startTime = System.nanoTime();
f();
return System.nanoTime() - startTime;
}
This can be easily extended to execute functions with parameters.
Why would we need closures for this?
It seems to me that the way it has been conceived, Closures add verbosity to the language without really providing much benefit.
If you really wanted to make Java easy, chnage it so that we can open a File with a simple one-liner like
File f = new File("C:\\readme.txt");
Hide away all the complexity in your implementation and let me do the simple things in an easy manner, instead of cluttering up the language in new verbose constructs.
I would like things explained in a very simple manner, preferably words of one or two syllables.
Post a Comment