As you saw in chapter 1, Clojure doesn’t have an interpreter. Code is evaluated one s-expression at a time. During that process, if something needs to be compiled, the Clojure runtime does so. Ultimately, because Clojure is hosted on the JVM, everything is converted to Java bytecode before execution, and the programmer doesn’t have to worry about when and how this happens.
Clojure provides a mechanism to do this compilation ahead of time (AOT). AOT compilation has its advantages. Packaging the code lets you deliver it as class files (and without including the source code) for use by other Java applications, and it speeds up the program’s startup time. In this section, we’ll examine how to AOT compile Clojure code.
5.2.1 Example: A tale of two calculators
Let’s examine some code that implements a couple of financial calculators that you might use to manage investments in stocks and bonds. We’ll lay out the code in a
127 Compiling Clojure code to Java bytecode
directory structure for easy organization—one that’s somewhat idiomatic in the Clo- jure world. Figure 5.1 shows this organization.
Note that calculators.clj is located in the src/com/curry/utils directory and is the file that contains the namespace of our current interest. Here are the contents:
(ns com.curry.utils.calculators (:gen-class))
(defn present-value [data]
(println "calculating present value..."))
The :gen-class directive used in the namespace declaration is used to generate a named class for this namespace when compiled. You can compile this code from a REPL that has both classes and src directories on the classpath. The compile function is used to do this, and it accepts the namespace to be compiled:
(compile 'com.curry.utils.calculators)
If successful, this function returns the name of the namespace that was compiled.
Let’s now examine what the output of this compilation process is, what class files are generated, and where they’re located.
GENERATEDCLASSES
As noted, the compile function compiles the specified namespace. In this case, it gener- ates class files for the com.curry.utils.calculators namespace. Three class files that get generated here are calculators_init.class, calculators.class, and calculators$present_
value__xx.class, and they’re located in the classes/com/curry/utils directory.
Generated classes using Leiningen
The paths of generated classes described here are the defaults for the raw Clojure REPL. If you’re using Clojure through Leiningen as we suggest in appendix A, then your generated classes will have the same paths but be under the target/ directory instead of the root. For example, instead of being under classes/com/curry/utils (raw Clojure REPL), generated classes will be under target/classes/com/curry/utils.
This difference is governed by the *compile-path* dynamic var explained shortly.
root
classes
src
com
curry
utils
calculators.clj
Figure 5.1 Typical organization of a Clojure project. The src directory contains the source code, organized in a similar way to Java packages.
A class file is created for each Clojure function. In this case, the present-value func- tion causes the calculators$present_value__xx.class file to be created, the name of which will vary each time the namespace is recompiled (because it’s a generated name). A class file is also generated for each gen-class, and in this case this corre- sponds to the calculators.class file.
Finally, the class files that have the __init in their names contain a loader class, and one such file is generated for every Clojure source file. Typically, this loader class doesn’t need to be referenced directly, because use, require, and load all figure out which file to use when they’re called.
:gen-class has a lot of options that allow control over various aspects of the gen- erated code. These are explored in section 5.2.2. Now that we’ve covered the basics of compilation, you’ll try to compile a namespace that’s spread across files.
ADDITIONALFILES
Here you’ll add a couple more calculators to the calculators namespace. You’ll cre- ate two more files to do this, one for each new calculator function. The resulting file structure is shown in figure 5.2.
The contents of dcf.clj are
(in-ns 'com.curry.utils.calculators) (defn discounted-cash-flow [data]
(println "calculating discounted cash flow..."))
And the contents of fcf.clj are
(in-ns 'com.curry.utils.calculators) (defn free-cash-flow [data]
(println "calculating free cash flow...")) root
classes
src
com
curry
utils
calculators.clj calc
dcf.clj fcf.clj
Figure 5.2 Adding two new files, dcf.clj and fcf.clj, in a subdirectory of utils that have code for the same com.curry.utils.calculators namespace.
129 Compiling Clojure code to Java bytecode
Note that they both use in-ns to ensure that these files all belong to the same name- space. calculators.clj is modified as follows:
(ns com.curry.utils.calculators (:gen-class))
(load "calc/fcf") (load "calc/dcf")
(defn present-value [data]
(println "calculating present value..."))
Note the use of load using relative paths, because the fcf.clj and dcf.clj files are inside the calc subdirectory of utils. Calling compile, as you did before, results in new class files being generated in the classes directory. Two files, namely dcf__init.class and fcf__init.class, are generated in the classes/com/curry/utils/calc directory. New files are also created for the new functions, namely discounted-cash-flow and free- cash-flow, in the classes/com/curry/utils directory.
*COMPILE-PATH*
In case you’re curious as to why the generated code is being output to the classes directory, it’s because it’s the default value of the global var *compile-path*. It’s easy to change this by calling set! to alter the value of the var or to call compile inside a binding form with *compile-path* bound to something appropriate. The thing to remember is that the directory must exist, and it should be on the classpath.
5.2.2 Creating Java classes and interfaces using gen-class and gen-interface
Clojure also has a standalone utility for generating Java classes and interfaces in the gen-class and gen-interface macros. (gen-interface works in a similar fashion to gen-class but has fewer options because it’s limited to defining an interface.) When code containing these calls is compiled, it generates bytecode for the specified classes or interfaces and writes them into class files, as you saw earlier.
In this section, you’ll see an example of how gen-class works. Consider the follow- ing listing, which is a contrived example of an abstract Java class that we’ll use to illus- trate gen-class.
package com.gentest;
public abstract class AbstractJavaClass {
public AbstractJavaClass(String a, String b) { System.out.println("Constructor: a, b");
}
public AbstractJavaClass(String a) { System.out.println("Constructor: a");
}
public abstract String getCurrentStatus();
Listing 5.1 An abstract Java class that will be used to illustrate gen-class
public String getSecret() { return "The Secret";
} }
Once AbstractJavaClass is compiled with javac AbtractJavaClass.java, the AbstractJavaClass.class file needs to be on Clojure’s classpath and in a subdirectory that matches the package specification. For example, if target/classes/ is in the class- path, this class file needs to be at target/classes/com/gentest/AbstractJavaClass.class.
After ensuring that the Clojure runtime can see the class, you can use gen-class as shown in this next listing.
(ns com.gentest.gen-clojure
(:import (com.gentest AbstractJavaClass)) (:gen-class
:name com.gentest.ConcreteClojureClass :extends com.gentest.AbstractJavaClass :constructors {[String] [String]
[String String] [String String]}
:implements [Runnable]
:init initialize :state localState
:methods [[stateValue [] String]])) (defn -initialize
([s1]
(println "Init value:" s1) [[s1 "default"] (ref s1)]) ([s1 s2]
(println "Init values:" s1 "," s2) [[s1 s2] (ref s2)]))
(defn -getCurrentStatus [this]
"getCurrentStatus from - com.gentest.ConcreteClojureClass") (defn -stateValue [this]
@(.localState this)) (defn -run [this]
(println "In run!")
(println "I'm a" (class this))
(dosync (ref-set (.localState this) "GO"))) (defn -main []
(let [g (new com.gentest.ConcreteClojureClass "READY")]
(println (.getCurrentStatus g)) (println (.getSecret g)) (println (.stateValue g)))
(let [g (new com.gentest.ConcreteClojureClass "READY" "SET")]
(println (.stateValue g)) (.start (Thread. g)) (Thread/sleep 1000)
(println (.stateValue g))))
Now, let’s go over the code in listing 5.2 to understand what’s going on. The call to ns should be familiar by now. It uses :import to pull in AbstractJavaClass from
Listing 5.2 gen-class generates a Java class to reference AbstractJavaClass
131 Compiling Clojure code to Java bytecode
listing 5.1. This was why it needs to be on the classpath. The next option, :gen-class, is our primary interest. It can take several options, some of which are used in this example and some aren’t. Table 5.1 describes the options used in listing 5.2.
Functions such as –initialize, -getCurrentStatus, and -run implement or override interface or superclass methods. The reason they’re prefixed with a dash (-) is so that they can be identified via convention. The prefix can be changed using the :prefix option (see table 5.2). Now that you understand what each option in the example does, you’re ready to run it.
RUNNINGTHEEXAMPLE
Run (compile 'com.gentest.gen-clojure) from a REPL to create the Concrete- ClojureClass.class file like you did in the previous example. The classpath needs to have clojure.jar on it as well as the locations of AbstractJavaClass.class and ConcreteClojure- Class.class. The following command assumes the CLASSPATH environment variable has been set up appropriately. The command to test the generated class is
java com.gentest.ConcreteClojureClass
This outputs the following to the console:
Init value: READY Constructor: a
getCurrentStatus from - com.gentest.ConcreteClojureClass The Secret
READY
Init values: READY , SET Constructor: a, b
SET
Table 5.1 gen-class options used in listing 5.2
Option Description
:name The name of the class that will be generated when this namespace is compiled.
:extends The fully qualified name of the superclass.
:constructors Explicit specification of constructors via a map where each key is a vector of types that specifies a constructor signature. Values are similar vectors that identify the signature of a superclass constructor.
:implements A vector of Java interfaces that the class implements.
:init The name of a function that will be called with the arguments to the constructor.
Must return a vector of two elements, the first being a vector of arguments to the superclass’s constructor, and the second an object to contain the current instance’s state (usually an atom).
:methods Specifies the signatures of additional methods of the generated class. This is not needed for public methods defined in inherited interfaces or superclasses:
gen-class will declare those automatically.
In run!
I'm a com.gentest.ConcreteClojureClass GO
As the code shows, you’ve used both constructor signatures to create instances of the generated class. You’ve also called a superclass method getSecret and the overridden method getCurrentStatus. Finally, you’ve run the second instance as a thread and checked the mutating state localState, which changed from "SET" to "GO".
Table 5.2 shows the other options available to gen-class.
This is quite an exhaustive set of options, and it lets the programmer influence nearly every aspect of the generated code.
LEININGENAND JAVAPROJECTS
The Java and Clojure code you used to create the ConcreteClojureClass class is an example of a mixed Clojure and Java project. Leiningen makes managing such projects
Table 5.2 More gen-class options
Option Description
:post-init Specifies a function by name that’s called with the newly created instance as the first argument and each time an instance is created, after all constructors have run. Its return value is ignored.
:main Specifies whether a main method should be generated so this class can be run as an application entry point from the command line. Defaults to true. :factory Specifies the name of the factory function(s) that will have the same signa-
ture as the constructors. A public final instance of the class will also be cre- ated. An :init function will also be needed to supply the initial state.
:exposes Exposes protected fields inherited from the superclass. The value is a map where the keys are the name of the protected field and the values are maps specifying the names of the getters and setters. The format is
:exposes{protected-field-name{:getname:setname}, ...}
:exposes-methods Exposes overridden methods from the superclass via the specified name. The format of this option is
:exposes-methods{super-method-nameexposed-name,...}
:prefix Defaults to the dash (-). When methods like getCurrentStatus are called, they will be looked up by prefixing this value (for example, -getCurrentStatus).
:impl-ns The name of the namespace in which to find method implementations.
Defaults to the current namespace but can be specified here if the methods being implemented or overridden are in a different namespace.
:load-impl-ns Whether the generated Java class will load its Clojure implementations when it’s initialized. Defaults to true; can be turned to false if you need to load either the Clojure or the generated Java classes separately. (This is a special- ized setting you’re unlikely to need.)
133 Calling Clojure from Java
much easier. You can instead create a project.clj like in listing 5.3 instead of invoking java, javac, and Clojure’s compile function manually. With Leiningen all you need to do is place your source files in the right directories and execute leinrun from the command line: this command will automatically compile AbstractJavaClass.java and the gen-classed ConcreteClojureClass in gen-clojure.clj and run the main method of ConcreteClojureClass.
Leiningen can alleviate much of the confusion and tedium out of managing Clo- jure projects, especially when the project mixes Java and Clojure code. Appendix A has more information on how to install Leiningen if you haven’t been using it already.
(defproject gentest "0.1.0"
:dependencies [[org.clojure/clojure "1.6.0"]]
; Place our "AbstractJavaClass.java" and "gen-clojure.clj" files under ; the src/com/gentest directory.
:source-paths ["src"]
:java-source-paths ["src"]
; :aot is a list of clojure namespaces to compile.
:aot [com.gentest.gen-clojure]
; This is the java class "lein run" should execute.
:main com.gentest.ConcreteClojureClass)
Now that you’ve seen how to compile and generate Java code from the Clojure source, we’re ready to move on. You’re now going to see how to go the other way: to call Clo- jure functions from Java programs. This will allow you to write appropriate parts of your application in Clojure using all the facilities provided by the language and then use it from other Java code.