We integrate the clojure library into a java application
Take the following code as an example:
(ns clj-lib.core
(:use clj-lib.util))
(defn prod
([x] (reduce * x))
([s x] (reduce * s x)))
(defprotocol IAppendable
(append [this value]))
(extend-protocol IAppendable
Integer (append [this value] (+ this value))
String (append [this value] (str this "," value)))
(defmulti pretty type)
(defmethod pretty Integer [x] (str "int " x))
(defmethod pretty String [x] (str "str " x))
There are no global variables, if necessary, you can create separate get-functions for them. A multimethod and protocol has been announced - they can also be used from Java.
Standard Java Interfaces
Clojure uses its implementation of standard structures, with its own interfaces. For added convenience, all standard collections implement interfaces from java.util. * . For example, all sequences (lists, vectors, even lazy sequences) implement the java.util.List interface . Of course, all “mutating” methods ( add , clear , etc.) simply throw an UnsupportedOperationException . Similarly with sets and dictionaries - they implement Set and Map, respectively.
All functions implement 2 standard interfaces java.lang.Runnable and java.util.concurrent.Callable. This can be convenient when working directly with java.lang.concurent (although, most likely, it is better to work with executors directly from Clojure).
When working with long arithmetic, it is important to remember that Clojure has its own type for long integers clojure.lang.BigInt . At the same time, Clojure can work with the standard java.util.math.BigInteger . There is no such “surprise” with long real ones - the standard java.util.math.BigDecimal is used . There is also a special type clojure.lang.Ratio for rational fractions.
Compilation and gen-class
Probably the most obvious option is to compile the clojure code and get a set of class files. We add the gen-class command to the declaration of our namespace, specify the signatures for the functions:
(ns clj-lib.core
(:use clj-lib.util)
(:gen-class
:main false
:name "cljlib.CljLibCore"
:prefix ""
:methods
[^:static [prod [Iterable] Number]
^:static [prod [Number Iterable] Number]
^:static [append [Object Object] Object]
^:static [pretty [Object] Object]]))
...
Here we specified Iterable as the argument type for the prod function . In fact, you can pass ISeq , an array, and even String there . But, most likely, in Java it will be more convenient to work with this interface.
After that, you need to compile the namespace. We execute in REPL'e:
(compile 'clj-lib.core)We get the classes / cljlib / CljLibCore.class file , which can be used directly in our application. But compiling from REPL is frankly inconvenient, so it’s better to set up a leiningen project:
(defproject clj-lib
...
:aot [my-app.core],
...
)
Now when creating a jar, leiningen will automatically compile the specified namespace. We execute the command:
lein jarWe connect my-lib.jar and clojure.jar to our Java project and use:
import java.math.BigDecimal;
import java.util.Arrays;
import java.util.List;
import clj_lib.core.IAppendable;
import cljlib.CljLibCore;
public class Program {
static void pr(Object v) {
System.out.println(v);
}
static class SomeClass implements IAppendable {
public Object append(Object value) {
// some code
return null;
}
}
public static void main(String[] args) throws Exception {
pr(CljLibCore.pretty(1));
pr(CljLibCore.pretty("x"));
Integer x = (Integer) CljLibCore.append(-1, 1);
pr(CljLibCore.append(x, 1));
pr(CljLibCore.append(new SomeClass(), new SomeClass()));
List v = Arrays.asList(1, 2, 3, 4, 5);
pr(CljLibCore.prod(v));
pr(CljLibCore.prod(BigDecimal.ONE, v));
}
}
When the class is loaded, the Clojure runtime will be automatically initialized - no additional actions are required. It is also important to note that we can extend all protocols directly from Java - we just need to implement the appropriate interface. But working with objects is still better through functions, rather than calling interface protocol methods. Otherwise, extend-protocol will not work - very bad.
Perhaps the main drawback of this approach is the need for compilation as such. Also documentation for functions is not available from the IDE; you need to adapt the source code of the library (or do the binding on Clojure). On the other hand, in some specific cases, the only option is to have an “honest” class file in the classpath.
We use clojure.lang.RT
The heart of all Clojure runtimes is precisely this class. It defines static methods for creating keywords, characters, vectors, implementing basic functions (for example, first and rest ), and much more. The class is undocumented, does not have a permanent interface - we use it at your own peril and risk. But there are several useful functions that make integration extremely simple:
- Var var (String ns, String name) - returns a var-cell by its full name;
- Keyword keyword (String ns, String name) - returns keyword (the first parameter may be null);
- void load (String path) - loads the clj script at the specified path.
And many more, it’s easier to turn to implementation . You can call an arbitrary function like this:
RT.var("clojure.core", "eval").invoke("(+ 1 2)");
We rewrite the above example using the RT class :
import java.math.BigDecimal;
import clojure.lang.RT;
import clojure.lang.Sequential;
import clojure.lang.Var;
public class Program {
static void pr(Object v) {
System.out.println(v);
}
public static void main(String[] args) throws Exception {
Var pretty = RT.var("clj-lib.core", "pretty");
Var append = RT.var("clj-lib.core", "append");
Var prod = RT.var("clj-lib.core", "prod");
pr(pretty.invoke(1));
pr(pretty.invoke("x"));
Integer x = (Integer) append.invoke(-1, 1);
pr(append.invoke(x, 1));
Sequential v = RT.vector(1, 2, 3, 4, 5);
pr(prod.invoke(v));
pr(prod.invoke(BigDecimal.ONE, v));
}
}
Now we can no longer extend the protocol directly from Java - the IAppendable interface is created in runtime and is not available when compiling our java file. But there is no need for AOT.
Java interface and reify
In fact, this is not a separate way, but rather an option on how to arrange interaction with the library. We write the Java interface:
public interface ICljLibCore {
Number prod(Iterable x);
Number prod(Number s, Iterable x);
Object append(Object self, Object value);
String pretty(Object x);
}
After that, create a similar function in Clojure:
(defn get-lib []
(reify ICljLibCore
(prod [_ x] (prod x))
(prod [_ s x] (prod x))
(append [_ t v] (append t v))
(pretty [_ x] (pretty x))))
When accessing the library, we create an instance of ICljLibCore and write it to a static field:
public static final ICljLibCore CLJ_LIB_CORE = (ICljLibCore) RT.var("clj-lib.core", "get-lib").invoke();
...
CLJ_LIB_CORE.pretty("1");
The disadvantage of this approach is that you have to manually create an interface. On the other hand, honest java-docs can be placed in this interface. It will be easier to replace the Clojure library with a Java implementation (if you suddenly run out of speed), it is easier to write tests. And, of course, no one AOT.
Conclusion
Outside of the article, some alternatives remain. For example, automatically generate wrapper classes based on Clojure code and meta data (and make this into a leiningen plugin). You can make transparent integration into a DI framework (for example, Spring or Guice). There are many options, with their pros and cons.