Skip to main content

Evolving the language

Posted by kirillcool on February 6, 2008 at 10:34 AM PST

I guess i wasn't very focused in yesterday's entry, paying too much attention to the details of the specific puzzle and not emphasizing my main point (or as Chris defined it the "money quote"). I'll try to rectify it here, bringing together my thoughts on the subject of evolving Java as a language.

First, let's start with the code from Neal's entry. Here is the relevant part:

    static <T,U> List<U> map(List<T> list, 
{T=>U} transform) {
        List<U> result = new ArrayList<U>(list.size());
        for (T t : list) {
            result.add(transform.invoke(t));
        }
        return result;
    }

    public static void main(String[] args) {
        List<Color> colors = map(Arrays.asList(
Flavor.values()), { Flavor f => f.color });
        System.out.println(colors.equals(Arrays.asList(Color.values())));
    }

Putting aside the puzzle punchline, here is how i would do the same without closures:

public class Puzzler<T, U> {

   interface Transformer<T, U> {
      U transform(T t);
   }

   List<U> transform(List<T> list,
          Transformer<T, U> transformer) {
      List<U> result = new ArrayList<U>(list.size());
      for (T t : list) {
         result.add(transformer.transform(t));
      }
      return result;
   }

   public static void main(String[] args) {
      List<Colour> colors = new Puzzler<Flavor, Colour>().transform(Arrays
            .asList(Flavor.values()), new Transformer<Flavor, Colour>() {
         public Colour transform(Flavor f) {
            return f.color;
         }
      });
      System.out.println(colors.equals(Arrays.asList(Colour.values())));
   }
}

And now comes the million dollar question - which one is better? It's a simple question that has two straightforward answers. However, the real answer is "it depends". And based on the subjective background of each one of us, we would go for either the first version or the second version. What does it depend on? Well, quite a few things.

There was an illuminating exchange of opinions in Scala blogosphere about a month ago, ignited by Doug Pardee's post, which lead Reg Braithwaite to quote Jef Raskin's views on the definition of intuitive:

It is clear that a user interface feature is "intuitive" insofar as it resembles or is identical to something the user has already learned. In short, "intuitive" in this context is an almost exact synonym of "familiar."

Let's get back to our two code samples, and to the question which one is better. In my highly subjective opinion, the second one is better, because:

  • it is more readable
  • it is more maintainable
  • it is more verbose

Allow me to address each one of these three points. The second sample is more readable because this is how i'm accustomed to program this sort of requirement. This is what the current language structures allow me to do, and i don't find it too much of a hassle. The code is more maintainable because of the limited variety of currently available options. It ascertains that i and other people who will have to maintain this code must have seen this approach a few times in the existing code base. Getting back to the Jef Raskin quote, it feels more intuitive since it is familiar. Not necessarily optimal, but familiar nonetheless.

The last point on verbosity might be a little controversial, but i'll stand by my words. I've programmed in Ada for almost five years (professionally) and i've never seen it as an over-verbose language. Sure, it might take me an extra 20 seconds to read through the definition of that interface and its invocation, but the extra verbosity makes sure that the code guides me (as its maintainer) along its intended path.

Why would one choose the first example? There are many reasons, all of them undoubtedly valid in the subjective eye of the relevant parties. Some would enjoy the perceived expressiveness (cramming more logic in less characters), some would prefer the abstraction of a method body as first-class language citizen, some would want to know that their language is on par with the hot kid-du-jour that gets all the blogosphere love, some would view it as a refreshing exercise to expand their mind, and some would see the commercial potential that lies in books, consulting and conferences (absolutely no offense meant).

One nagging question remains unanswered - who is the intended target of the new language features? Are the creative minds behind different closures proposals at work just because they are creative, or is there an overwhelming evidence that Java programs (and programmers to a lesser extent) suffer without this language feature. Couple that with the absolute refusal to remove existing language features (like, say, poorly-implemented generics), and you get the following from Bruce Eckel:

But we need to become especially conservative when considering major, fundamental language features like closures which, while they can be very appealing in theory, may have a cost that is too great in practice when they are forced into a language that values backward compatibility over the clarity of its abstractions.

There are real-life scenarios addressed by closures. Anonymous listeners, unnecessary complex try-catch-finally blocks on closing streams and connections, blocks guarded by semaphores, you name it. But if the language is not going to remove (refuse to compile) the existing ways of addressing these scenarios, adding yet another way to do what millions (yes, millions that do not read or write blogs) Java programmers already know how to do is very harmful. Harmful to the code readability (at least in the first few years with the very slow adoption rate for the server-side JDK upgrades) and harmful to the code maintainability.

In my opinion, the language is only a tool. At the end of the day, we still have real people addressing real problems. The technical merits of new language proposals need to be weighed carefully against the potential negative disruption that they bring into the everyday cycle of software development. The puzzlers are only a tip of the iceberg. When i need to maintain and extend existing code, i already have a hard enough time to understand the business logic in it. If you're not going to remove the existing ways to address the limitations imposed by the language, don't add yet another way. It's not going to help me. Not in the everyday world.

Related Topics >>

Comments

Good post.

First a disclaimer - I have my own proposal - Clear, Consistent, and Concise Syntax (C3S):

http://www.artima.com/weblogs/viewpost.jsp?thread=182412

As many people have pointed out verbose syntax is not the end of the world. Hence my order of priority - Clear, Consistent, and finally Concise (C3S). For clarity I have favoured keywords over symbols. For consistency I have chosen the new syntax to be sugar for an inner class. One of the disadvantages of BGGA and FCM is that they have to box their closure into an object and this leads to puzzling behaviour one example of which is RestrictedClosures in BGGA:

http://www.artima.com/forums/flat.jsp?forum=106&thread=220920&start=0&ms...

To reduce verbosity I suggest type inference, the dropping of brackets on method calls if it is unambiguous, optional return statements, and no ; before }. In C3S the action listner example used in other posts is:

button.addAction method( e ) { processAction e };

A further point about these examples, they tend to be unrealistic and hide details (as others have said). For example I would typically write an Action and add that to the button. These more realistic examples are hard/impossible in BGGA, FCM, and CICE because they involves more than one method, uses an abstract class, require access to other methods (BGGA and FCM only), or need an argument to the constructor. An Action example in Java 5:

final JTextArea textArea = new JTextArea( 5, 20 ); final JScrollPane scrollPane = new JScrollPane( textArea ); textArea.setEditable( false ); final JButton button = new JButton( new AbstractAction( "Press Me!" ) { public void actionPerformed( final ActionEvent notUsed2 ) { textArea.append( "Pressed!\n" ); } });

Is not easy in BGGA, FCM, or CICE but is in C3S:

final textArea = JTextArea.new 5, 20; final scrollPane = JScrollPane.new textArea; textArea.setEditable false; final button = JButton.new AbstractAction.new( "Press Me!" ) { method( notUsed2 ) { textArea.append "Pressed!\n" } };

@javakiddy,joshy

Why not FCM?

