Friday, June 12, 2009

Learning Clojure - Calling Clojure From Java

Last night, we played with some of Clojure's interop with Java. We tried out with great success Jay Fields little example. Next up was compiling Clojure to Java class files (this was much trickier).

The documentation is pretty good and can be found at: http://clojure.org/compilation and under the (gen-class) API entry. There are two tricky bits, 1) the extra 'this' arg 2) the classes compilation directory.

But first the code sample:


(ns printer
  (:gen-class
   :methods [[printString [String] void]]
  )
)

(defn -printString [this arg]
        (println arg))

:gen-class is the directive to the clojure compiler that it should compile this .clj file into a java class. The :methods section allows us to define methods, we're defining the printString method which is a void and takes a String as it's arg.

The clojure function must begin with a '-' and then there's the 'this' arg. It took a little bit to find the 'this' gem in the documentation on the compilation entry (seriously, who reads everything before they start experimenting)

hasNext and next are implementations of methods in the Iterator interface. While the methods take no args, the implementation functions for instance methods will always take an additional first arg corresponding to the object the method is called upon, called by convention 'this' here. Note how the state can be obtained using an ordinary Java field access.

So no matter what method you expose to java it will alway have at least one arg which is the instance of the object. I can't tell you how many times we were so confused why we were getting wrong number of arg error messages when first trying this out.

Now to compile:
java -cp clojure.jar:. clojure.main -r
user=>(compile 'printer)
java.lang.RuntimeException: java.lang.ClassNotFoundException: printer$_printString__4 (NO_SOURCE_FILE:0)

This is because you didn't create 'classes' directory and add it to your classpath, it's exactly what the error says right?
create the directory, add it to your class path and try again
java -cp clojure.jar:.:classes clojure.main -r
user=>(compile 'printer)
printer

Great, hopefully there are no more exceptions, try javap on printer.class you should see something like:

zacharyshaw$ javap classes/printer
public class printer extends java.lang.Object{
    public static {};
    public printer();
    public java.lang.Object clone();
    public java.lang.String toString();
    public boolean equals(java.lang.Object);
    public int hashCode();
    public void printString(java.lang.String);
    public static void main(java.lang.String[]);
}

Now you should be all set to call through to your printer.class

public class Foo {
 public static void main(String[] args) {
  new printer().printString("hello world");
 }
}

There's loads of different things you can do, like implementing interfaces, extending classes etc. I'm really looking forward to seeing how I can integrate clojure into one of my projects (only where it makes sense of course :P )

6 comments:

Dan Pierkowski said...

I would add one clarification:

":gen-class is the directive to the clojure compiler that it should.... compile this .clj file"

:gen-class tells the clojure compiler to compile the .clj file as a java class.

:gen-interface tells the clojure comipler to compile the .clj file as a java interface.

Zachary D. Shaw said...

Ahh good point Dan, I updated the post to be a little clearer.

RD said...

Thanks a lot dude,
I was on the arity of the methods
for days.
Greatly appreciate the info.

RD said...

Read "was on the arity of the methods"
as "was also stuck on the arity of the methods"

Zachary D. Shaw said...

hey rdsr glad to help!

-Zach

Unknown said...

Thanks a lot for explaining about "this" as an extra argument, it really helped :)

 
Web Statistics