This is Part One of a series of articles on Java.next. In Part One, I will explore the common ground shared by the Java.next languages.
I have chosen four languages which together represent "Java.next": Clojure, Groovy, JRuby, and Scala. At first glance, these languages are wildly different. Clojure is a Lisp. Groovy is the "almost Java" choice. JRuby has the beauty of Ruby, and the mindshare of Rails. Scala, unlike the others, brings the notion that we need more static typing.
As you might imagine, there is heated debate about which of these languages is best for some purpose, or best in general. Lost in the debate is the fact that these languages share a ton of common ground. They all evolved against a shared background, the Java language. Their design decisions are all influenced by what has worked well in Java, and what has failed.
In this article I will demonstrate two important points about the common ground these languages share:
I have distilled the shared advantages of Java.next to eight points, which are explored in more detail below.
In Java, we live every day with the distinction between objects and primitives. This causes three practical problems:
In Java.next, everything is an object. You can invoke methods on all types using the same syntax.
In Java, to create a property, you must define a field, a getter, a setter, and (often) a constructor, all with appropriate protection modifiers. In Java.next, you can define all of these in a single step.
If you need to override (or omit) a getter, setter, or constructor for a class, you can also do that, without having to spell out all boilerplate versions of the other pieces.
And that's not all. All of these languages embrace TMTOWTDI (There's More Than One Way To Do It), so there are multiple variants on the approaches shown above.
Java.next provides a convenient literal syntax for the most important collections: arrays and maps. In addition, you can string together multiple operations by passing function arguments, without having to write explicit iterators or loops. For example, to find the all the squares under 100 that are also odd:
There are similar conveniences for name/value collections, a.k.a. hashes or dictionaries.
The convenient collections described above are a special case of a more general idea: functional programming. Java.next supports functions as first class objects, allowing function arguments, functions that create new functions, and closures over the current scope. As a simple example, consider creating an adder function that adds some value chosen at runtime:
In Java, you cannot override operators. Math looks like this, for types like BigDecimal:
Java.next allows you to override operators. This allows you to do create new types that feel like built-in types, e.g. you could write a
RationalNumber that supports +, -, *, and /.
Checked exceptions are a failed experiment. Java code is bloated with checked exception handling code that tends to obscure intent without improving error handling. Worse yet, checked exceptions are a maintenance headache at abstraction boundaries. (New kinds of unrecoverable failures down the dependency chain should not necessitate recompilation!)
Java.next does not require you to declare checked exceptions, or to explicitly deal with checked exceptions from other code. It is a testimony to the power of Java (the platform) that other languages are free to ignore the ugliness of checked exceptions in Java (the language).
In Java, you cannot add methods to existing types. This leads to absurd object-mismodeling, as developers create utility classes that defy the point of OO:
In Java.next, you can add methods to existing types:
In Java, you have the language and the libraries. The two are clearly distinct: you can write new libraries, but you cannot add language features.
In Java.next, the line between language and libraries is blurry. You can create new constructs that work like core language features. For example, Clojure provides an
; clojure (and 1 2) => 2
But maybe your problem domain isn't so binary. You need a
most function, that returns true if most of its arguments evaluate to true. Clojure doesn't have this, but you can write one:
The point here is not "Does my language need a 'most' conditional?" Probably not. The point is that different domains have different needs. In Java.next, the boundary between the language and the libraries is a minimized. You can adapt the language to your domain, instead of the other way around.
As another example, consider Ruby's attribute syntax:
attr_accessor is built into the language.
dsl_attribute is a library method that I wrote, which allows you to omit the "=" when assigning values, e.g.
The Java.next languages share a ton of common ground. Although I've used small isolated examples for explanation, the real power comes from using these features together. Combining all the Java.next features leads to an entirely different style of coding.
In my experience, this style of coding tends to reduce the size of a codebase by an order of magnitude, while improving readability.
Many people are looking for the "next big language." The next big language is already here, but it isn't a single language. It is the collection of ideas above (plus probably some I missed) as manifested in Java.next.
Does the transition to Java.next deserve the name "big"? Absolutely. In my experience, the move from Java to Java.next is every bit as big as the previous tectonic shifts in the industry, both in learning curve and in productivity advantages once you make the transition.
As an industry, we need to reset the bar to include Java.next. Once we have, we can have a conversation about the differences in these languages. I will take up the unique aspects of the Java.next languages in future installments of this series.
BigDecimalexample does not work as I would expect on the Scala build I have (2.7.1.final). But the important point is that I could make it work by adding an implicit conversion. I am not dependent on the language designers, I can improve the language myself.
blank?example for Clojure.