button.addActionListener(#processAction(e)); You can't beat that syntax for clarity or simplicity. It's "call this method when the action is performed". There is an inline syntax, but when you can pass any currently defined method the need for inlining is greatly diminished. And thats really why I like FCM. It works with EVERY method in every library ever written in java.

In my book if it takes parameters and returns a value then it's a method. Why come up whit a new syntax to pass around the same parameters and return the same values?

@joshy: I think they could be very useful used as event handlers. Take this example:

Hmm, but surely you haven't proved their usefulness, you've just hidden their complexity?

The fact is that closures offer a very lightweight syntax, which is ideal for single statement/line code snippets like the 'sort' criteria passed into a collection. That lightweight syntax, however, becomes pointless when the code it wraps runs into several (or many) lines. A bit like wrapping one inch of foam padding around a sledge hammer -- it's still gonna hurt when it hits you in the face! :)

The trick of your example was to move the event handling code into a separate method, thereby hiding how ineffective closures become when used with anything other than ultra terse blocks of code. What, for example, is the benefit if closures if we expand your example to its full size (see below) ? (Apart, that is, from acting like a poor-man's (type-unsafe) event delegate? :)

// Sans closures button.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent ev) { doSomething(); doAnotherThing(); doYetMoreThings(); doEvenMoreThings(withThisThing); if(firstDayOfSpring) { dontForgetToDoThis(); ohAndAlmostForgotThisToo(); } } }); // Avec closures button.addActionListener({ActionEvent e => processAction(e)}); private void processAction(ActionEvent e) { doSomething(); doAnotherThing(); doYetMoreThings(); doEvenMoreThings(withThisThing); if(firstDayOfSpring) { dontForgetToDoThis(); ohAndAlmostForgotThisToo(); } }

@kirill, your later code is not equivalent with the former one. You fuck up with the wildcard. The second code shoul be something like that:

public class Puzzler { interface Transformer>T, U< { U transform(T t); } static <T,U> List<U> transform(List<T> list, Transformer<? super T, ? extends U> transformer) { List<U> result = new ArrayList<U>(list.size()); for (T t : list) { result.add(transformer.transform(t)); } return result; } ...

Rémi

@atripp It's less clever for the very reason BK says clever code should be avoided. It's TOO clever which makes it less clever because it's too hard to read, use. So we apply BK's paradigm to it, and decide that too clever is less clever and should therefore not be used.

Closures are way too clever for their own good, causing severe code obfuscation for the purpose of making the writer look clever. As a result they make the writer look foolish instead by making him look like a showoff who just wants to impress people with his ability to write oneliners that do magic that noone understands.

* it is more readable * it is more maintainable * it is more verbose Dead on target Kirill. Couldn't agree more on all 3 points. I've got tired of repeating it at every forum where (the seemingly tireless) promoters of BGGA blow their trumpets and try to project BGGA as the best possible closures implementation for Java ( go CICE!). Again, I'd urge everyone to also read Tim Bray's post on this topic.

Strange arguments and comments... Since when has ignorance become a virtue? Re: Not everybody want to be a new Guy Steele, Huh? well, fair enough. I had an honor of meeting Guy Steele and I can re-assure the audience: this is not something that happens to everybody; most people are safe. ;)

@joshy

Are you sure about the cause and effect of your relative usage vs creation? Maybe your usage statistics would be different if generics were not so tricky for creating methods and classes?

Great post! I would add one more bullet item:
* It's less clever.
To paraphrase Brian Kernighan: "Whenever you see a particular part of your code that you find particularly clever, strike it out and write it again."

Being clever is a lot of fun, especially to show yourself and your friends how smart you are. But cleverness has no place in real-world code. The person maintaining your code wants to easily maintain it, not be impressed with your skills.

Joshy makes a great point, too. I also don't mind generics because I just "use" them 99% of the time. But closures is different. Their whole purpose is to be used all over the place, and people will do that.

javakiddy: I think they could be very useful used as event handlers. Take this example:

button.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { processAction(e); } });

