Java.next: The Java.next languages

Leveraging Groovy, Scala, and Clojure in an increasingly polyglot world

This article launches a new developerWorks series by Neal Ford that performs a deep comparison of three next-generation JVM languages: Groovy, Scala, and Clojure. In this initial installment, find out what you'll gain from understanding their similarities and differences whether or not you choose to keep using Java as your main programming language for now.

16 April 2013 - Added links to "Common ground in Groovy, Scala, and Clojure, Part 1" and "Common ground in Groovy, Scala, and Clojure, Part 2" in Resources.

14 May 2013 - Added a link to "Common ground in Groovy, Scala, and Clojure, Part 3" in Resources.

Share:

Neal Ford, Director / Software Architect / Meme Wrangler, ThoughtWorks Inc.

Neal FordNeal Ford is Director, Software Architect, and Meme Wrangler at ThoughtWorks, a global IT consultancy. He is also the designer and developer of applications, instructional materials, magazine articles, courseware, and video/DVD presentations, and he is the author or editor of books spanning a variety of technologies, including the most recent Presentation Patterns. He focuses on designing and building large-scale enterprise applications. He is also an internationally acclaimed speaker at developer conferences worldwide. Check out his website.



14 May 2013 (First published 29 January 2013)

Also available in Chinese Russian Japanese Vietnamese

In a keynote address that I once co-presented with Martin Fowler, he made a perceptive observation:

The legacy of Java will be the platform, not the language.

The original engineers of Java technology made a brilliant decision to separate the language from the runtime, ultimately enabling more than 200 languages to run on the Java platform. This architecture is crucial for the platform's long-term vitality, because computer programming languages typically have short lifespans. Since 2008, the annual JVM Language Summit, hosted by Oracle, has given implementers of alternative languages on the JVM an opportunity to collaborate openly with Java platform engineers.

Welcome to the Java.next column series. In it, I profile three modern JVM languages — Groovy, Scala, and Clojure — that offer an interesting mix of paradigm, design choice, and comfort factor. I won't spend time here providing deep descriptions of each language; those are available on their respective websites (see Resources). But language-community websites — whose primary purpose is evangelism — lack objective information or examples of tasks a language is ill-suited for. The substantive comparisons I'll perform in this series are intended to help fill that void. This article sets the stage with an overview of the Java.next languages and the benefits of learning about them.

About this series

Java's legacy will be the platform, not the language. More than 200 languages run on the JVM, each bringing interesting new capabilities beyond those of the Java language. This series explores three next-generation JVM languages — Groovy, Scala, and Clojure — comparing and contrasting new capabilities and paradigms. The series aims to give Java developers a glimpse into their own near future — and help them make educated choices about the time they devote to new-language learning.

Beyond Java

The Java language rose to prominence through what Bruce Tate, in his book Beyond Java (see Resources), calls a perfect storm: the combined factors of the rise of the web, the unsuitability of existing web technologies for various reasons, and the rise of multitiered application development by enterprises. Tate also argues that this perfect storm was a unique series of events, and that no other language will ever rise to the same relative prominence in the same way.

The Java language has proved quite elastic in capabilities, but its syntax and inherent paradigms have long-understood limitations. Despite the promising changes that are coming to the language, the syntax simply can't support some important future goals, such as elements of functional programming. But if you're trying to find a single new language to displace Java, you're searching for the wrong thing.

Polyglot programming

Polyglot programming — a term that I resurrected and repopularized in a 2006 blog entry (see Resources) — is based on the realization that no single language is suitable for solving every problem. Some languages have built-in perspectives or features that fit specific problems better. For example, as sophisticated as Swing is, developers find it incredibly cumbersome to write Swing UIs in Java, because it requires type declarations, clumsy anonymous inner classes for behavior, and other friction. Using a language that's better-suited to building UIs, such a Groovy with its SwingBuilder facilities (see Resources), makes building Swing applications much more palatable.

The proliferation of languages run on the JVM makes the idea of polyglot programming all the more compelling, because you can mix and match while maintaining the same underlying byte code and libraries. For example, SwingBuilder doesn't replace Swing; it layers atop the existing Swing API. Of course, for a long time, developers have been mixing languages outside the JVM — for example, by using SQL and JavaScript for specialized purposes — but it is becoming more prevalent within the JVM boundaries. Many ThoughtWorks projects incorporate multiple languages, and all the tools developed by ThoughtWorks Studios use mixed languages.

Even if Java remains your primary development language, learning how alternative languages work enables you to incorporate them strategically. Java will remain an important part of the JVM ecosystem, but ultimately more as the platform's assembly language — a place you go purely for performance reasons or to meet specialized requirements.

