Llamando a clojure desde java
La mayoría de los principales resultados de Google para "llamar a clojure desde Java" están desactualizados y recomiendan su uso clojure.lang.RT
para compilar el código fuente. ¿Podría ayudarnos con una explicación clara de cómo llamar a Clojure desde Java, suponiendo que ya haya creado un jar a partir del proyecto Clojure y lo haya incluido en el classpath?
Actualización : desde que se publicó esta respuesta, algunas de las herramientas disponibles han cambiado. Después de la respuesta original, hay una actualización que incluye información sobre cómo crear el ejemplo con las herramientas actuales.
No es tan simple como compilar en un jar y llamar a los métodos internos. Sin embargo, parece haber algunos trucos para que todo funcione. Aquí hay un ejemplo de un archivo Clojure simple que se puede compilar en un jar:
(ns com.domain.tiny
(:gen-class
:name com.domain.tiny
:methods [#^{:static true} [binomial [int int] double]]))
(defn binomial
"Calculate the binomial coefficient."
[n k]
(let [a (inc n)]
(loop [b 1
c 1]
(if (> b k)
c
(recur (inc b) (* (/ (- a b) b) c))))))
(defn -binomial
"A Java-callable wrapper around the 'binomial' function."
[n k]
(binomial n k))
(defn -main []
(println (str "(binomial 5 3): " (binomial 5 3)))
(println (str "(binomial 10042 111): " (binomial 10042 111)))
)
Si lo ejecutas, deberías ver algo como:
(binomial 5 3): 10
(binomial 10042 111): 49068389575068144946633777...
Y aquí hay un programa Java que llama a la -binomial
función en el archivo tiny.jar
.
import com.domain.tiny;
public class Main {
public static void main(String[] args) {
System.out.println("(binomial 5 3): " + tiny.binomial(5, 3));
System.out.println("(binomial 10042, 111): " + tiny.binomial(10042, 111));
}
}
Su salida es:
(binomial 5 3): 10.0
(binomial 10042, 111): 4.9068389575068143E263
La primera pieza de magia es usar la :methods
palabra clave en la gen-class
declaración. Eso parece ser necesario para permitirle acceder a la función Clojure, algo así como métodos estáticos en Java.
Lo segundo es crear una función contenedora que Java pueda llamar. Observe que la segunda versión de -binomial
tiene un guión delante.
Y, por supuesto, el propio frasco de Clojure debe estar en la ruta de clases. Este ejemplo utilizó el frasco Clojure-1.1.0.
Actualización : esta respuesta se ha vuelto a probar utilizando las siguientes herramientas:
- Clojure 1.5.1
- Leiningen 2.1.3
- JDK 1.7.0 Actualización 25
La parte de Clojure
Primero cree un proyecto y una estructura de directorio asociada usando Leiningen:
C:\projects>lein new com.domain.tiny
Ahora, cambie al directorio del proyecto.
C:\projects>cd com.domain.tiny
En el directorio del proyecto, abra el project.clj
archivo y edítelo de modo que el contenido sea el que se muestra a continuación.
(defproject com.domain.tiny "0.1.0-SNAPSHOT"
:description "An example of stand alone Clojure-Java interop"
:url "http://clarkonium.net/2013/06/java-clojure-interop-an-update/"
:license {:name "Eclipse Public License"
:url "http://www.eclipse.org/legal/epl-v10.html"}
:dependencies [[org.clojure/clojure "1.5.1"]]
:aot :all
:main com.domain.tiny)
Ahora, asegúrese de que todas las dependencias (Clojure) estén disponibles.
C:\projects\com.domain.tiny>lein deps
Es posible que vea un mensaje sobre la descarga del frasco de Clojure en este momento.
Ahora edite el archivo Clojure C:\projects\com.domain.tiny\src\com\domain\tiny.clj
de modo que contenga el programa Clojure que se muestra en la respuesta original. (Este archivo se creó cuando Leiningen creó el proyecto).
Gran parte de la magia aquí está en la declaración del espacio de nombres. Le :gen-class
dice al sistema que cree una clase nombrada com.domain.tiny
con un único método estático llamado binomial
, una función que toma dos argumentos enteros y devuelve un doble. Hay dos funciones con nombres similares binomial
, una función tradicional de Clojure y -binomial
un contenedor accesible desde Java. Tenga en cuenta el guión en el nombre de la función -binomial
. El prefijo predeterminado es un guión, pero se puede cambiar por otro si se desea. La -main
función simplemente realiza un par de llamadas a la función binomial para asegurar que estamos obteniendo los resultados correctos. Para hacer eso, compila la clase y ejecuta el programa.
C:\projects\com.domain.tiny>lein run
Debería ver el resultado que se muestra en la respuesta original.
Ahora empaquetelo en un frasco y colóquelo en un lugar conveniente. Copia el frasco de Clojure allí también.
C:\projects\com.domain.tiny>lein jar
Created C:\projects\com.domain.tiny\target\com.domain.tiny-0.1.0-SNAPSHOT.jar
C:\projects\com.domain.tiny>mkdir \target\lib
C:\projects\com.domain.tiny>copy target\com.domain.tiny-0.1.0-SNAPSHOT.jar target\lib\
1 file(s) copied.
C:\projects\com.domain.tiny>copy "C:<path to clojure jar>\clojure-1.5.1.jar" target\lib\
1 file(s) copied.
La parte de Java
Leiningen tiene una tarea incorporada lein-javac
que debería poder ayudar con la compilación de Java. Desafortunadamente, parece no funcionar en la versión 2.1.3. No puede encontrar el JDK instalado y no puede encontrar el repositorio de Maven. Las rutas a ambos tienen espacios integrados en mi sistema. Supongo que ese es el problema. Cualquier IDE de Java también podría encargarse de la compilación y el empaquetado. Pero para esta publicación, vamos a la vieja escuela y lo haremos en la línea de comando.
Primero cree el archivo Main.java
con el contenido que se muestra en la respuesta original.
Para compilar la parte java
javac -g -cp target\com.domain.tiny-0.1.0-SNAPSHOT.jar -d target\src\com\domain\Main.java
Ahora cree un archivo con metainformación para agregar al archivo jar que queremos construir. En Manifest.txt
, agregue el siguiente texto
Class-Path: lib\com.domain.tiny-0.1.0-SNAPSHOT.jar lib\clojure-1.5.1.jar
Main-Class: Main
Ahora empaquete todo en un archivo jar grande, incluido nuestro programa Clojure y el jar Clojure.
C:\projects\com.domain.tiny\target>jar cfm Interop.jar Manifest.txt Main.class lib\com.domain.tiny-0.1.0-SNAPSHOT.jar lib\clojure-1.5.1.jar
Para ejecutar el programa:
C:\projects\com.domain.tiny\target>java -jar Interop.jar
(binomial 5 3): 10.0
(binomial 10042, 111): 4.9068389575068143E263
La salida es esencialmente idéntica a la producida solo por Clojure, pero el resultado se ha convertido a un doble de Java.
Como se mencionó, un IDE de Java probablemente se encargará de los complicados argumentos de compilación y del empaquetado.
A partir de Clojure 1.6.0, existe una nueva forma preferida de cargar e invocar funciones de Clojure. Ahora se prefiere este método a llamar a RT directamente (y reemplaza muchas de las otras respuestas aquí). El javadoc está aquí ; el punto de entrada principal es clojure.java.api.Clojure
.
Para buscar y llamar a una función Clojure:
IFn plus = Clojure.var("clojure.core", "+");
plus.invoke(1, 2);
Las funciones en clojure.core
se cargan automáticamente. Se pueden cargar otros espacios de nombres mediante require:
IFn require = Clojure.var("clojure.core", "require");
require.invoke(Clojure.read("clojure.set"));
IFn
s se puede pasar a funciones de orden superior, por ejemplo, el siguiente ejemplo pasa plus
a read
:
IFn map = Clojure.var("clojure.core", "map");
IFn inc = Clojure.var("clojure.core", "inc");
map.invoke(inc, Clojure.read("[1 2 3]"));
La mayoría IFn
de los correos electrónicos en Clojure se refieren a funciones. Algunos, sin embargo, se refieren a valores de datos que no son funcionales. Para acceder a ellos, utilice deref
en lugar de fn
:
IFn printLength = Clojure.var("clojure.core", "*print-length*");
IFn deref = Clojure.var("clojure.core", "deref");
deref.invoke(printLength);
A veces (si utiliza alguna otra parte del tiempo de ejecución de Clojure), es posible que deba asegurarse de que el tiempo de ejecución de Clojure esté correctamente inicializado; llamar a un método en la clase Clojure es suficiente para este propósito. Si no necesita llamar a un método en Clojure, simplemente hacer que la clase se cargue es suficiente (en el pasado ha habido una recomendación similar para cargar la clase RT; ahora se prefiere esto):
Class.forName("clojure.java.api.Clojure")
EDITAR Esta respuesta se escribió en 2010 y funcionó en ese momento. Consulte la respuesta de Alex Miller para obtener una solución más moderna.
¿Qué tipo de código llaman desde Java? Si tiene una clase generada con gen-class, simplemente llámela. Si desea llamar a una función desde un script, consulte el siguiente ejemplo .
Si desea evaluar el código a partir de una cadena, dentro de Java, puede utilizar el siguiente código:
import clojure.lang.RT;
import clojure.lang.Var;
import clojure.lang.Compiler;
import java.io.StringReader;
public class Foo {
public static void main(String[] args) throws Exception {
// Load the Clojure script -- as a side effect this initializes the runtime.
String str = "(ns user) (defn foo [a b] (str a \" \" b))";
//RT.loadResourceScript("foo.clj");
Compiler.load(new StringReader(str));
// Get a reference to the foo function.
Var foo = RT.var("user", "foo");
// Call it!
Object result = foo.invoke("Hi", "there");
System.out.println(result);
}
}