We tend to think of programming languages in two categories:
"living languages" in which we should seriously consider
developing new code, and "legacy languages" that we mainly
use, if at all, because we have to maintain an existing code base. The
act of classifying a language into one or the other category helps us
decide what, if anything, we might consider doing to change the
language. If a language is primarily a legacy language, changes should
be aimed at making it easier to maintain and modify existing bodies of
code. A living language, on the other hand, also benefits from changes
that make it easier to design, develop, and maintain new code. Living
languages evolve to reduce accidental complexity.
"What does a high-level language accomplish? It frees a
program from much of its accidental complexity. An abstract program
consists of conceptual constructs: operations, datatypes, sequences,
and communication. The concrete machine program is concerned with bits,
registers, conditions, branches, channels, disks, and such. To the
extent that the high-level language embodies the constructs wanted in
the abstract program and avoids all lower ones, it eliminates a whole
level of complexity that was never inherent in the program at all."
-No Silver Bullet - Fred Brooks
Programs written in legacy languages tend to exhibit a high degree
of accidental complexity
[Code's
Worst Enemy, Steve Yegge]
[Mr. Yegge
meets Mr. Brooks, Brian C Cunningham].
Early in the life of a
language, the complexity of programs written in that language may
appear to be essential, but as we learn more about software
engineering and programming languages, we find patterns of complexity
appearing in the code that can be eliminated by improved languages.
A good example of this is garbage collection. In C and C++, memory
management is a pervasive concern. Smart pointers and destructors
help, but they do not significantly reduce the complexity of memory
management. In languages with garbage collection, most of the
complexity of memory management is assumed by the implementation of
the language. Most languages that have been introduced in the past ten
years support garbage collection.
Another example is concurrency. The
threads-and-locks-and-semaphores primitives of Java enable
parallel programming, but require that programmers express concurrency
at a fairly low level. This has been "good enough" for some
time, as most programs are not deployed on highly concurrent
hardware. But that is changing
[The Free Lunch Is Over, Herb Sutter].
Libraries such as java.util.concurrent
and Doug Lea's
fork-join framework help somewhat, but in many cases they
introduce complexities of their own. Other languages that support
closures, such as Scala make
fork-join-like libraries much easier to use. Scala supports control
abstraction [Crowl and LeBlanc],
which allows the libraries
to manage much of the complexity associated with concurrency [Debasish Ghosh].
Support
for the Actors
model [Haller and Odersky], for example, can be expressed cleanly as a library in
Scala
Besides raising
the level of abstraction of concurrent code, control abstraction
also raises the level of abstraction for sequential code by eliminating
whole categories of boilerplate, which can instead be moved into
common library code. This kind of boilerplate cannot be
significantly reduced by adding one or two custom statements to the
language, because such built-in forms necessarily make assumptions
about the use cases that narrow their applicability. For example, ARM
blocks don't document how they handle exceptions arising from
the close() method. One example in the proposal suggests they are
silently swallowed at runtime, which may work for many cases involving
I/O streams, but another example given is a transactional API, in
which ignoring such exceptions is precisely wrong. Without a
specification for the syntax and semantics, the reader is welcome to
imagine the most favorable treatment of each use case. But an
attempt to reconcile these and other conflicting requirements may show
the approach cannot be salvaged. Perhaps that is why no progress
has been made since mid 2006.
What about Java? Is it a living language, or a
legacy language like Cobol? This question underlies much of the
debate about how to move the Java programming language forward, if at
all. Carl Quinn asked at the December 14, 2007, JavaPolis Future of
Computing Panel (to be published on
http://www.parleys.com): "How can we address the issue of
evolving the [Java] platform, language, and libraries without breaking
things?"
Neal Gafter:
"If you don't want to change the meaning of anything ever,
you have no choice but to not do anything. The trick is to minimize
the effect of the changes while enabling as much as possible. I think
there's still a lot of room for adding functionality without
breaking existing stuff..."
Josh Bloch: "My view of what really happens is a little bit
morbid. I think that languages and platforms age by getting larger and
clunkier until they fall over of their own weight and die very very
slowly, like over ... well, they're all still alive (though not
many are programming Cobol anymore). I think it's a great thing,
I really love it. I think it's marvelous. It's the cycle of
birth, and growth, and death. I remember James saying to me [...]
eight years ago 'It's really great when you get to hit the
reset button every once and a while.'"
Josh may well be right. If so, we should place Java on life support
and move our development to new languages such as Scala. The fork-join
framework itself is an example of higher-order
functional programming, which Josh
argues is a style that we
should neither encourage nor support in Java. Is it really time to
move on?
Personally, I believe rumors of Java's demise are greatly
exaggerated. We should think of Java as a living language, and strive
to eliminate much of the accidental complexity of Java programs. I
believe it is worth adding support for closures and control
abstraction, to reduce such complexity of both the sequential and
concurrent aspects of our programs. At the same time, for completely
new code bases, we should also consider (and continue to develop)
newer languages such as Scala, which benefit from the lessons of Java.