Java Magazine Logo
Originally published in the July/August 2014 issue of Java Magazine. Subscribe today.

Alternative Languages for the JVM

by Raoul-Gabriel Urma

Published July 2014

A look at eight features from eight JVM languages

The Java Virtual Machine (JVM) isn’t just for Java anymore. Several hundred JVM programming languages are available for your projects. These languages ultimately compile to bytecode in class files, which the JVM can then execute. As a result, these programming languages benefit from all the optimizations available on the JVM out of the box.

The JVM languages fall into three categories: They have features that Java doesn’t have, they are ports of existing languages to the JVM, or they are research languages.

The first category describes languages that include more features than Java and aim to let developers write code in a more concise way. Java SE 8 introduced lambda expressions, the Stream API, and default methods to tackle this issue of conciseness. However, developers love many other features—such as collection literals, pattern matching, and a more sophisticated type inference—that they can’t find in Java yet. The languages we’ll look at in this first category are Scala, Groovy, Xtend, Ceylon, Kotlin, and Fantom.

The second category is existing languages that were ported to the JVM. Many languages, such as Python and Ruby, can interact with Java APIs and are popular for scripting and quick prototyping. Both the standard implementation of Python (CPython) and Ruby (Ruby MRI) feature a global interpreter lock, which prevents them from fully exploiting a multicore system. However, Jython and JRuby—the Python and Ruby implementations on the JVM—get rid of this restriction by making use of Java threads instead. (You can read more about JRuby and JRubyFX in this issue’s “JavaFX with Alternative Languages” article by Josh Juneau. Juneau also covers Jython extensively on his blog.)

Another popular language ported to the JVM is Clojure, a dialect of Lisp, which we’ll look at in this article. In addition, Oracle recently released Nashorn, a project that lets you run JavaScript on the JVM.

The third category is languages that implement new research ideas, are suited only for a specific domain, or are just experimental. The language that we’ll look at in this article, X10, is designed for efficient programming for high-performance parallel computing. Another language in this category is Fortress from Oracle Labs, now discontinued.

For each language we examine, one feature is presented to give you an idea of what the language supports and how you might use it.

1 | Scala

Scala is a statically typed programming language that fuses the object-oriented model and functional programming ideas. That means, in practice, that you can declare classes, create objects, and call methods just like you would typically do in Java. However, Scala also brings popular features from functional programming languages such as pattern matching on data structures, local type inference, persistent collections, and tuple literals.

The fusion of object-oriented and functional features lets you use the best tools from both worlds to solve a particular problem. As a result, Scala often lets programmers express algorithms more concisely than in Java.

Feature focus: pattern matching. To illustrate, take a tree structure that you would like to traverse. Listing 1 shows a simple expression language consisting of numbers and binary operations.

[Java]

class Expr { ... }
class Number extends Expr { int val; ... }
class BinOp extends Expr { String opname; Expr left, right; ... }

Listing 1

Say you’re asked to write a method to simplify some expressions. For example “5 / 1” can be simplified to “5.” The tree for this expression is illustrated in Figure 1.

Architect-languages-f1
Figure 1

In Java, you could deconstruct this tree representation by using instanceof, as shown in Listing 2. Alternatively, a common design pattern for separating an algorithm from its domain is the visitor design pattern, which can alleviate some of the verbosity. See Listing 3.

[Java]

Expr simplifyExpression(Expr expr) {
    if (expr instanceof BinOp
          && "/".equals(((BinOp)expr).opname)
          && ((BinOp)expr).right instanceof Number
          && ... // it’s all getting very clumsy
          && ... ) {
        return (Binop)expr.left;
     }
     ... // other simplifications
}

Listing 2

[Java]

public class SimplifyExprVisitor {
    ...
    public Expr visit(BinOp e){
        if("/".equals(e.opname) && 
e.right instanceof Number && ...){
            return e.left;
        }
        return e;
    }
}

Listing 3

However, this pattern introduces a lot of boilerplate. First, domain classes need to provide an accept method to use a visitor. You then need to implement the “visit” logic.

In Scala, the same problem can be tackled using pattern matching. See Listing 4.

[Scala]

