This post originated from an RSS feed registered with Java Buzz
by Tim Vernum.
Original Post: Achieving closure
Feed Title: A word used to describe something
Feed URL: http://blog.adjective.org/feed/category/Development/rss2
Feed Description: Tim's random musings on software development, java, jetty and other such frivolities.
Over the last week or so I've spent some time evaluating the variousclosurediscussions
that are floating about.
So, I sat down to write some code. Most of the situations where I've longed
for some sort of closure syntax are in iteration contexts - searching lists,
filtering lists, etc. - so my first test scenario was just that. I wanted to
iterate over a list of integers, and negate each one to produce a new list.
Then I wanted to iterate over that list and print out each number.
Here's the code in idiomatic Java5 syntax:
public void sample( List<Integer> numbers )
{
List<Integer> inverse = new ArrayList<Integer>( numbers.size() );
for( Integer i : numbers )
{
inverse.add( -i.intValue() ) ;
}
for( Integer i : inverse )
{
System.out.println(i);
}
}
If we define a couple of interfaces and utility methods:
public static interface Mapper<FROM,TO>
{
public TO map(FROM arg);
}
public static interface Acceptor<FROM>
{
public void accept(FROM arg);
}
public static <FROM,TO> List<TO> map(Iterable<FROM> itr, Mapper<FROM,TO> mapper)
{
List<TO> result = new ArrayList<TO>();
for( FROM el : itr )
{
result.add( mapper.map(el) ) ;
}
return result;
}
public static <FROM> void apply(Iterable<FROM> itr, Acceptor<FROM> apply)
{
for( FROM el : itr )
{
apply.accept(el);
}
}
Then we can write that code in Java5 with anonymous inner classes:
public void sample( List<Integer numbers )
{
List<Integer> inverse = map( numbers,
new Mapper<Integer,Integer>()
{
public Integer map(Integer i) { return -i.intValue() ; }
}
) ;
apply( inverse,
new Acceptor<Integer>()
{
public void accept(Integer i) { System.out.println(i); }
}
);
}
I would argue that this syntax is significantly uglier, and less clear than
the idiomatic java.
-- please correct me if I've done something wrong with these samples -
none of the proposal authors provided a compiler to test with :)
The CICE example is a lot shorter than either of the Java5 examples
(although that's partly due to my choice in code style), but I'm not sure that
it's any more readable than the first version. One of the keys to clarity and
readability is to make the important bits stand out, and in this example that
doesn't really happen. Some of that is due to my choice in the parameter order,
and the fact that the apply and map methods are static rather than members of
Collection, but a large part of it is that there's a lot of characters that are
mostly redundant to my understanding of the code. I don't care that the map
method takes a Mapper<Integer,Integer> - I care that it is going to map
the elements through the expression "-i.intValue()", and that is hidden at the
end of the statement in amongst the noise of the interface and type
parameters.
However, the CICE example is pretty easy to understand. I didn't have to
keep consulting back to the spec to work out what I needed to write, and there
was only 1 syntax to learn (and most of that was syntax I already knew)
It's not a lot different from CICE. There's less noise, but more magic. I
don't need to "return" keyword in the map example, and I don't need semicolons
in either. However, within this choice of BGGA syntax, I don't think the
removal of those syntactical elements has aided the readability in any
meaningful way. In fact in both cases I originally had them in the code and
then realised they were not needed.
I don't know whether the "new Integer(...)" is required. I suspect not, but
I'm really not 100% sure. Without it, the closure would return "int" rather
than "Integer" (the CICE example didn't have this confusion because I had
specified the return type as a type parameter). However the BGGA refers to "an
assignment conversion from the result type of T to the return type of U", which
I can only hope includes auto-boxing. But is BGGA smart enough to know that it
should handle map<?,?>( List<Integer> int(Integer) ) by setting the
2nd type parameter to Integer and auto-boxing the closure result? I hope so.
The fact that I need to stop and think about it is a little worrying - there's
enough compiler magic going on that I can't really call this example "straight
forward"
That certainly reads a little more succinctly. It looks a bit like the Java5
foreach syntax, but not quite. It's concise and readable, but it's a little bit
foreign.
I'm not sure which proposal I prefer. CICE was easy to write, and easy to
understand, but I'm not sure it adds enough syntactical benefit to actually
make the language significantly better. If we end up with CICE closures, I
suspect a lot of people will keep writing code in the style of the first
example. That's OK - the first example is perfectly readable - but it might be
a sign that CICE doesn't give quite enough syntactical sugar. I do think CICE
will provide some readability benefits for list searching/filtering though.
List<Integer> positive = select( Integer i : numbers) { i >= 0 }
is significantly easier to read. It's clear what the code is trying to do -
it's less clear (particularly to less experienced developers) how it
accomplishes it.
Ultimately, I see this as an intractable problem. The goal here is to add a
concise, neat and elegant syntax for closures to a language which is none of
those things. Any closure proposal that meets that goal is going to look out of
place in the overly-verbose, staticly-typed world of java. I want closures, I
think they're an important part of any modern programming language, but there's
a big risk of this turning into a case of lipstick on a pig.
BGGA ends up providing a syntax that is quite similar to the syntax provided
by some other languages. The syntax really helps in writing concise,
intentional code, but it doesn't really have a "java feel" to it.
Java isn't ruby. Even if we wanted to turn it into ruby, it's too late. So
java needs a closure syntax that looks and feels like java. I think BGGA misses
the mark on that one. But if we're going to add a closure syntax it at least
needs to offer some advantage over what we currently have, and I think CICE
falls down there.
Turning to some of the semantic concepts involved, I really like that fact
that CICE treats everything as an implementation of a SAM. There's no new
types, no new methods on Class/Object, not much new to learn. It also forces
library writers to develop against interfaces rather than function types. This
has 2 specific advantages that I can think of
Teams who reject the new syntax will not be prevented from using those new
APIs.
It opens up some interesting possibilities to use dynamic proxies
But in the end I can't really support CICE for the use cases I've played
with here. It simply doesn't improve the clarity of the code sufficiently to
warrant a change to the language - even such a simple change. Perhaps when I
dig into more complex uses cases its value will show through, but for simple
cases, I can't see it.
Neither proposal has yet included any discussion of APIs (or new syntax) for
manipulating closures. I haven't seen a nice way to perform currying in either
proposal (perhaps I'm just not imaginative enough), and neither proposal seems
to allow taking a reference to an existing method (either static or
non-static). In my "apply" example above, I see no real reason why I had to
write a new closure for System.out.println(i), that's already a void(Integer)
method, why can it not be assigned to a function type? In BGGA that syntax is
simple enough that the loss is quite small. In CICE that ability is missed.
I didn't need any of the complex BGGA features. No synchronized parameters.
No non-local returns. No "Unreachable" types. I still question whether those
features are worth adding to the language. There's a lot of complexity there
for a fairly uncommon use case.
Partly it's an issue of balance: One of my other concerns with CICE is that
it follows the same route the anonymous inner classes took us down the first
time - add minimal changes to the language in order to cater for a particular
set of use cases. I'm not sure that's what we need. We don't just need to be
able to do the same things better, we need to be able to do better things. We
all decided quite a long time ago that anonymous inner classes aren't enough to
provide the expressiveness developer are after. What happens when we decide
that CICE isn't enough either? Do we keep adding little bits of syntax until we
get where we want to go? I think BGGA is going the other direction. It's adding
a lot of stuff that we haven't tried before, and I don't think we (or they)
truly understand the consequences. Do we really need all of that functionality
now? Can we be confident that the decisions we make in our current relative
ignorance will turn out to be the right ones? Perhaps it is possible to
implement part of BGGA in Java 7 and then refine it after that. I'm not really
sure how to approach it, but it seems like a lot to through into the language
without a good feel for how it will play out.
I'll be very disappointed if Java 7 ends up having either CICE (as it
currently stands) or BGGA (any version <= 0.2) because I don't think either
of the proposals manages to get the balance of "java-ness", readability,
expressiveness, and clarity that we need.
Side Note: I wrote all the samples in vi, and was pleasantly surprised
to find I could still write java code without an IDE. I had expected to be
cursing at the unnecessary verbosity of java, but it was quite manageable for
the small snippets of code I was writing