Skip to main content

Evolution vs Intelligent Re-design?

Posted by javakiddy on February 13, 2008 at 4:12 PM PST

You know you're getting old when you find yourself complaining about how English is being butchered, instead of inventing new ways to butcher it yourself.

Languages change and evolve, they cannot stand still. This applies to programming languages just as much as natural written/spoken language. The difference is, of course, natural languages don't require backwards compatibility. It's this which causes the headaches — if we could just add keywords whenever we wanted, or retrofit the grammar at the drop of a hat, there wouldn't be a problem. But there's a huge body of Java code already out there, and it would be nice if none of it got broken by any changes.

I wonder if this is why programming languages, unlike natural languages, seem to have shelf lives? In nature the success of a species is constrained by the size of its compatible habitat. As the environment changes a species may evolve, but only up to a point. Eventually its popularity fades and better adapted animals arrive to dominate the landscape.

In programming, a dramatic environmental shift began at the end of the Seventies when the personal computer saw the end of dinosaur Mainframes, and with them stalwart languages like COBOL in favour of C and later C++. Fifteen years on and the internet saw C and C++ slowly retreat into niche markets, replaced by net-savy new blood in the form of Java, JavaScript, PHP, etc.

Why couldn't COBOL just evolve to become less 'records and batch processing', and more 'files and interactivity'? Why couldn't C just evolve to be less 'bytes and memory pointers', and more 'bytecode'? The answer is they could have — but there's only so much retro-fitting one can do to an established technology...

So how far can Java continue to evolve before it too goes the way of the dinosaur?

Seeking closure (groan!)

[I'm taking my life in my hands with the below — language design isn't really my field. So treat this blog as a 'view from the trenches', rather than the considered opinion of someone who's spent decades developing compilers.]

An issue came up on the comments for Kirill Grouchnikov's blog "Evolving the language" (a reply to his own "And so it begins...") relating to the use of the BGGA Closures for event handling. While the BGGA proposal is slightly more than just a lightweight replacement for anonymous inner classes, when it comes to event handling it's the removal of the inner class boilerplate which seems the biggest selling point.

As samyron pointed out in the comments of the Kirill's blog, the following code...

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

...would be translated into...

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

...which, at first glance, certainly seems more compact.

Although the closure syntax may be a little more cryptic to the untrained eye, this it outweighed by the reduced need for boilerplate. If your code is merely a short and snappy negative/positive/zero condition to sort a collection, or a filename filter for JPEGs, it's likely to be dwarfed by the enclosing anonymous inner class code. So a leaner syntax on the face of it certainly does seem useful.

But event handlers typically have to load/save data, interact with objects, update the UI state, talk to databases, etc. — all this means they are rarely single statement affairs. A more realistic event handler might be (adapting the above example)...

// Sans Closures
button.addActionListener(new ActionListener()
{ public void actionPerformed(ActionEvent ev)
{ doSomething();
doAnotherThing();
doYetMoreThings();
doEvenMoreThings(withThisThing);
if(firstDayOfSpring)
{ dontForgetToDoThis();
ohAndAlmostForgotThisToo();
}
}
});

...which would translate as...

// Avec Closures
button.addActionListener({ActionEvent e => processAction(e)});
private void processAction(ActionEvent e)
{ doSomething();
doAnotherThing();
doYetMoreThings();
doEvenMoreThings(withThisThing);
if(firstDayOfSpring)
{ dontForgetToDoThis();
ohAndAlmostForgotThisToo();
}
}

It basically comes down to this: when the code body size is short, a heavyweight wrapper (as provided by anonymous inner classes) seems unreasonable. But as body expands beyond one line, the concept of a lightweight wrapper becomes more and more of an irrelevance. Indeed its impact becomes so trivial as to, some might argue, no longer justify its more cryptic syntax. The very fact that the original example hid the stodge behind a processAction() method betrays the ineffectiveness of the BGGA closure syntax for anything other than ultra-trivial blocks of code.

While I recognise Closures have several strings to their bow, I personally wouldn't count 'event handling' as one of them. In a practical sense I doubt their tight syntax would have much of an impact given the size of most event handlers. It's a bit like when your girlfriend takes her socks off in a vain attempt to cheat the bathroom scales.

The meaning of liff [*]

Of course everyone has their own favourite Java language enhancement. I, as a Swing coder, would like to see an end to the cumbersome boilerplate code for pushing stuff onto the Event Dispatch Thread. Perhaps a syntax for bundling arbitrary blocks of code into Runnables and posting them onto the end of queues?

// Ugly
SwingUtilities.invokeLater(new Runnable()
{ public void run()
{ // Your Swing code here
}
});
// Nice!  'SwingUtilities' implements an event queue interface.  (But how 
// to support invokeAndWait() ..?)
goto(SwingUtilities)
{ // Your Swing code here
}

Not that anything like the above is likely to make it into the language. :)

While the elite coder welcomes more syntactic shortcuts, the novice merely sees a steeper learning curve. A clean syntax with familiar grammatical patterns employed over and over is easier to learn than one full of cryptic looking quirks and counter-intuitive side effects.

The rather awkward way in which Generics were slotted into the existing language is already a big turn off for beginners. New programmers will always favour a syntax with a low barrier to entry for novices, rather than go faster stripes for experts. They'll vote with their feet if a language takes a dramatic turn for the worse.

The trick is to balance the two concerns — move the language forward, but not in such a way as to dramatically shorten its life expectancy. Unfortunately this can sometimes mean missing out on juicy additions, simply because the fallout would be too extreme.

But do we really need every juicy addition? Languages are tools, and each has its own strengths and weaknesses. They say French is good for making love, German for giving orders, and English for apologising — unfortunately mix-and-matching spoken language to suit your mood isn't normally an option. But computer languages are not so tightly bound: we can if so desired pick Python to write one project, Java for the next, and Ruby for a third. We can even mix them within a single project.

The Da Vinci Machine promises to re-invent Java's runtime core to suit a much wider range of languages. If this project bears fruit (and I sincerely hope it does) we may not have to worry so much about including so many new feature into the Java language itself. But this will mean that while the Java runtime will go on, the Java language itself will eventually die out.

Do we want Java to die out? An alternative is to seize the initiative and begin developing a Java replacement, a Mk. II if you like. Going back to the drawing board, we can fix all the problems with Generics and easily slot in Closures and Properties in a way which makes sense.

So how much life is their left in the current Java? We have a lot of time and effort invested in it, which has resulted in a healthy reputation, but can it last much longer? When is it time to stop evolving, and start over with an 'intelligent re-design'?

Related Topics >>

Comments

@cayhorstmann: What is needed is a way to take a block of code and pass it to a method so that it can have its way with it.

Actually what the SwingUtilities example does is to take a block of code and pass (queue) it to a designated thread so that it can have its way with it. A subtle difference, but an important one. ;)

Your SwingUtilities example is excellent. You want it, but not everyone does. A successful programming language should provide means for giving you what you want without burdening others.

Currently, Java has three mechanisms for this: inheritance, libraries, and annotations. None of them is powerful enough to give you invokeLater. What is needed is a way to take a block of code and pass it to a method so that it can have its way with it.

Fortunately, computer scientists know of such a mechanism. It is called a closure.

The rationale for adding closures to the language is not merely to give you a marginally nicer syntax for event handlers, but to give you a general tool to solve your problems.

Natural languages have shelf lives too. Take a look at Latin. It was in it's day THE language of commerce and government. At some point an elite deiced the language was being corrupted. That it was being polluted by new words and concepts. This elite worked to reduce the language back to it's pure roots. This was the death knell for Latin. A language that does not change and add new words and concepts is dead. Computer languages are the same. Java needs to grow and change. Old things need to be removed, new concepts should be accommodated.

We should release Java 3.0 based on OpenJDK. This release would break backwards compatibility for the sake of cleaning up the language but provide automatic code-migration tools which would convert old source-code and class files on your behalf.

An alternative is to seize the initiative and begin developing a Java replacement, a Mk. II if you like. Going back to the drawing board, we can fix all the problems with Generics and easily slot in Closures and Properties in a way which makes sense. It is already here: Scala

Actually, it would be // Avec Closures button.addActionListener({ActionEvent e => doSomething(); doAnotherThing(); doYetMoreThings(); doEvenMoreThings(withThisThing); if(firstDayOfSpring) { dontForgetToDoThis(); ohAndAlmostForgotThisToo(); } });

Regarding EDT stuff - what if you could annotate a block of code (instead of a class or a method)? Then, you would just have two new annotations (RunOnEDTSync and RunOnEDTAsync) and use those to annotate your code. That would be a hefty addition to the language, of course.

Precisely my sentiments.. I am also of the opinion that we should freeze the language changes and instead simply recommend something like Scala for the cases where it makes sense.

Imagine that Robinson Crusoe went "offline" during Java 1.4. Will he recognize Java 7 as anywhere near to being the Java he knew if he comes back "online" in 2009? It is already a different language with Java 5.0. So why not just move to Scala, instead of having to learn all the quirks of Java 1.4 along with the quirks of Java 5, Java 7?

