Thursday, October 19, 2006

Concurrent Loop Excetion - Comparing Java and Groovy

Neal showed in his blog a new example of how closures in java could look like, in real world usage.

If I would program this in Groovy (which runs on the JVM btw), I would maybe write this:


def getAttendees() {
return getResponses()
.findAll{it.mayAttend()}.collectAsync(threadPool) {it.attendee}
}

Right, that's nearly a one-liner. Of course that code is not fully equal to Neal's program, because mayAttend() is executed synchronous, and Groovy doesn't have collectAsync. But we could write such a method and use it in a category. Maybe the method would look like:

def collectAsync(threadPool, Collection col, Closure action) {
def result = []
def tasks = col.collect {
try{
synchronized (result) {
result << action.call(it)
} catch (Throwable ex) {
LOGGER.log(Level.SEVERE, "Uncaught Exception", ex);
}
}
threadPool.invokeAll(tasks)
if (!result) return null
return result
}

For the non Groovy people some comments.. Groovy has an dynamic typing system, native literals for lists (see result), operator overloading (<<), closures and of course runs on the jvm with very good integration. Anyway, I am wrapping the original closure in another closure for the logging. This removes some code from the usage. If we where funny, we could use an additonal closure for the logging. "it" is a shortcut for the current value given to the closure. "it.attendee" is equal to "it.getAttendee()", Groovy does have logic for beans.

So how does it work? I use findAll to filter out all EventResponses that don't attend. findAll returns a new list containing only the important responses. collectAsync creates a list of closures, each closure representing a task, and gives that to my threadpool as suggested in the lower part of the article - of course it wasn't with closures there. Anyway, during execution each task calls the action closure and writes in "result". The nice thing is, that this function could be a library function used somewhere. There is no reference to something special besides the logger and threadPool. In fact the code is short only if the method is a library function. It is no use to tell people how well closures might look, if I compare library functions and their usage with raw code. The Groovy version is short because of the lack of typing we are using and much other factors. If I would have to add types then it would become much larger. For example even if there where a findAll method working on a collection and able to take such a closure, I still would have to define the list element type... possibly findAll<EventResponse>{it.mayAttend()} Then, "it" is not typed. Ok then it might look like: findAll{EventResponse it -> it.mayAttend()}. the Groovy syntax for defining parameters is a little different form the closure proposal, don't care to much about that. Next is collectAsync.. collectAsync<Principal>(threadPool){EventResponse it -> it.attendee} That makes:

getResponses().findAll{
EventResponse it -> it.mayAttend()
}.collectAsync<Principal>(threadPool){
EventResponse it -> it.attendee
}

Could still be seen as a one-liner, but it is a bit too big for my taste. Of course collectAsync would have to be changed too:

Collection<R> collectAsync(threadPool, Collection<T> col,
Closure<T,T> action) {
List<R> result = []
List<closure<Void>> tasks = col.collect<> {
try{
synchronized (result) {
result << action.call(it)
}
} catch (Throwable ex) {
LOGGER.log(Level.SEVERE, "Uncaught Exception", ex)
}
}
threadPool.invokeAll(tasks)
if (!result) return null
return result
}

And this version is just a wish... I mean [] must be replaced by new ArrayList<R>(), !result with isEmpty(). Then we still are not in java, because we need to redefine our generic closure type to Closure1 for the closure taking one argument and Closure0 for closure taking no arguments. Next downside is that findAll and collectAsnyc might not be defined on Collection to keep compatibility (that is no problem in Groovy, as Groovy does have a special mechanism to handle this). So findAll might be a static import, and collectAsync is possibly defined in whatever threadPool is. Then the code may look like:

return threadPool.collectAsync<Principal>(
findAll(getResponses()) {
EventResponse it -> it.mayAttend()
}), {
EventResponse it -> it.attendee
})

which is much worse to read due to the new ordering...

List<EventResponse> filtered = findAll(getResponses()) {
EventResponse it -> it.mayAttend()
}
return threadPool.collectAsync<Principal>(filtered){
EventResponse it -> it.attendee
}

looks much better again.. Of course we are not using the syntax suggested in the closure proposal.. then it would look like:

List<EventResponse> filtered = findAll(getResponses(),
EventResponse(EventResponse it) {it.mayAttend()});
return threadPool.collectAsync<Principal>(filtered,
Principal(EventResponse it) {it.getAttendee()})

And a version using inner classes would like:

List<EventResponse> filtered = findAll(getResponses(),
new Closure1<EventResponse,EventResponse) {
EventResponse call(Eventresponse it) {it.mayAttend()}
});
return threadPool.collectAsync<Principal>(filtered,
new Closure1<Principal,EventResponse) {
Principal call(EventResponse){it.getAttendee()}
});

So we really gain something from the closure proposal - if compared to the current usage of inner classes, but then if I compare that to the very short Groovy version... Java could be shorter here if more type inference was used. It is not the shortness of the Groovy version I like so much, it is the clear code.

But another thing should be made clear with this: the programming style in a language like Groovy is different from the style normally used in Java. I mean compare my endresult with the version without closures at the end of the article. I think I would prefere the other version. Even if I "beautify" the code by using proper identation and such things, I am not gaining much. Strange, isn't it? Java prevents us from defining usefull library functions because of its syntax bloat. Even though there is neraly no logic left in the method itself and all is done by library functions, I have to write so much code for the little compiler... annoying would I say.

I really hope they decide to add more type inheritance. Of course I know that type inference is no easy task for the compiler builder, but it should be doable. Java has way to go here I guess.

No comments: