By Brennan Spies - Posted on July 15th, 2008
Actually, the title should be "The Next Great Mid-Level Language", but doesn't sound quite as snappy...
There has been a lot of talk in the last year or two about Java losing ground to newer languages and about which language is going to replace Java as the dominant language going into the next decade. Various factions from the Ruby and Scala camps have weighed in on the debate in earnest, leaving even some die-hard Java programmers wondering if they should be learning a new language (and soon). After all the smoke clears, what should the reasonable person conclude? Is it time to move on?
First of all, Java as a platform is not going away anytime soon, and is certainly not disappearing into a "Cobol limbo" where only legacy applications are maintained. With its large installed base of actively developed applications, enterprise credibility, and incredibly rich open-source ecosystem, Java has proven that the 10-year mark is no longer the death knell for the popularity of a language. Java programmers will continue to be productively employed for many years to come...
...Nevertheless, Java as a language is showing its age and each new addition to it (generics, closures, etc.) leaves awkward stretch marks as the authors of these changes try to reconcile them with the existing language design. Combine this with the fact that languages such as Ruby have come along that tantalize Java programmers with their concision and expressiveness, and a window of opportunity begins to open--an opportunity for a new language to come forward and replace Java as the language of choice.
So if a language could exploit this window, what would it look like? What are the important factors in the adoption of a (mid-level) language today? I've identified a few key factors that I believe are necessary to any new language if it truly hopes to replace Java as a dominant mid-level programming language. These are (not necessarily in order of importance):
1. Cross-Platform. This may seem a given since mid-level languages invariably run on a virtual machine--until, of course, you think about .NET. The Mono project, despite a valiant effort, just does not have the resources to keep pace with Microsoft's .NET implementation. Microsoft will always be Microsoft, and many organizations will choose to develop in .NET for this reason. In the larger picture, however, few organizations are going to change their entire infrastructure to Windows in order to use .NET. Hence languages like C# that are only first-class citizens on Windows will be limited by this Achilles' heel. This means that good language candidates will likely run on the JVM since this is the only truly mature cross-platform virtual machine available today.
2. Expressive. The new language must allow the programmer to express ideas in a concise and natural way. Closures come to mind here, and they are used in languages such as Ruby to great effect. But there are also many small improvements, such as operator overloading for objects that represent numbers or the use of string interpolation, that increase the overall expressiveness of a language; "syntactic sugar" in some cases, maybe, but still important.
3. Solves Current Pain Points. Java's stellar rise in the 1990's was due in no small part to the fact that it solved many of the pain points with programming in C++: cross-platform code, memory management, etc. Any language that succeeds Java will have to also solve pain points of Java (and other current languages).
What pain points, you ask? I'll give an example: mixins in Fan (somewhat similar to traits in Scala). C++ suffered from the diamond problem stemming from the use of concrete multiple inheritance. Java solved this problem by using single concrete inheritance and abstract interfaces instead. This works well, for the most part, but has its own pitfalls: (a) it violates the DRY principle because you often have to repeat method implementations in two different classes (because of single inheritance), and (b) introducing new methods into the interface is very hard to do because it breaks all existing implementations of that interface--fine if it is only used in-house, but not good if it is a publicly used interface (such as the Collections API). Mixins in Fan allow both concrete and abstract methods but no concrete class fields (although they can declare abstract fields); this solves the two problems with Java interfaces nicely: (a) since mixins can be multiply inherited, a single method implementation can be used in many places in the object hierarchy, and (b) since concrete methods can be implemented in mixins, all classes that use them inherit these concrete methods without having to rewrite them. (There is a good explanation of how Scala does the same with traits here.)
4. Better Concurrency. As we are often reminded, multi-core processors have become the norm in computing, and software that does not exploit this fact is at a huge disadvantage. Hence the importance of the JSR-166y proposal, which aims to provide a fork-join framework in Java to ease the parallelization of tasks. But the lock-based method of dealing with concurrency is inherently complicated, and alternate approaches like the Actor model (based on message passing) have been adopted by languages such as Erlang, Scala, and Fan as simpler, more scalable ways of dealing with concurrency. This feature alone is the "must have" of any new language, as concurrency is quickly becoming as important as memory management.
5. Makes Good Decisions on What NOT to Include. A good language should encourage good programming practices, and to this end should avoid features that are inherently problematic or have low power/weight ratios. An example of the former would be the "open" classes of Ruby, which have led to the anti-pattern referred to as monkey-patching. An example of the later might be Java's own implementation of generics, which carries some considerable power, but in practice is complicated by wildcards and type erasure. A language should also avoid having too many features; in theory, the more features the merrier, but in practice all these features often have complicated interactions with each other. Choosing one good approach rather than several often saves the language from "surprise" corner cases that mar the overall experience of using the language.
6. Accessibility. In this post, among other things, Charles Nutter (currently the co-lead on the JRuby implementation) discusses how he first came upon Ruby:
This is the key ingredient of any successful language--the comfortableness of writing and reading the language. Sometimes this is because of the evolutionary nature of the language, and sometimes it is just good intuition on the part of the language designer. This is the often-overlooked human-centric part of any programming language.
C#: The star of the .NET platform, C# is largely derived from C++ and Java. Functions are first-class citizens in the language from day one, so C# had the advantage in adding functional language features to the language. Many interesting language features (closures, continuations, currying, etc.), but one big drawback: not cross-platform.
Ruby: Ruby was the hot language in 2006-2007. It's actually been around as long as Java, but remained a fairly obscure language until the Ruby on Rails framework catapulted it into fame. It is a very expressive and flexible language that continues to have a devoted following, especially among web developers. On the downside, Ruby 1.8.x is a very slow interpreted language. Ruby 1.9 has the officially blessed YARV virtual machine, but re-inventing the wheel here will mean that good performance will a few years away (minimum). There are other options (one of the better ones being JRuby) , but performance is probably not the primary consideration anyway since Ruby is more of a "high-level" scripting language than a mid-level language--i.e., it is so flexible in its design that certain optimizations are just not possible. It is probably better being complemented by a mid-level language such as Java for software where performance is more important.
Scala: Created by Martin Odersky, one of the designers of the GJ language that eventually turned into the Java generics implementation, Scala is a hybrid imperative-functional language with many interesting ideas (type inference, traits, pattern matching, and actors, to name just a few) and a fairly steep learning curve. Scala compiles to JVM bytecode and so is inter-operable with existing Java libraries. Scala is well-liked by many of the Java luminaries (Neal Gafter, Joshua Bloch, and even James Gosling himself), but its complexity and functional orientation may prevent it from becoming truly mainstream.
Fan: A new kid on the block, the Fan language is still actively evolving but shows a lot of promise. It incorporates many of the features of Java, C#, and even languages like Pascal (that funny-looking ":=" assignment operator) into a very fluid style that is immediately comfortable when you read the code. With first-class functions, Ruby-style closures, actor-based concurrency, and mixin-based multiple inheritance, Fan's developers seem to have a knack for picking the best features of existing languages and putting them together in a fairly seamless way. Fan is far from being the hot new language yet, but it is certainly one to watch.
It is not enough for a new programming language just to be "cool" or more fun to program. It has to be able to solve real problems in a much better and easier way than Java does now. My guess is that some combination of language improvements (closures, mixins, etc.) plus easier concurrent programming will be the pole vault that will lift the next language to the top. Sponsorship by a respected major company (think "Google") wouldn't hurt either. Which one...well, that's hard to say, isn't it? In the meantime I will continue to program happily and productively in Java.