def simplifyExpression(expr: Expr): Expr = expr match {
    case BinOp("+", e, Number(0)) => e   // Adding zero
    case BinOp("*", e, Number(1)) => e   // Multiplying by one
    case BinOp("/", e, Number(1)) => e   // Dividing by one
    case _ => expr                       // Can’t simplify expr
}

Listing 4

2 | Groovy

Groovy is a dynamically typed object-oriented language. Groovy’s dynamic nature lets you manipulate your code in powerful ways. For example, you can expand objects at runtime (for example, by adding fields or methods).

However, Groovy also provides optional static checking, which means that you can catch errors at compile time (for example, calling an undefined method will be reported as an error before the program runs, just as in Java). As a result, programmers who feel that they are more productive without types getting in their way can embrace Groovy’s dynamic nature. Nonetheless, they can also opt to gradually use static checking later if they wish. In addition, Groovy is friendly to Java programmers because almost all Java code is also valid Groovy code, so the learning curve is small.

Feature focus: safe navigation. Groovy has many features that let you write more-concise code compared to Java. One of them is the safe navigation operator, which prevents a NullPointerException. In Java, dealing with null can be cumbersome. For example, the following code might result in a NullPointerException if either person is null or getCar() returns null:

Insurance carInsurance = 
person.getCar().getInsurance();

To prevent an unintended NullPointerException, you can be defensive and add checks to prevent null dereferences, as shown in Listing 5.

[Java]

Insurance carInsurance = null;
if(person != null){
    Car car = person.getCar();
    if(car != null){
        carInsurance = 
            car.getInsurance();
    }
}

Listing 5

However, the code quickly becomes ugly because of the nested checks, which also decrease the code’s readability. The safe navigation operator, which is represented by ?., can help you navigate safely through potential null references:

def carInsurance = 
person?.getCar()?.getInsurance()

In this case, the variable carInsurance will be null if person is null, getCar() returns null, or getInsurance() returns null. However, no NullPointerException is thrown along the way.

3 | Clojure

Clojure is a dynamically typed programming language that can be seen as a modern take on Lisp. It is radically different from what object-oriented programmers might be used to. In fact, Clojure is a fully functional programming language, and as a result, it is centered on immutable data structures, recursion, and functions.

Feature focus: homoiconicity. What differentiates Clojure from most languages is that it’s a homoiconic language. That is, Clojure code is represented using the language’s fundamental datatypes—for example, lists, symbols, and literals—and you can manipulate the fundamental datatypes using built-in constructs. As a consequence, Clojure code can be elegantly manipulated and transformed by reusing the built-in constructs.

Clojure has a built-in if construct. It works like this. Let’s say you want to extend the language with a new construct called unless that should work like an inverted if. In other words, if the condition that is passed as an argument evaluates to false, Clojure evaluates the first branch. Otherwise—if the argument evaluates to true—Clojure evaluates the second branch. You should be able to call the unless construct as shown in Listing 6.

[Clojure]

(unless false (println "ok!!") (println "boo!!")) 
; prints "ok!!"

(if false (println "boo!!") (println "ok!!")) 
; prints "ok!!"

Listing 6

To achieve the desired result you can define a macro that transforms a call to unless to use the construct if, but with its branch arguments reversed (in other words, swap the first branch and the second branch). In Clojure, you can manipulate the code representing the branches that are passed as an argument as if it were data. See Listing 7.

[Clojure]

