2009-03-09

Interacting between <insert JVM lang here> and Java

[2009-03-15: This blog post has been thoroughly rewritten after I found out some details were wrong and people wanted more details]

I don't like Java. If care to find out why you can search my blog, but it's no secret Java is not exactly at the top of my list of favorite programming languages. But I am not stupid enough to think that Java is about to go away. And I have nothing against the JVM, just the primary language that runs on it.

Thanks to the JVM, just because I have to work with Java code does not mean I have to write Java code. Thanks to various languages being ported to the JVM there are now multiple options for working with Java code in languages other than Java:



By targeting the JVM all the above languages can call Java code in order to be relevant in a Java-heavy world. But just because they can easily consume Java code does not mean that the reverse is true. What I am looking for is not just a way to call into Java, but to call other languages from Java.

To better explain this, take the following three files. First, I have Spam.java:

public class Spam {
public String serves() {
return "spam";
}
}
I have another class that implements a subclass, BaconSpam.java:
public class BaconSpam extends Spam {
@Override
public String serves() {
return "bacon " + super.serves();
}
}
And finally, the class that runs the show, Waitress.java:
public class Waitress {
public static void main(String[] args) {
BaconSpam menu = new BaconSpam();
String food = menu.serves();
System.out.println("We serve " + food + "!");
}
}
What I am after is a language to rewrite BaconSpam.java in another JVM language such that I don't touch Spam.java. I also want the changes to Waitress.java to be minimal or non-existent while still having to store an instance of the class and the value returned by serves() to show how objects would be stored and used in a long-running Java application (i.e. no cheating by inlining some call in the println() call to make the example a little tougher and more "real world").


Jython



Thanks to the Jython guys I was pointed to a Jython Monthly article from October 2006 that explains how best to go about accessing Jython code from Java.

To start I wrote BaconSpam in Python:
import Spam

class BaconSpam(Spam):
def serves(self):
return " ".join(["bacon", Spam.serves(self)])


While that was rather simple, there is still the issue of getting an instance of the class. Because Jython dynamically interprets Python code you can't simply drop in BaconSpam.py and have it work with Waitress.java. You need to create an instance of org.python.util.PythonInterpreter and interface with it to get at the Python code:

import org.python.core.__builtin__;
import org.python.core.PyObject;

public class Waitress {
public static void main(String[] args) {
PyObject BaconSpam = __builtin__.__import__("BaconSpam").__getattr__("BaconSpam");
Spam menu = (Spam)BaconSpam.__call__().__tojava__(Spam.class);
String food = menu.serves();
System.out.println("We serve " + food + "!");
}
}


Because Jython dynamically loads Python code, Waitress.java has to be modified. Luckily a large chunk of the code is boilerplate that can be extracted out into a factory class to help simplify things.

And just a warning for anyone wanting to run the above code: for some odd reason the above code only worked for me with Jython 2.5b3, not 2.2.1.


JRuby



Writing the BaconSpam subclass was simple:


class BaconSpam < Spam
def serves
"bacon " + super
end
end


But as of right now you need to use JSR 223 to interface with the class which is worse than the Jython approach, so I am not going to go through the steps here.

But Charles Nutter has blogged about adding signature support to JRuby's in-dev compiler2. It looks like as soon as inheritance is handled properly in compiler2 that JRuby will be in the same position at Groovy and Scala for ease of integration (see below for details on those two languages).

Rhino



I don't like JavaScript, so I am skipping Rhino. =) But it's another JSR 223 approach.

Clojure



I actually didn't get Clojure to work. I tried to follow the gen-class example from the Clojure wikibooks, but ran into several issues that included having to manually execute compilation for the Eclipse plug-in to even get an error message and putting the code in a package to get Clojure to not assume I was working off of java.lang when inheriting from Spam.java.


(ns pkg.BaconSpam
(:gen-class
:extends pkg.Spam
:exposes {serves servesSuper}))

(defn -serves [this]
(str "bacon " (.servesSuper this))
)


With an error of "java.lang.IllegalArgumentException: Don't know how to create ISeq from: Symbol", I just stopped trying to make this work. I assume Waitress.java will need to be changed beyond just being put in a package anyway in order to deal with Clojure's dynamic typing.


Groovy



Writing the Groovy version of BaconSpam was very easy thanks to the language having been designed for the JVM from the outset. The only trick was that serves() needed to have the return type specified for the method instead of being dynamic:


public class BaconSpam extends Spam {
// Using 'def' makes return value dynamic.
String serves() {
return "bacon " + super.serves()
}
}


With the typed method there is no need for modifying Waitress.java. Groovy basically ends up looking like Java with some syntax removed.

One issue that did come up with writing the Groovy example was that the Eclipse plug-in is in some bad shape; I couldn't get it to run the project. This drove me to download and use NetBeans since it has Groovy support included. That was a much better experience since it actually worked.

Oh, and the docs suck. Took way too long to figure out how the darn language is even structured. Just had to read various examples to figure things out.


Scala



Much like Groovy, Scala was easy to use to rewrite BaconSpam.


class BaconSpam extends Spam {
override def serves(): String =
return "bacon " + super.serves()
}


And just like Groovy there was no need to change Waitress.java in order to interact with the class. But unlike Groovy the Eclipse plug-in for Scala actually allowed me to execute the application. Plus the docs are better so I didn't have to go digging around to figure out what I needed to do.