java clojure clojure-java-interop

Llamando a Clojure desde Java



clojure-java-interop (9)

La mayoría de los principales éxitos de google para "llamar a clojure desde Java" están desactualizados y recomendamos usar clojure.lang.RT para compilar el código fuente. ¿Podría ayudar con una explicación clara de cómo llamar a Clojure desde Java suponiendo que ya ha creado un contenedor del proyecto Clojure y lo incluyó en el classpath?


A partir de Clojure 1.6.0, hay una nueva forma preferida de cargar e invocar las funciones de Clojure. Ahora se prefiere este método para llamar a RT directamente (y reemplaza a muchas otras respuestas aquí). El javadoc está here - el principal punto de entrada es clojure.java.api.Clojure .

Para buscar y llamar a una función de Clojure:

IFn plus = Clojure.var("clojure.core", "+"); plus.invoke(1, 2);

Las funciones en clojure.core se cargan automáticamente. Otros espacios de nombres se pueden cargar a través de require:

IFn require = Clojure.var("clojure.core", "require"); require.invoke(Clojure.read("clojure.set"));

IFn se pueden pasar a funciones de orden superior, por ejemplo, el siguiente ejemplo pasa plus para 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 de los IFn en Clojure se refieren a las funciones. Algunos, sin embargo, se refieren a valores de datos no funcionales. Para acceder a estos, use deref lugar de fn :

IFn printLength = Clojure.var("clojure.core", "*print-length*"); IFn deref = Clojure.var("clojure.core", "deref"); deref.invoke(printLength);

