Java, Scala, Ceylon: Evolution in the JVM Petri Dish
Join the DZone community and get the full member experience.
Join For FreeCeylon is a marvelous new JVM language from Gavin King, the creator of Hibernate. It enters a crowded field -- ever since Martin Odersky's pioneering Scala language demonstrated the possibility for non-Java languages to target JVM byte code, the JVM-based language scene has assumed the growth profile of an unpeeled egg microwaved five minutes on HIGH.
We now have Kotlin, Clojure, Groovy, Jython, JRuby, Mirah, Gosu, Fantom, Frege, Haxe, and a long trailing continuum of more obscure JVM-based languages in varying stages of completeness after that. Even Java itself now contains within it a complete JavaScript engine, which is a real hoot to those who may recall JavaScript's early struggles for respect as a real language, back when terms like "Turing Complete" weren't Googleable (they were only AltaVistable) and StackOverflow hadn't already cached the answer to almost anything you might dare to ask (and then closed the question as not constructive).
But from this crowded JVM field, Ceylon stands out as exceptionally well done, elegant, and likely to succeed in the "forward-looking Java developer" niche now dominated by Scala, and what's more, Ceylon's emphasis on tooling, team productivity, and readability means it could well take over some of the core Java market as well.
To be sure, many JVM languages aren't even competing in the same space; they've been designed with a different audience and purpose in mind. For instance, many don't bother with type safety, some forego performance in favor of an interpreter approach, and some have prioritized backward compatibility with Java syntax, acting more like an extension to the language (and taking all its legacy baggage along for the ride). Fantom and Groovy interestingly seem to be probing some sort of middle ground between type safety and duck typing through optional typing.
Once these Java non-competitors are taken out, as well as any that are still in a pre-1.0 release state, we are left with Java 8, Scala, Kotlin (not technically quite at 1.0 yet, but very close), Gosu, and Ceylon operating in a similar space. Let's take a look at what makes Ceylon special.
All the modern constructs
First, the ante to even be considered for this article as a new language in the Java space -- Ceylon of course has type safety, closures, generics, type inference (that one's not quite a must-have, but it's certainly nice), automatic property-like getters/setters, and some kind of Java inter-op to hook into the vast existing Java infrastructure. (Java itself gets an honorory free pass here, even though it's borderline on some of these baseline requirements).
Ceylon takes packaging seriously
Ceylon's module system automatically generates and embeds pom.xml files for Maven-style dependency management and fetching, as well as an OSGI manifest for fine-grained control over where they're visible -- if you've ever needed two different versions of the same jar for different parts of your app, you'll appreciate this. Then Ceylon's Herd repository supplies any needed dependencies not just at compile time, but also at runtime (reminiscent of the Grape system for Groovy).
Type-safe metaprogramming
Wow. I don't know any other language that has this.
Here is something that has always irked me about Java: if I wanted to refer to the name of a method or a field, there's no compiler-checked way to do this. For instance, if I'm writing an HQL Hibernate query to find Movies made in 1975 I might write "select from Movies where year = 1975", but all the promise of Java's compile-time safety just went out the window, because when I rename my "year" field to "releaseYear" nothing in the compiler will remind me to also change this query.
Even the Criteria API, for all its trouble, doesn't save me here, because it still ultimately boils down to an unchecked String in the Criterion definition. How about reflection? Nope, I can certainly use reflection to obtain an instance of Method (or in Scala I can even get a MethodSymbol instance from the AST), but here's the kicker: in order to obtain that object reference, I still need to pass in a String specifying which method I'd like a reference to, which brings me back to square one!
As a workaround, you can run any of several JPA annotation preprocessors to generate a static metamodel class from your domain objects, which you can then refer to in your code in a compiler-checked way in a subsequent build. It's ugly, but that's the best you can do in Java.
Scala until recently offered very little of its own reflective capability outside of what Java provides, but they've lately started to fill out their reflection API in the 2.10 and 2.11 releases. They've also introduced an experimental macro preprocessing API, but from what I can see the use case I've outlined is still not possible (though you may enjoy reading about the many uses of the _ symbol as you search for a way to do it). Kotlin and Gosu also can't do this (Kotlin has announced better meta-programming support as a future goal however).
Without further ado, here's an example demonstrating how to do this in the current 1.1 release of Ceylon:
class Movie (shared Integer year) {}
shared void run() {
value d = `value Movie.year`;
print( d.name); //prints "year"
}
If you misspell "year" in your run method (Ceylon's equivalent to java Main), the compiler will catch this. Beautiful! This is actually the feature that sold me, the piece de resistance, the bowl of brown-free M&Ms that signaled to me that I could relax, these guys "get it" (where "it" is defined as the things I really care about!). Meta-programming is tricky stuff, and in Ceylon, it looks remarkably clean while still remaining type-safe. The only special symbols here are the backticks, which mean "get the metamodel for the thing enclosed in these backticks."
Incidentally, Ceylon has a lot less squiggly-emoji-spaceship operators-type stuff than Scala (which can be downright Perlish). To me this is a plus for readability and ease of comprehension across a team. Ceylon also cleverly dodges the DSL bullet, another plus for team readability, while still accommodating some of the impetus behind DSLs only in a saner, more manageable way.
Ceylon can also run in JavaScript
This is not actually unique to Ceylon -- Kotlin and Fantom also offer this -- but that doesn't make it any less weirdly cool to me. Ceylon has two compiler implementations: one for the JVM and the other for JavaScript, but my sense is that the JVM still gets a little more attention however, so time will tell how well the JavaScript implementation keeps pace. I imagine this could be a great feature for integrating the work of front- and back-end teams though, and in any case, it's nice being able to run examples from the tutorial right in your browser. It also gives me hope that there might one day be other implementations, perhaps not even bound to a JVM? (To be sure, I asked the Ceylon team about this possibility; there are no plans for this in the roadmap at this time).
Some great ideas in the type system
For the most part, there hasn't been much innovation in type safety among mainstream languages after Scala's inferred types. Gosu interestingly allows its extensible type system to incorporate elements that aren't traditionally type-checked, such as your XML config file, but when it comes to how types are handled within the program itself, post-Scala languages seem to have liked and adopted Scala's inferred type declarations, but then looked at Scala's generics support and concluded it was the limit of what's practical, and finally settled on regressing from that extreme to regain simplicity at the cost of some safety and correctness.
But Ceylon looked at the problem with fresh eyes and found brilliantly simple improvements that resulted in an even stronger, safer, and simpler type system than Scala's! Incredible!
A key innovation is Ceylon's introduction of union, intersection and enumerated types, which basically let you combine existing types into an aggregate type (and such types can themselves be again similarly aggregated -- think of the expressive power of a tree). This master stroke is surprisingly useful!
For example, the union type is how Ceylon tracks what is effectively nullable, by establishing the rule that the Null class can only ever be assigned to a type that is unioned with Null. Such types can easily be declared on-the-fly as needed -- here's how to declare a type that is nullable:
//convenient shorthand way to declare a Nullable type by following it with a question mark
YourTypeHere?
//the above is just syntax sugar for this
YourTypeHere|Null
The compiler then forces any instance of this type to be checked for Null before being used. The result: NullPointerExceptions are only ever possible in Ceylon when it interfaces with Java, and they are not possible in a pure Ceylon program. How about that, no more NPEs! (Incidentally, Kotlin also offers a similar ? syntax to prevent NPEs, but I like how generalized Ceylon's approach is, where NPEs are just one of a range of problems addressed by the union type).
Another example of Ceylon's clever type innovation in action is its tuple (a tuple is a list containing mixed types - no big deal for duck-typed programs like Ruby/JRuby or Python/Jython or Lisp/Clojure, but historically problematic for strongly typed languages like Java). Ceylon implements tuples as nested unions, which allow Ceylon to provide tuples of arbitrary length.
By contrast, Scala's tuples have arbitrary length in a different way -- they're arbitrarily capped at 22! If you'd like to use a 23-element tuple in Scala, you're out of luck! Java and Gosu meanwhile don't provide tuples at all, and Kotlin got rid of them as too bothersome. Of course, with the ease of defining impromptu classes some of the need for tuples is alleviated in all these languages, but tuples still prove useful when supporting functional ideas like currying, and the fact that Ceylon alone could provide them so easily without any special language constructs is a testament to the power of its type system.
Generics and the JVM holy grail: type reification
Generics are where type systems generally unravel, fast. They're also good at unravelling high-level reviews like this one, so suffice it to say that Java made serious compromises in its approach to generics, with the excuse that this was the best that could be done without changing the JVM (alternative JVM-language authors have been busy proving them wrong ever since). Ceylon has again made clever insights to simplify matters even further here, and along the way it somehow also delivered reified generics (the ability to recover generically compiled type information at runtime, a seemingly impossible feat given Java's type-erasure architecture whereby generic types are discarded during compilation). Gosu is the only other language which has this ability, which simplifies a wide range of programming problems. I don't know through what voodoo either one was implemented, but am impressed to see it there.
Everything's an object
People tend to love this about Ruby. I love this about Ceylon too. It's consistent and egalitarian... no special magic reserved only for Ceylon. Most symbols like [] and + are just shorthand ways to invoke underlying method calls with all the flexibility and benefits of object orientation. So for example the expression 2 + 3 is really just shorthand for 2.plus(3):
print( 2.plus(3) ); //prints 5
Ceylon's online Javadoc-equivalent includes source code
Awesome! When the docs aren't enough, just drop into the source code and read it -- nothing to download or sync up, it's included on every page of their core API doc as a link in the top right corner.
Getters and setters are seamless
From the day that I was told to use getters and setters in Java, I felt irritated that they would require a different syntax from an ordinary field access (something C# with the benefit of coming second did a better job with).
Scala, Kotlin, Gosu and Ceylon all do a better job with getters/setters than Java. But Ceylon adds a nice touch: it not only lets you seamlessly add getters/setters when you need them to class fields, it also allows this for any value, regardless of class encapsulation. In Ceylon, a direct reference is indistinguishable to the calling code from one with an intercepting getter/setter:
// a regular, directly referenced value
Integer i = 5;
print(i.string); //prints 5
// this value uses a getter/setter, with a backing variable to hold its state.
variable Integer jstate = 5;
Integer j {
return jstate * 2;
}
assign j {
jstate = j-1;
}
j = 5;
print(j.string); //prints 8
Notice how nothing changed in the calling code using the plain value i versus the getter/setter-enhanced value j? That's how it should be. Incidentally, you may have noticed that values with getters/setters can no longer hold data, they must proxy back to another object to hold their data. This seems like a harmless if needless restriction at first, but really it makes sense from a syntactic point of view; otherwise how would you ever be able to access the unadulterated underlying data, given how indistinguishable the raw vs get/set data accessors are?
One thing I've always wished for in Java is a generalization of this seamless wrapping ability, so that all method calls in a class might be optionally wrapped with another pair of methods (one just before, the other just after the method call). This would eliminate much of the need for AOP and a lot of other Java workarounds, and generally ease implementation of decorator patterns. Ruby/JRuby have this capability; maybe one day, one of the type checked JVM languages can get a variant of it too. Until then, I'm glad to see the improvement of Ceylon's getters/setters.
Finally, my teeny tiny complaint
My only complaint, and its triviality shows how far I have to dig to find anything to complain about, is that whereas Scala and Kotlin both went with the short "var" and "val" keywords to distinguish mutable vs immutable variables, Ceylon chose the longhand "variable" and "value." I can live with it :)
Of course a more practical downside to consider before adopting Ceylon is its immaturity, but the same goes for any of the type-safe languages under discussion here other than the venerable Java, and perhaps Scala now as well. Many significant libraries and frameworks have yet to be written for Ceylon, so you'll still need to use its Java interop for things like multi-threading, database access, or any number of enterprise-y endeavors. But many good things are in the pipeline in all these areas, and on the plus side Ceylon already comes with a high-quality Eclipse plugin with full syntax completion, a mark of maturity that more typically comes later in a language's lifetime.
Conclusion
When James Gosling created Java, it was a huge advance from what existed at the time. Java's impact on the world can hardly be overstated, but perhaps greater still was the backdoor gift it came with, the JVM. While Java grows long in the tooth, a number of languages have arisen to make use of its twin gift, the JVM. Of these next-gen languages, all have their merits, but Ceylon seems an especially promising contender to perhaps one day replace Java. Whether Java can catch up or perhaps take a page from the old Microsoft playbook and "embrace and extend" these features, only time will tell. In the meantime, Ceylon has delivered an exciting realization of what is already possible on the JVM right now.
Opinions expressed by DZone contributors are their own.
Trending
-
Automating the Migration From JS to TS for the ZK Framework
-
Effortlessly Streamlining Test-Driven Development and CI Testing for Kafka Developers
-
Never Use Credentials in a CI/CD Pipeline Again
-
Seven Steps To Deploy Kedro Pipelines on Amazon EMR
Comments