If this could be re-written using closures (please forgive me if the syntax is incorrect, I haven't been keeping up with all of the closures proposals).

button.addActionListener({ActionEvent e => processAction(e)});

I think this is to read (my opinion, yours may differ). The only issue I see is that you cannot remove this listener should you need too as you have no reference to it.

Joshy: The issue is relative usage vs creation. Allow me to explain:

I thought the main benefit of closures was the ability to quickly slot small chunks of 'bespoke' code into pre-existing 'standard' functionality? In which case the end user (ie. not the library coder, but the library user) will be seeing them rather a lot.

To be honest I can't see them being used for event handlers a lot. Closures are a ultra lightweight syntax, and most event handlers in the real world (at least in Swing) demand more than a single terse statement or block of code.

Of course, I couldn't agree more.

I guess it's time to start removing stuff from the language (and, more important, from the libraries) so new features can be added without creating maintenance problems. Give then a fancy new name like "Java3" and provide a compile-time switch (or even a jre run-time switch) so you are forced to opt either for the "new features" or the "old way".

That is, make it clear to developers and mantainers which set of language features they have to worry, so instead of a big fat language you have two similar but different choices.

I myself would rather make sure no API from Java7 uses Enumeration and other deprecated methds tham rushing into closures. Why don't put all new features into "incubation" as part of Groove or some OpenJDK Project before making into the official JDK?

There's one thing I see missing from this argument whenever I hear people talk about closures, and it's something that has been missed from most discussions of generics too. The issue is relative usage vs creation. Allow me to explain:

Generics can be tricky to use when creating methods and classes which use generics. Generics is much easier to use when simply calling generified methods and classes that other people have created. So which do I do more often? Why, number two, of course. In fact, I'd say that 90% of my usage of generics is with the built in Collections libraries. Another 9% is other APIs written by fellow engineers. Maybe 1% (at most) is my own generified code. So I feel the trade off is fine for generics because I spend most of my time using it the easy way and very little time using it the hard way.

So how is this relevant to Closures? We must ask ourselves, how will people actually use closures in the real world? And I don't me the uber-geeks that hang out on websites like Java.net (or super-uber-geeks who have our own weblogs :). Instead think of how the average programmer will use them. My guess is 90% will be for Collections improvements (like Comparators) and event handlers. Another 9% will be for concurrency related things (like the new fork / join APIs). So that leaves about 1% of the time when they creating APIs which use closures.

In this light I think Closures are worth the complexity tradeoff. (As for which particular proposal to endorse, I have not the skill nor time to compare them, but I really liked Neal's presentation.)

Excellent blog. I think the proponents and opponent of closures are talking at cross purposes. Proponents talk about how useful closures are, opponents talk about the stress it will place on the existing language syntax to graft them in.

The question is to what extent we turn Java into a Swiss Army Knife -- a tool which is a jack of all trades, but a master of none? Personally I think we have to recognise that Java will never be all things to all men -- there'll always be particular tasks or modes of programming to which Java will be awkward or inefficient. Trying to plug these gaps (and don't forget, as time marches on innovation opens up new gaps) will make the language more complex and less intuitive, as more and more patterns and variations are layered into the existing grammar. This in turn raises the bar for future Java novices, and makes it more likely they will turn instead to rival (cleaner) languages.

Just because I like bacon, and I like ice-cream, doesn't mean I'd buy a bacon and ice-cream sandwich! :)

nah, it's one 10 page question with a 417 page answer :)

And noone will ask much about closures. Kids will think they know all about it and abuse it without second thought, while the rest of us are trying to figure out what the heck those kids were thinking when writing that nonsense and implementing it again from scratch without closures (or will have jumped ship to get away from the disaster area Java will become).

That document may well be 427 pages long, but if the questions it contains really are 'frequently asked' then there must be a lot of incredibly inquisitive people around :)

A comprehensive FAQ on float and double would probably be even longer. While there are awkward corner cases in generics, I don't see many problems in day to day use. As for closures, the code you already write is probably a poor candidate for use of closures. The best uses will be in code that many currently omit (like tedious finally's) or don't write at all. Some of the uses of the proposed concurrent package are dramatically different (cleaner) if closures are available.

mthornton - and we all know how well generics performed on anything but toy examples. A 427 page long FAQ (!!!) on generics comes to mind. I wonder how big would the closures FAQ be...

Verbosity is a double edge sword. Too much and the essential detail of what is being done is lost in a forest of trivia. This problem isn't apparent in toy examples that neatly fit in a blog.

Sheesh!! Now, everyone and his dog wants to be the next Guy Steele. At this rate, Java will really be in deep shit I would say, much less evolving.