Algunas veces (si usa 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, basta con simplemente hacer que la clase se cargue (en el pasado hubo una recomendación similar para cargar la clase RT, que ahora se prefiere):

Class.forName("clojure.java.api.Clojure")


Esto funciona con Clojure 1.5.0:

public class CljTest { public static Object evalClj(String a) { return clojure.lang.Compiler.load(new java.io.StringReader(a)); } public static void main(String[] args) { new clojure.lang.RT(); // needed since 1.5.0 System.out.println(evalClj("(+ 1 2)")); } }


Estoy de acuerdo con la respuesta de Clartaq, pero sentí que los principiantes también podrían usar:

  • información paso a paso sobre cómo obtener esta ejecución
  • información que es actual para Clojure 1.3 y versiones recientes de leiningen.
  • un jarro Clojure que también incluye una función principal, por lo que se puede ejecutar de forma independiente o vinculado como una biblioteca.

Así que cubrí todo eso en esta publicación de blog .

El código de Clojure se ve así:

(ns ThingOne.core (:gen-class :methods [#^{:static true} [foo [int] void]])) (defn -foo [i] (println "Hello from Clojure. My input was " i)) (defn -main [] (println "Hello from Clojure -main." ))

La configuración del proyecto leiningen 1.7.1 tiene este aspecto:

(defproject ThingOne "1.0.0-SNAPSHOT" :description "Hello, Clojure" :dependencies [[org.clojure/clojure "1.3.0"]] :aot [ThingOne.core] :main ThingOne.core)

El código de Java se ve así:

import ThingOne.*; class HelloJava { public static void main(String[] args) { System.out.println("Hello from Java!"); core.foo (12345); } }

O también puede obtener todo el código de este proyecto en github .


Otra técnica que funciona también con otros lenguajes sobre JVM es declarar una interfaz para las funciones que desea llamar y luego usar la función ''proxy'' para crear una instancia que las implemente.


Si el caso de uso es incluir un JAR construido con Clojure en una aplicación Java, he encontrado que tener un espacio de nombres separado para la interfaz entre los dos mundos es beneficioso:

(ns example-app.interop (:require [example-app.core :as core]) ;; This example covers two-way communication: the Clojure library ;; relies on the wrapping Java app for some functionality (through ;; an interface that the Clojure library provides and the Java app ;; implements) and the Java app calls the Clojure library to perform ;; work. The latter case is covered by a class provided by the Clojure lib. ;; ;; This namespace should be AOT compiled. ;; The interface that the java app can implement (gen-interface :name com.example.WeatherForecast :methods [[getTemperature [] Double]]) ;; The class that the java app instantiates (gen-class :name com.example.HighTemperatureMailer :state state :init init ;; Dependency injection - take an instance of the previously defined ;; interface as a constructor argument :constructors {[com.example.WeatherForecast] []} :methods [[sendMails [] void]]) (defn -init [weather-forecast] [[] {:weather-forecast weather-forecast}]) ;; The actual work is done in the core namespace (defn -sendMails [this] (core/send-mails (.state this)))

El espacio de nombres central puede usar la instancia inyectada para realizar sus tareas:

(ns example-app.core) (defn send-mails [{:keys [weather-forecast]}] (let [temp (.getTemperature weather-forecast)] ...))

Para fines de prueba, la interfaz puede ser anulada:

(example-app.core/send-mails (reify com.example.WeatherForecast (getTemperature [this] ...)))


También puede usar la compilación AOT para crear archivos de clase que representan su código clojure. Lea la documentación sobre compilación, gen-class y amigos en los documentos de la API de Clojure para obtener detalles acerca de cómo hacer esto, pero en esencia, creará una clase que llame a funciones clojure para cada invocación de método.

Otra alternativa es utilizar la nueva funcionalidad defprotocol y deftype, que también requerirá la compilación AOT pero proporcionará un mejor rendimiento. Todavía no conozco los detalles de cómo hacer esto, pero una pregunta en la lista de correo probablemente lo solucionaría.


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 construir 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 ejecuta, debería ver algo como:

(binomial 5 3): 10 (binomial 10042 111): 49068389575068144946633777...

Y aquí hay un programa Java que llama a la función -binomial en 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 palabra clave :methods en la declaración gen-class . 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 de envoltura a la que Java pueda llamar. Tenga en cuenta que la segunda versión de -binomial tiene un guión delante.

Y, por supuesto, el propio jar Clojure debe estar en la ruta de la clase. Este ejemplo usó el jarro Clojure-1.1.0.

Actualización : esta respuesta se ha vuelto a probar con las siguientes herramientas:

  • Clojure 1.5.1
  • Leiningen 2.1.3
  • JDK 1.7.0 Actualización 25

La parte Clojure

Primero crea 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 archivo project.clj y edítelo para que los contenidos se muestren 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 contenedor Clojure en este punto.

Ahora edite el archivo de Clojure C:/projects/com.domain.tiny/src/com/domain/tiny.clj manera 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. The :gen-class le dice al sistema que cree una clase llamada 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 llamadas de forma similar binomial , una función Clojure tradicional, y -binomial y envoltorio 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 a otra cosa si se desea. La función -main solo hace 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 la salida que se muestra en la respuesta original.

Ahora empaquételo en un frasco y colóquelo en un lugar conveniente. Copie el tarro 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 integrada, lein-javac , que debería ser capaz de ayudar con la compilación de Java. Desafortunadamente, parece estar roto 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 podría manejar la compilación y el empaquetado también. Pero para este post, vamos a la vieja escuela y lo haremos desde la línea de comando.

Primero crea el archivo Main.java con los contenidos mostrados en la respuesta original.

Para compilar parte de Java

javac -g -cp target/com.domain.tiny-0.1.0-SNAPSHOT.jar -d target/src/com/domain/Main.java

Ahora crea un archivo con cierta metainformación para agregar al 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 gran archivo jar, incluido nuestro programa Clojure y el frasco 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 por Clojure solo, pero el resultado se ha convertido en un doble de Java.

Como se mencionó, un IDE de Java probablemente se encargará de los complicados argumentos de compilación y el empaquetado.


EDITAR: Escribí esta respuesta hace casi tres años. En Clojure 1.6 hay una API adecuada exactamente con el propósito de llamar a Clojure desde Java. Por favor, la respuesta de Alex Miller para obtener información actualizada: https://.com/a/23555959/202121

Respuesta original de 2011:

Tal como lo veo, la forma más simple (si no se genera una clase con compilación AOT) es usar clojure.lang.RT para acceder a funciones en clojure. Con él puedes imitar lo que hubieras hecho en Clojure (sin necesidad de compilar las cosas de manera especial):

;; Example usage of the "bar-fn" function from the "foo.ns" namespace from Clojure (require ''foo.ns) (foo.ns/bar-fn 1 2 3)

Y en Java:

// Example usage of the "bar-fn" function from the "foo.ns" namespace from Java import clojure.lang.RT; import clojure.lang.Symbol; ... RT.var("clojure.core", "require").invoke(Symbol.intern("foo.ns")); RT.var("foo.ns", "bar-fn").invoke(1, 2, 3);

Es un poco más detallado en Java, pero espero que quede claro que los fragmentos de código son equivalentes.

Esto debería funcionar siempre que Clojure y los archivos fuente (o archivos compilados) de su código Clojure estén en el classpath.


EDITAR Esta respuesta se escribió en 2010 y funcionó en ese momento. Vea la respuesta de Alex Miller para 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 la función desde el script, mire al siguiente ejemplo .

Si desea evaluar el código de cadena, dentro de Java, puede usar 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); } }