Llamando a clojure desde java

Resuelto Arthur Ulfeldt asked hace 14 años • 10 respuestas

La mayoría de los principales resultados de Google para "llamar a clojure desde Java" están desactualizados y recomiendan su uso clojure.lang.RTpara 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?

Arthur Ulfeldt avatar Feb 02 '10 10:02 Arthur Ulfeldt
Aceptado

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 -binomialfunció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 :methodspalabra clave en la gen-classdeclaració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 -binomialtiene 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.cljarchivo 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.cljde 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-classdice al sistema que cree una clase nombrada com.domain.tinycon 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 -binomialun 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 -mainfunció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-javacque 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.javacon 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.

clartaq avatar Feb 02 '2010 20:02 clartaq

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.corese 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"));

IFns se puede pasar a funciones de orden superior, por ejemplo, el siguiente ejemplo pasa plusa 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 IFnde 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 derefen 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") 
Alex Miller avatar May 09 '2014 03:05 Alex Miller

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);
  }
}
Alex Ott avatar Feb 02 '2010 08:02 Alex Ott