Evolution

In the early 1980s, when I was in university, we used a development environment called Pecan Pascal. Its unique feature was that the same Pascal code could run on either the Apple II or IBM PC. The Pecan engineers achieved this feat by using something mysterious called "byte code." Developers compiled their Pascal code to this "byte code," which ran on a "virtual machine" written natively for each platform. It was a hideous experience! The resulting code was achingly slow even for simple class assignments. The hardware at the time just wasn't up to the challenge.

A decade after Pecan Pascal, Sun released Java using the same architecture, straining but succeeding in mid-1990s hardware environments. It also added other developer-friendly features such as automatic garbage collection. Having worked in languages like C++, I never want to code in a non-garbage-collected language again. I'd rather spend time at a higher level of abstraction thinking about ways to solve complex business problems, not complicated plumbing problems like memory management.

One of the reasons that computer languages typically don't have long lives is the rate of innovation in language and platform design. As our platforms have become more powerful, they can handle more busywork. For example, Groovy's memoization feature (added in 2010) caches the results of function calls. Rather than hand-writing caching code, potentially introducing bugs, you need merely call the memoize() method, as shown in Listing 1:

Listing 1. Memoizing a function in Groovy
def static sum = { number ->
  factorsOf(number).inject(0, {i, j -> i + j})
}
def static sumOfFactors = sum.memoize()

In Listing 1, the results from the sumOfFactors method are automatically cached. You can also customize the caching behavior with alternative methods, memoizeAtLeast() and memoizeAtMost(). Clojure also includes memoization, and it's trivial to implement in Scala. Advanced features such as memoization that exist in next-generation languages (and in some Java frameworks) will gradually find their way into the Java language. The next release of Java will add higher-order functions, making memoization much easier to implement. By studying next-generation Java languages, you get a sneak peak into future Java features.


Groovy, Scala, Clojure

Groovy is twenty-first-century Java syntax — espresso instead of regular coffee. Groovy's design goals are to update and remove friction from Java's syntax while supporting the primary paradigms in the Java language. Thus, Groovy "knows" about such things as JavaBeans, simplifying property access. Groovy incorporates new features at a rapid rate, including some important functional features I'll highlight in future installments. Groovy is still primarily an object-oriented, imperative language. Two fundamental differences from Java are that Groovy is dynamically rather than statically typed, and that its metaprogramming capabilities are significantly better.

Scala is a language designed from the ground up to take advantage of the JVM, but with a completely redesigned syntax. Scala is a strongly, statically typed language — more stringently typed than Java yet less obnoxious about it — that supports both object-oriented and functional paradigms, favoring the latter. For example, Scala prefers val declarations, yielding an immutable variable (similar to flagging a parameter as final in Java) to vars, which create more-familiar mutable variables. Scala provides a bridge from what you likely are (an object-oriented imperative programmer) to what you likely should be (a more functional programmer) by supporting both paradigms deeply.

Clojure is the most radical syntactic departure from the other languages, being a dialect of Lisp. A strongly, dynamically typed language (like Groovy), it reflects opinionated design decisions. Although Clojure allows you full and deep interoperability with legacy Java, it isn't trying to build a bridge from the older paradigm. For example, Clojure is unapologetically functional and supports object orientation to allow interoperability. Yet it supports all the features that object-oriented programmers are accustomed to, such as polymorphism — but in a functional rather than object-oriented manner. Clojure is designed around a few core engineering principles, such as Software Transactional Memory, that break old paradigms in favor of new capabilities.


Paradigms

Outside of syntax, the most interesting differences among these languages concern typing and the underlying primary paradigm: functional or imperative.

Static vs. dynamic typing

Static typing in programming languages refers to explicit type declarations, such as Java's int x; declaration. Dynamic typing refers to languages that don't require type information for declaration. All the languages under consideration are strongly typed languages, meaning that your code can reflect on the type after assignment.

Java's type system has been widely criticized as providing many of the inconveniences of static typing without enough of the benefits. For example, before the limited type inferencing currently available, Java required developers to repeat type declarations on both sides of assignments. Scala is more statically typed than Java but much less cumbersome in daily use, because it makes heavy use of type inferencing.

Groovy has one behavior that at first glance seems to bridge the static vs. dynamic divide. Consider the simple collection factory shown in Listing 2:

Listing 2. Groovy collection factory
class CollectionFactory {
  def List getCollection(description) {
    if (description == "Array-like")
      new ArrayList()
    else if (description == "Stack-like")
      new Stack()
  }
}

The class in Listing 2 acts as a factory, returning either of the two List interface implementers — ArrayList or Stack — based on the passed description parameter. To Java developers, this appears to ensure that whatever is returned meets that contract. However, the two unit tests in Listing 3 reveal a complication:

