Integrate the clojure library in java application
Clojure has a very tight integration with Java. Direct use of Java libraries in the app in Clojure — it is quite simple and ordinary. Backward integration is more complicated. This article describes some options for integrating Clojure code into a Java application.
As example, consider the following code:
the
There are no global variables, if necessary, for them to create private get-function. Declared a multimethod and Protocol — you can also use them from Java.
the
Clojure uses its own implementation of standard structures, with their interfaces. To make all the standard collections implement the interfaces of the java.util.*. For example, all sequences (lists, vectors, and even lazy sequences) implement the interface java.util.List. Of course, all "mutating" methods (add, clear, etc.) just throw the exception UnsupportedOperationException. Similarly, sets and dictionaries — they sell a Set and Map, respectively.
All functions implement the interface 2 standard java.lang.Runnable and java.util.concurrent.Callable. This can be useful when the direct operation with java.lang.concurent (though likely better with executor'AMI to work directly from Clojure).
When working with long arithmetic, it is important to remember that in Clojure the type of long integers clojure.lang.BigInt. While Clojure is able to work with standard java.util.math.BigInteger. With long material such a "surprise" no — use the standard java.util.math.BigDecimal. There is also a special type of clojure.lang.Ratio for rational fractions.
the
Probably the most obvious option is compile clojure code and get the set of class files. Add the command gen-class in our namespace Declaration, we specify signatures for the functions:
the
Here we have specified Iterable as the argument type for the function prod. Actually you can transfer and ISeq and the array, and even String. But most of all, in Java it is more convenient to work with this interface.
Then you have to compile your namespace. Performed in REPL'e:
the
Get the file classes/cljlib/CljLibCore.class, which you can use directly in our application. But to compile from the REPL, and frankly uncomfortable, so it is better to set leiningen project
the
Now when creating a jar-OK leiningen will automatically compile the specified namespace. Run the command:
the
Plug-in my-lib.jar and clojure.jar to our Java project and use:
the
When a class is loaded will be automatically initialized by the runtime Сlojure — no further action is required. It is also important to note that we can expand all protocols directly from your Java — you need only implement the appropriate interface. But working with objects is still better than using a function and not calling methods on the interface Protocol. Otherwise it will not work extend-protocol very bad.
Perhaps the main disadvantage of this approach is the need to compile as such. Still not available from the IDE documentation for the functions you need to adapt the library source code (or to do the binding in Clojure). On the other hand, in some specific cases, the only option is to have "honest" class file in the classpath.
the
Heart entire runtime Сlojure is this class. It defines static methods for creating of keywords, symbols, vectors, implementation of basic functions (e.g. firstand rest) and whatnot. The class is undocumented, has no permanent interface -- use at your own risk. But there are some useful functions that make the integration very simple:
the
And many other, easier turn to implementation. To call an arbitrary function:
the
Let's rewrite the above example using the class RT:
the
Now we are unable to extend the Protocol directly from the Java interface IAppendable is created in runtime and not available during the compilation of our java file. But there is no need to AOT.
the
Actually this is not a standalone method, but rather an option, as you can arrange the interaction with the library. Write Java interface:
the
Then in Clojure to create a similar function:
the
When referring to the library, we create an instance ICljLibCore and write it to a static field:
the
The disadvantage of this approach — you have to manually create the interface. On the other hand, in this interface you can put honest java-doc. It will be easier to replace Clojure library implementation in Java (if you suddenly cease to miss speed), it is easier to write tests. And, of course, one AOT.
the
Outside of article there are some alternatives. For example, to automatically generate wrapper classes based on Clojure code and meta-data (and to issue it as a leiningen plugin). It is possible to make transparent the integration of the DI framework (e.g. Spring or Guice). A lot of options, with their pros and cons.
Article based on information from habrahabr.ru
As example, consider the following code:
the
(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, for them to create private get-function. Declared a multimethod and Protocol — you can also use them from Java.
the
Standard Java interfaces
Clojure uses its own implementation of standard structures, with their interfaces. To make all the standard collections implement the interfaces of the java.util.*. For example, all sequences (lists, vectors, and even lazy sequences) implement the interface java.util.List. Of course, all "mutating" methods (add, clear, etc.) just throw the exception UnsupportedOperationException. Similarly, sets and dictionaries — they sell a Set and Map, respectively.
All functions implement the interface 2 standard java.lang.Runnable and java.util.concurrent.Callable. This can be useful when the direct operation with java.lang.concurent (though likely better with executor'AMI to work directly from Clojure).
When working with long arithmetic, it is important to remember that in Clojure the type of long integers clojure.lang.BigInt. While Clojure is able to work with standard java.util.math.BigInteger. With long material such a "surprise" no — use the standard java.util.math.BigDecimal. There is also a special type of clojure.lang.Ratio for rational fractions.
the
Compilation and gen-class
Probably the most obvious option is compile clojure code and get the set of class files. Add the command gen-class in our namespace Declaration, we specify signatures for the functions:
the
(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 have specified Iterable as the argument type for the function prod. Actually you can transfer and ISeq and the array, and even String. But most of all, in Java it is more convenient to work with this interface.
the name of the class, you can choose any.
If the parameter is not set, it will use clj_lib.core. For the record it will generate a class clj_lib.core.IAppendable in the package clj_lib.core. Ie we will have a class and a package with the same name. Therefore it is better to specify the class name explicitly.
Then you have to compile your namespace. Performed in REPL'e:
the
(compile 'clj-lib.core)
Get the file classes/cljlib/CljLibCore.class, which you can use directly in our application. But to compile from the REPL, and frankly uncomfortable, so it is better to set leiningen project
the
(defproject clj-lib
...
:aot [my-app.core],
...
)
Now when creating a jar-OK leiningen will automatically compile the specified namespace. Run the command:
the
lein jar
Plug-in my-lib.jar and clojure.jar to our Java project and use:
the
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<Integer> v = Arrays.asList(1, 2, 3, 4, 5);
pr(CljLibCore.prod(v));
pr(CljLibCore.prod(BigDecimal.ONE, v));
}
}
When a class is loaded will be automatically initialized by the runtime Сlojure — no further action is required. It is also important to note that we can expand all protocols directly from your Java — you need only implement the appropriate interface. But working with objects is still better than using a function and not calling methods on the interface Protocol. Otherwise it will not work extend-protocol very bad.
Perhaps the main disadvantage of this approach is the need to compile as such. Still not available from the IDE documentation for the functions you need to adapt the library source code (or to do the binding in Clojure). On the other hand, in some specific cases, the only option is to have "honest" class file in the classpath.
the
Use clojure.lang.RT
Heart entire runtime Сlojure is this class. It defines static methods for creating of keywords, symbols, vectors, implementation of basic functions (e.g. firstand rest) and whatnot. The class is undocumented, has no permanent interface -- use at your own risk. But there are some useful functions that make the integration very simple:
the
- Keyword keyword (String ns, String name) — returns the keyword (the first parameter can be null); the
- void load(String path) — loads the clj script at the specified path.
Var var(String ns, String name) — returns a var-cell name; the
And many other, easier turn to implementation. To call an arbitrary function:
the
RT.var("clojure.core", "eval").invoke("(+ 1 2)");
Let's rewrite the above example using the class RT:
the
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 are unable to extend the Protocol directly from the Java interface IAppendable is created in runtime and not available during the compilation of our java file. But there is no need to AOT.
the
a Java-interface and reify
Actually this is not a standalone method, but rather an option, as you can arrange the interaction with the library. Write Java interface:
the
public interface ICljLibCore {
Number prod(Iterable x);
Prod Number(Number s, Iterable x);
Object append(Object self, Object value);
String pretty(Object x);
}
Then in Clojure to create a similar function:
the
(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 referring to the library, we create an instance ICljLibCore and write it to a static field:
the
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 — you have to manually create the interface. On the other hand, in this interface you can put honest java-doc. It will be easier to replace Clojure library implementation in Java (if you suddenly cease to miss speed), it is easier to write tests. And, of course, one AOT.
the
Conclusion
Outside of article there are some alternatives. For example, to automatically generate wrapper classes based on Clojure code and meta-data (and to issue it as a leiningen plugin). It is possible to make transparent the integration of the DI framework (e.g. Spring or Guice). A lot of options, with their pros and cons.
Комментарии
Отправить комментарий