Generics did not introduce a new paradigm in Java. It completed the Java type system (with new quirks). But something like Closures would introduce a completely new paradigm in Java such as custom designed control statements, DSLs etc. It is cool, but it definitely is not the way the existing Java libraries have been defined. It is also not the way the majority of the Java developers have learned to think. Things like closures should either have been in the beginning itself or not at all.

As a side note.. Even after these many years, Java.net has still not fixed "Preview"! It still removes my comment after a preview. Is it so hard to implement?

The fulcrum of this debate has been stated:

But do we really need every juicy addition?

I'd slightly alter the emphasis accordingly:

But do we really need every juicy addition?


Let's not get distracted with fortification of the language unless the need to do so so has been specified succinctly. The prescription for legacy language longevity was described, albeit retroactive, but what would that be for Java?
My serrated knife is still the best tool in my kitchen for slicing bread and tomatoes, depite all of the Ronco&#169 advertisements...

@tobega: How about a named private inner class: [...]

The problem with Swing's single thread model is any code which changes the UI needs to be run on Swing's EDT. Suppose we kick off another thread to do some time consuming work, but we also want to give feedback to the user...

void loadSlowData(File f) { statusLabel.setText("Please wait..."); // Do some initialisation try { do { // Read and process data statusLabel.setText("x% loaded"); }while(inputStr!=null); statusLabel.setText("Finished!"); } catch(Exception e) { statusLabel.setText("ERROR!"); } finally { // Clean up } }

The sections in bold need to be run on Swing's event thread. The interleaving code should be run elsewhere, so it won't block the UI....

You can see the problem, and why multiple named inner classes would be cumbersome -- particularly with the "x% loaded" message, where 'x' would be calculated from local method variables. Yes, in this simplistic example we could write a method which takes a String, makes Runnables and queues them -- but in real life our interaction with Swing isn't always so neat.

I should point out though that my original SwingUtilities example was in jest -- we can't go changing a language just because a single community has some headaches with the way a specific library was implemented. (Or can we..? :)

How about a named private inner class: SwingUtilities.invokeLater(new MySwingCodeToDoThis()); with a static instantiation method: SwingUtilities.invokeLater(MySwingCode.toDoThat()); inheriting an invokeLater method: MySwingCode.toDoTheOther().invokeLater(); Why always compare to using an anonymous class?

Great post Simon,

Human languages evolve because of the need to communicate experiences more effectively with each other. Much of the recent change to English is not really new language, it's the inclusion of words and phrases from other cultures.. pretty much because the English speakers aren't living on an isolated island anymore... they now have to communicate with those who speak other languages.

You ask why COBOL didn't evolve... and the truth is that it did evolve... it's just that the evolved version is not as well suited to describing the tasks of programming languages than other languages such as 'C' and eventually Java (which all share a lineage with ALGO)L.

I'm rambling now (big surprise).... Good post Simon.

-JohnR

@luggypm: Teach the new programmers about closures once they've got annonymous inner classes well and truly under their hoods.

Although it is true any complexity can be overcome by careful teaching, in reality most programmers don't acquire their first taste of a language through a carefully planned out tutorial or course. Also, if a particular syntax or construct has widespread use across a range of code and APIs (which is not the case with Reflection, to be honest) it becomes almost impossible to shield learners from encountering it.

To some extent the problem of the learning curve is a red herring. The learning curve is a combination of language and lessons. Teach the new programmers about closures once they've got annonymous inner classes well and truly under their hoods. If you tried to teach new java programmers about reflection too early you might put them off - thats no reason to remove reflection .

@swpalmer: Actually they don't. setText is defined as being thread safe :-)

Defined where?

@javakiddy: The sections in bold need to be run on Swing's event thread.

Actually they don't. setText is defined as being thread safe :-)

Point taken though... and now that SwingWorker is part of the standard JRE I would have hoped that it got more use.. but even though I know that *I* don't even use it yet. Maybe I need a wizard action in Netbeans to wrap code so that it is called on the event thread...

Oh, and you yourself noticed the possibilities of parameterizing.

When you ask the question about what is needed to change a language there certainly is some function of utility and complication and a threshold value for when it's worth it.

I don't see why multiple named inner classes would be more cumbersome than multiple anonymous classes. The line count is the same and the readability is much greater.

That said, it would be handy to be able to send off one-liners. But perhaps you could use BeanShell or Groovy for those?