(defmacro unless
  "Inverted 'if'"
  [condition & branches]
  (conj (reverse branches) condition 'if))

Listing 7

In this macro definition, the symbol branches consists of a list that contains the two expressions representing the two branches to execute (println "boo!!" and println "ok!!"). With this list in hand, you can now produce the code for the unless construct. First, call the core function reverse on that list. You’ll get a new list with the two branches swapped. You can then use the core function conj, which when given a list, adds the remaining arguments to the front of the list. Here, you pass the if operation together with the condition to evaluate.

4 | Kotlin

Kotlin is a statically typed object-oriented language. Its main design goals are to be compatible with Java’s API, have a type system that catches more errors at compile time, and be less verbose than Java. Kotlin’s designers say that Scala is a close choice to match its design goals, but they dislike Scala’s complexity and long compilation time compared to Java. Kotlin aims to tackle these issues.

Three Types

The JVM languages fall into three categories: They have features that Java doesn’t have, they are ports of existing languages to the JVM, or they are research languages.

Feature focus: smart casts. Many developers see the Java cast feature as annoying and redundant. For an example, see Listing 8.

[Java]

if(expr instanceof Number){
    System.out.println(((Number) expr).getValue());
}

Listing 8

Repeating the cast to Number shouldn’t be necessary, because within the if block, expr has to be an instance of Number. The generality of this technique is called flow typing—type information propagates with the flow of the program.

Kotlin supports smart casts. That is, you don’t have to cast the expression within the if block. See Listing 9.

[Kotlin]

if(expr is Number){
    println(expr.getValue())
// expr is automatically cast to Number
}

Listing 9

5 | Ceylon

Red Hat developed Ceylon, a statically typed object-oriented language, to give Java programmers a language that’s easy to learn and understand (because of syntax that’s similar to Java) but less verbose. Ceylon includes more type system features than Java. For example, Ceylon supports a construct for defining type aliases (similar to C’s typedef; for example, you could define Strings to be an alias for List<String>), flow typing (for example, no need to cast the type of an expression in a block if you’ve already done an instanceof check on it), union of types, and local type inference. In addition, in Ceylon you can ask certain variables or blocks of code to use dynamic typing—type checking is performed at runtime instead of compile time.

Feature focus: for comprehensions. for comprehensions can be seen as syntactic sugar for a chain of map, flatMap, and filter operations using Java SE 8 streams. For example, in Java, by combining a range and a map operation, you can generate all the numbers from 2 to 20 with a step value of 2, as shown in Listing 10.

[Java]

List<Integer> numbers = IntStream.rangeClosed(1, 10).mapToObj(
x -> x * 2).collect(toList());

Listing 10

In Ceylon, it can be written as follows using a for comprehension:

List<Integer> numbers = 
[for (x in 1...10) x * 2];


Here’s a more-complex example. In Java, you can generate a list of points in which the sum of the x and y coordinates is equal to 10. See Listing 11.

[Java]

List<Point> points = IntStream.rangeClosed(1, 10).boxed()
.flatMap(x -> IntStream.rangeClosed(1, 10)
  .filter(y -> x + y == 10)
  .mapToObj(y -> new Point(x, y)))
  .collect(toList());

Listing 11

Thinking in terms of flatMap and map operations using the Stream API might be overwhelming. Instead, in Ceylon, you can write more simply, as done in the code shown in Listing 12, which produces [(1, 9), (2, 8), (3, 7), (4, 6), (5, 5), (6, 4), (7, 3), (8, 2), (9, 1)].

[Ceylon]
List<Point> points = 
   [for (x in 1..10) for(y in 1..10) 
   if(x+y == 10) Point(x, y)];

Listing 12

The result: Ceylon can make your code more concise.

6 | Xtend

Xtend is a statically typed object-oriented language. One way it differs from other languages is that it compiles to pretty-printed Java code rather than bytecode. As a result, you can also work with the generated code.

Xtend supports two forms of method invocation: default Java dispatching and multiple dispatching. With multiple dispatching, an overloaded method is selected based on the runtime type of its arguments (instead of the traditional static types of the arguments, as in Java). Xtend provides many other popular features available in other languages such as operator overloading and type inference.

One unique feature is template expressions, which are a convenient way to generate string concatenation (similar to what template engines provide). For example, template expressions support control-flow constructs such as IF and FOR. In addition, special processing of white space allows templates to be readable and their output to be nicely formatted.

Feature focus: active annotations. Xtend provides a feature called active annotations, which is a way to do compile-time metaprogramming. In its simplest form, this feature allows you to generate code transparently, such as adding methods or fields to classes with seamless integration in the Eclipse IDE for example. New fields or meth-ods will show up as members of the modified classes within the Eclipse environment. More-advanced use of this feature can generate a skeleton of design patterns such as the visitor or observer pattern. You can provide your own way to generate code using template expressions.

Here’s an example to illustrate this feature in action. Given sample JSON data, you can automatically generate a domain class in your Xtend program that maps JSON properties into members. The Eclipse IDE will recognize these members, so you can use features such as type checking and autocompletion. All you have to do is wrap the JSON sample within an @Jsonized annotation. Figure 2 shows an example within the Eclipse IDE using a JSON sample representing a tweet.

Architect-languages-f2

Figure 2

7 | Fantom

Fantom is an object-oriented language featuring a type system that takes an alternative view compared to most other established, statically typed languages. First, it differentiates itself by not supporting user-defined generics. However, three built-in classes can be parameterized: List, Map, and Func. This design decision was made to let programmers benefit from the use of generics (such as working with collections—see the link to an empirical study conducted by Parnin et al. in “Learn More”) without complicating the overall type system. In addition, Fantom provides two kinds of method invocations: one that goes through type checking at compile time (using a dot notation: .) and one that defers checking to runtime (using an arrow notation: ->).

Feature focus: immutability. Fantom encourages immutability through language constructs. For example, it supports const classes—once created, an instance is guaranteed to have no state changes. Here’s how it works. You can define a class Transaction prefixed with the const keyword:

const class Transaction {
  const Int value
}


The const keyword ensures that the class declares only fields that are immutable, so you won’t be able to modify the field named value after you instantiate a Transaction. This is not much different than declaring all fields of a class final in Java. However, this feature is particularly useful with nested structures. For example, let’s say the Transaction class is modified to support another field of type Location. The compiler ensures that the location field can’t be reassigned and that the Location class is immutable.

For instance, the code in Listing 13 is incorrect and will produce the error Const field 'location' has non-const type 'hello_0::Location'. Similarly, all classes extending a const class can be only const classes themselves.

[Fantom]

const class Transaction {
  const Int value
  const Location location := Location("Cambridge")
}
class Location{
  Str city
  new make(Str city) { this.city = city }
} 


8 | X10

X10 is an experimental object-oriented language that IBM developed. It supports features such as first-class functions and is designed to facilitate efficient programming for high-performance parallel computing.

Learn More


 Java Generics Adoption: How New Features Are Introduced, Championed, or Ignored

To this end, the language is based on a programming model called the partitioned global address space. In this model, each process shares a global address space, and slices of this space are allocated as private memory for local data and access. To work with this model, X10 offers specialized built-in language constructs to work with concurrency and distributed execution.

Compared to popular object-oriented languages, a novel feature in its type system is support for constraint types. You can think of constraint types as a form of contracts attached to types. What makes this useful is that errors are checked statically, eliminating the need for more-expensive runtime checks. For example, one possible application of constraint types is to report out-of-bound array accesses at compile time.

Feature focus: constraint types. Consider a simple Pair class, with a generated constructor:

class Pair(x: Long, y: Long){}

You can create Pair objects as follows:

val p1 : Pair = new Pair(2, 5);

However, you can also define explicit constraints (similar to contracts) on the properties of a Pair at use-site. Here, you want to ensure that p2 holds only symmetric pairs (that is, the values of x and y must be equal):

val p2 : Pair{self.x == self.y} 
= new Pair(2, 5);

Because x and y are different in this code example, the assignment will be reported as a compile error. However, the following code compiles without an error:

val p2 : Pair{self.x == self.y} 
= new Pair(5, 5); 


Conclusion

In this article, we examined eight features from eight popular JVM languages. These languages provide many benefits, such as enabling you to write code in a more concise way, use dynamic typing, or access popular functional programming features.

I hope this article has sparked some interest in alternative languages and that it will encourage you to check out the wider JVM eco-system.

Acknowledgements. I’d like to thank Alex Buckley, Richard Warburton, Andy Frank, and Sven Efftinge for their feedback.


RaoulGabrielUrma-headshot



Raoul-Gabriel Urma
started his PhD in computer science at the University of Cambridge at the age of 20. He is a coauthor of Java 8 in Action: Lambdas, Streams, and Functional-Style Programming (Manning Publications, 2014). In addition, he has given more than 20 technical talks at international conferences. He holds a MEng degree in computer science from Imperial College London and graduated with first-class honors, having won several prizes for technical innovation.