A few days ago I wrote about how you can compile Clojure code into jar files. Well, I’d like to take a closer look at how functions are exported and how they look in Java code.

Let’s take a look at this simple “Hello, World!” program.

(ns hello-world.core)

(defn hello [subject]
  (println (str "Hello, " subject "!")))

(defn -main []
  (hello "World"))

Running this program will print “Hello, World!” out to the console. Now I want to be able to do a few things with the jar that this code generates:

  • Invoke main
  • Invoke hello from a ‘core’ object
  • Invoke hello statically

Setting up Main

Assuming project.clj has already been set up, the only thing left to do is add :gen-class to the file’s namespace.

(ns hello-world.core
  (:gen-class))
; ...

After compilation, we can see some Java class files have been generated in /target/classes/hello_world–one of these will be named core.class. If we open this up, we see some Java code has been generated with our main method.

package hello_world;
// ...
public class core {
    // ...
    public static void main(String[] var0) {
        // Implementation ...
    }
}

Setting up Non-Static Hello

Back in our Clojure core.clj file, we’ll need to add a new method to export in our Java class and update :gen-class with a :methods option.

(ns hello-world.core
  (:gen-class
    :methods [[hello [String] void]]))
; ...
(defn hello [subject]
  (println (str "Hello, " subject "!")))

(defn -hello [_ subject] (hello subject))
; ...

Now what does that :methods setting mean? :methods is an array of methods that are to be exported from the module upon compilation. Here, we’re exporting a method named hello that accepts a Java String and returns void.

Because we named our method hello in :methods, we need a corresponding Clojure function that starts with a dash: -hello.

While it may seem redundant to define the function twice, this is necessary in order for the function to be added to the jar.

You may have noticed that -hello takes two parameters, where the first is ignored. Why is that? Let’s look at the core.class file.

package hello_world;
// ...
public class core {
    private static final Var hello__var = Var.internPrivate("hello-world.core", "-hello");
    // ...
    public void hello(String var1) {
        Var var10000 = hello__var;
        Object var2 = var10000.isBound() ? var10000.get() : null;
        if (var2 != null) {
            ((IFn)var2).invoke(this, var1);
        } else {
            throw new UnsupportedOperationException("hello (hello-world.core/-hello not defined?)");
        }
    }
    // ...
}

In the autogenerated class, we have a reference to the Clojure -hello function and ((IFn)var2).invoke(this, var1); within the Java hello method for that function. Essentially what this is doing is executing the -hello function with a this parameter as the first parameter and the and the actual parameter that was passed to the hello Java function will come after that.

This is very similar to how the first parameter would work in a deftype function, referencing the object.

Setting up Static Hello

There are only a couple differences between compiling a static and non-static Clojure function. Here’s how you might set up a static hello function:

(ns hello-world.core
  (:gen-class
    :methods [[hello [String] void]
              ^{:static true} [static_hello [String] void]]))

(defn hello [subject]
  (println (str "Hello, " subject "!")))
(def -static_hello hello)
; ...

Since we already have a hello function declared, we must name the static version something different: static_hello. In our :methods declaration, we indicate that the function is static with ^{:static true}.

When we create our -static_hello function, we can just set it to our existing hello function. This means that -static_hello only has one parameter, as opposed to our -hello function. This is because static methods don’t require an object reference parameter.

If we look back at core.class, we can see exactly how this function is declared as well as the call to invoke, which only takes one parameter (without the this reference).

package hello_world;
// ...
public class core {
    // ...
    public static void static_hello(String var0) {
        Var var10000 = static_hello__var;
        Object var1 = var10000.isBound() ? var10000.get() : null;
        if (var1 != null) {
            ((IFn)var1).invoke(var0);
        } else {
            throw new UnsupportedOperationException("static_hello (hello-world.core/-static_hello not defined?)");
        }
    }
    // ...
}

Now we’re ready to compile. Running lein uberjar should create a jar file with main, hello, and static_hello functions!