20 Coolest Features of Non-Java JVM Languages
Java has been a key player in the programming landscape since it was created in the mid-90s. One of the big attractions of Java is its «write once, run anywhere» execution model, where Java source code is compiled to bytecode by the compiler. This bytecode can later be executed by the Java Virtual Machine (JVM), which converts the Java bytecode to machine-specific instructions. Separating the writing of code from the execution of code into two domains means that Java developers don’t have to write architecture-specific instructions. They can just write Java code, and expect that the people building the JVM will take care of all of the machine-specific nuances. Of course, the JVM doesn’t know how the bytecode was generated, just that it’s supposed to run it. Language designers soon realised that this meant they could create new languages which ran on the JVM, provided they created intermediate compilers that could convert those languages into Java bytecode. And so, almost immediately after the first version of Java was released, new JVM languages started to appear. What follows are some of the coolest features I’ve found in these non-Java JVM languages.
A small clarification: some of the features below are found in multiple languages on this list. Languages which developed later are naturally influenced by those that came before them, and languages which have more resources behind them can incorporate more features. This is why — for instance — the feature set of Kotlin (funded by JetBrains, which charge €500/year for their IDE, IntelliJ) is essentially a superset of all of the features of other popular modern languages like Scala and Groovy. I made no attempt to uncover which language first implemented which particular feature below, rather, I wanted to simply highlight some interesting features which haven’t (yet) been incorporated into Java, to the best of my knowledge. If you find any mistakes below, or know of any other neat features available in non-Java JVM languages, please let me know in the comments!
Table of Contents
- Ioke — homoiconicity
- Gosu — open type system
- Gosu — optional and named parameters
- Prompto — standard dialects
- Frege and Eta — «Haskell for the JVM»
- Ateji PX — parallel blocks
- Fantom — «once» methods and lots of literals
- Whiley — verification via formal specification
- Whiley — flow-sensitive typing
- Jython & JRuby — language interoperability
- Clojure — «LISP for the JVM»
- Scala — «everything is an object»
- Scala — everything is an expression
- Scala — case classes and pattern matching
- Scala — implicit programming
- Groovy — partial application and composition of functions
- Groovy — easy regular expressions
- Groovy — JSON-to-classes
- Kotlin — JavaScript transpilation
- Kotlin — coroutines
#1. Ioke — homoiconicity
Homoiconicity is a subject which takes a bit of explanation, but what it boils down to is that a homoiconic program is composed of language elements which can be interpreted as objects within the language itself.
You could imagine a programming language where every object is a directed graph — loops are created with actual loops in the graph, and all information needed to run the program is contained within the vertices of the graph. If this language itself can manipulate graphs, then it can operate on its own source code. «Code as data» is a common maxim when working with homoiconic languages.
In Ioke, «everything is an expression composed of a chain of messages». The entire language syntax can be summarised as follows:
program ::= messageChain? messageChain ::= expression+ expression ::= message | brackets | literal | terminator literal ::= Text | Regexp | Number | Decimal | Unit message ::= Identifier ( "(" commated? ")" )? commated ::= messageChain ( "," messageChain )* brackets ::= ( "[" commated? "]" ) | ( " commated? ">" ) terminator ::= "." | "\n" comment ::= ";" .* "\n"
Since every program is a message chain, and a message chain is itself an object in Ioke, Ioke programs can manipulate their own source code (or the source code of other Ioke programs) as data. While the jury is still out as to whether homoiconicity is a good thing™ for a programming language to achieve, it certainly is an interesting feature, if nothing else.
#2. Gosu — open type system
Gosu is A JVM language that provides an open type system, meaning Gosu programmers can add functionality to classes, even if they didn’t create those classes themselves. This includes built-in Java classes like java.lang.String . A simple example is given on Gosu’s Wikipedia page:
enhancement MyStringEnhancement : String function print() print(this) > >
The above enhancement adds a print() method to the java.lang.String class, meaning a String can now print itself:
Gosu also provides extremely simple closures and enhancements to the Iterable interface, which — combined with the language’s scripting-like syntax — make Collection manipulation a breeze:
var lstOfStrings = "This", "is", "a", "list" > var longStrings = lstOfStrings.where( \ s -> s.length > 2 ) .map( \ s -> s.toUpperCase() ) .order() print(longStrings.join(", ")) // prints "LIST, THIS"
#3. Gosu — optional and named parameters
Gosu also brings to the JVM a feature which exists in many other languages, but is (as of this writing) still missing from Java: optional parameters.
In Java, there’s no way to specify a default value for a parameter in a constructor. If you want to make a parameter optional, you have to use a Builder Pattern, remove the parameter from the constructor entirely and rely on the user to set it after the fact, or create multiple constructors:
public class Person private String opt = "default"; public Person (String opt) this.opt = opt; > public Person() // . > //. >
Gosu is one of many modern languages that makes this functionality a bit easier by offering optional parameters. Here’s some Gosu code similar to the Java code above:
class Person var _opt : String construct (opt : String = "default") _opt = opt > //. >
This constructor provides a default value «default» to the parameter opt . This constructor can be called with a single String parameter, or with no parameters at all. In that case, the default value will be used.
Optional parameters can cause problems when constructors have more than one argument, though. Consider this case, for instance:
class Person var _opt : String var _ional : String construct (opt : String = "default", ional : String = "ditto") _opt = opt _ional = ional > //. >
This new Person implementation has two optional parameters. So if we call its constructor with:
. which parameter should be assigned the value «potato» ? They’re both String s and they both have default values, after all. What if we want to use the default value for opt , but a non-default value for ional ? Gosu resolves this issue with named parameters. When we call the construct method, we can specify which parameter we want to assign this value to:
The named parameter makes it clear that we want to assign this value to ional , and not opt .
#4. Prompto — standard dialects
Prompto is a cloud-based programming language designed for building complete information systems, like client-facing web apps which need to interact with server-side data (e-commerce sites, for instance). Prompto is the name of the programming language used to build these applications, but also the platform on which they run.
Prompto (the platform) provides various development tools, including a web-based REPL, database connectors, a debugger, a JVM-based compiler, and a JavaScript transpiler.
Prompto (the language) was started as an experiment in reifying attributes, and offers some radical features for a JVM language, including safe multiple inheritance, easy integration with various languages (including Java, JavaScript, C#, and Python, with Swift in the works), and — my favourite feature — dialects.
With Prompto dialects, «syntax is a detail». Programmers can write in whichever style suits them best, and code from one dialect can be losslessly translated into other dialects.
Prompto’s three built-in dialects are:
method main() print("15 + 3.5"); print("= 28.5"); >
def main(options:Text:>): print("15 + 3.5") print("= 28.5")
define main as method doing: print "15 + 3.5" print "= 28.5"
All three blocks of code above are the same Prompto code, expressed in different dialects. Every programming language must provide some syntax to perform a handful of basic tasks: defining variables, running code in a loop until some condition is met, printing information to the user, etc. With Prompto, you’re not stuck with a single way to express these concepts, you can choose the style of programming which suits you best!
I’m personally very excited about the idea of dialects in programming languages and I wish more languages would follow Prompto’s lead and incorporate this cool feature.
#5. Frege and Eta — «Haskell for the JVM»
Frege and Eta are two JVM-based languages which both purport to be «Haskell for the JVM». (Haskell is a programming language which has become nearly synonymous with the functional programming paradigm since the language’s first release almost 30 years ago.) So one might expect both of these languages to bring pure functional programming to the JVM. so why are they two separate languages?
Eta isn’t just meant to look like Haskell, it is Haskell (or a dialect of it), just running on the JVM. Eta strives for maximum compatibility with the Glasgow Haskell Compiler (GHC), and the common repository of Haskell libraries, Hackage. In addition, Eta offers Java interoperability with its strongly-typed Foreign Function Interface (FFI), meaning you can write functional code, but still use the Java classes and features you’re used to.
Frege, on the other hand, «while it supports basic Haskell, lacks many of the key extensions required to compile Hackage, and hence cannot reuse the existing infrastructure.» Frege’s «Differences Between Frege and Haskell» page on their GitHub was last updated in May 2017, and explains how Frege errs on the side of Java rather than Haskell:
«The Frege-Prelude defines many functions, type classes and types known from Haskell in a similar way. The goal is to implement most of the Haskell 2010 standard library in a most compatible way as far as this makes sense.»
«Apart from that, everything else will be quite different, because Frege uses the Java APIs whenever possible. At the time being, there is not much library support yet beyond the standard libraries.«
Frege is more established. Eta development has been more active recently. Both projects have one or two key developers, and so are maintained by very small teams. Although they both have roughly the same number of GitHub stars, «eta lang» returns almost 75x as many results on Google as «frege lang». Eta also has an official website.
While both languages are intriguing, and both advertise themselves as «Haskell for the JVM», if I had to learn only one of these two languages, I would probably pick Eta. Es tut mir leid, Herr Doktor Frege.
#6. Ateji PX — parallel blocks
«Do one thing, and do it well», the mantra of UNIX architects in the 1970s, was well-heeded by the developers of Ateji PX, a programming language which extends Java with a single key feature — easy parallelism.
Ateji is pronounced «ah-teh-gee», and the «PX» stands for «parallel extensions», which are what it provides for the Java ecosystem.
Writing parallelised (or «concurrent») code is still not easy, more than a decade into the era of multi-core processors. Different languages attack this problem in different ways. Some allow users to manually create threads, some allow higher-level management of pools of threads, some provide serialised and parallelised versions of the same methods, some implement actor models, and many languages do many (or all) of these. The programming language community doesn’t seem to have yet come to a consensus on when and how parallelism should be applied, in spite of a well-established pi-calculus having existed in theoretical computer science for decades.
Ateji PX threw its metaphorical hat into the ring around 2010, with its supremely simple parallelised blocks:
public class HelloWorld public static void main(String[] args) [ System.out.println("Hello"); || System.out.println("World"); ] > >
Above, the [] delimit a parallel block, inside of which, the operator || is used to create a parallel branch. The above code could print either