Listing 3. Testing collection types in Groovy
@Test
void test_search() {
  List l = f.getCollection("Stack-like")
  assertTrue l instanceof java.util.Stack
  l.push("foo")
  assertThat l.size(), is(1)
  def r = l.search("foo")
}

@Test(expected=groovy.lang.MissingMethodException.class) 
void verify_that_typing_does_not_help() {
  List l = f.getCollection("Array-like")
  assertTrue l instanceof java.util.ArrayList
  l.add("foo")
  assertThat l.size(), is(1)
  def r = l.search("foo")
}

In the first unit test in Listing 3, I retrieve a Stack via the factory, verify that it is indeed a Stack, then perform Stack-like operations such as push(), size(), and search(). However, in the second unit test, I must guard the test with a MissingMethodException expected exception to pass the test. When I retrieve my Array-like collection into a variable typed as a List, I can verify that I do indeed receive a list in return. However, when I try to call the search() method, it triggers an exception because ArrayList doesn't include a search() method. Thus, the declaration provided no compile-time protection ensuring that the method invocation was correct.

Although this might seem like a bug, it's proper behavior. Types in Groovy only ensure the validity of the assignment statement. For example, in Listing 3, if I returned something that didn't implement the List interface, a runtime exception (GroovyCastException) would be triggered. This places Groovy firmly in the strongly, dynamically typed family with Clojure.

However, recent changes to the language have made the static vs. dynamic divide in Groovy even blurrier. Groovy 2.0 added a @TypeChecked annotation, which lets you make ad hoc decisions about the strictness of type checking, at the class or method level. Listing 4 illustrates this annotation:

Listing 4. Type checking via annotation
@TypeChecked
@Test void type_checking() {
    def f = new CollectionFactory()
    List l = f.getCollection("Stack-like")
    l.add("foo")
    def r = l.pop()
    assertEquals r, "foo"
}

In Listing 4, I add the @TypeChecked annotation, which verifies both assignment and subsequent method invocation. For example, the code in Listing 5 will no longer compile:

Listing 5. Type checking preventing invalid method invocation
@TypeChecked
@Test void invalid_type() {
    def f = new CollectionFactory()
    Stack s = (Stack) f.getCollection("Stack-like")
    s.add("foo")
    def result = s.search("foo")
}

In Listing 5, I must add the type cast for the return from the factory to allow me to call Stack's search() method. This facility comes with restrictions: Many of Groovy's dynamic features won't work when static typing is enabled. However, this example illustrates Groovy's continuing evolution in bridging the static vs. dynamic divide.

All of these languages have quite powerful metaprogramming facilities, so more-stringent typing can be added after the fact. For example, several side projects exist to add selective typing to Clojure. Generally, though, if selective typing is optional, it's not part of the type system; it's a verification mechanism.

Imperative vs. functional

The other main comparison axis is imperative vs. functional. Imperative programming focuses on step-by-step instructions, in many cases mimicking ancient low-level hardware conveniences. Functional programming focuses more on functions as first-class constructs and tries to minimize state transitions and mutability.

Groovy, being heavily inspired by Java, is primarily an imperative language. But from the outset, many functional language features were included, and more have been added over time.

Scala bridges these two paradigms, supporting both. While preferring (and encouraging) functional programming, Scala still supports object-orientation and imperative programming. Thus, using Scala properly requires a disciplined team to ensure that you don't mix and match paradigms haphazardly, which is always a danger in multiparadigm languages.

Clojure is unapologetically functional. It supports object orientation to allow it to easily interact with other JVM languages, but it's not trying to be a bridge. Rather, the opinionated design of Clojure makes a statement about what the designer thinks are good engineering practices. Those decisions have far-reaching implications, enabling Clojure to solve some nagging problems in the Java world (such as concurrency) in groundbreaking ways.

Many of the shifts in thinking required to learn these new languages will come from this imperative/functional divide, and it's one of the richest areas of exploration in this series.


Conclusion

Developers live in an increasingly polyglot world where multiple languages are employed to solve problems. Learning to leverage new languages effectively helps you determine when this approach is appropriate. Even if you don't leave Java, it will gradually incorporate features from next-generation JVM languages; looking at them now gives you a sneak peak at the future of the Java language.

In the next Java.next installment, I'll start the comparison by looking at what Groovy, Scala, and Clojure all have in common.

Resources

Learn

Get products and technologies

Discuss

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into Java technology on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Java technology, Open source
ArticleID=855992
ArticleTitle=Java.next: The Java.next languages
publish-date=05142013