Chamando o clojure de java

A maioria dos principais hits do Google para “clojure chamando de java” estão desatualizados e recomendo usar clojure.lang.RT para compilar o código-fonte. Você poderia ajudar com uma explicação clara de como chamar o Clojure do Java supondo que você já construiu um jar do projeto Clojure e o incluiu no classpath?

Atualização : Desde que esta resposta foi publicada, algumas das ferramentas disponíveis foram alteradas. Após a resposta original, há uma atualização incluindo informações sobre como construir o exemplo com as ferramentas atuais.

Não é tão simples quanto compilar um jar e chamar os methods internos. Parece haver alguns truques para que tudo funcione. Aqui está um exemplo de um arquivo Clojure simples que pode ser compilado em um jar:

 (ns com.domain.tiny (:gen-class :name com.domain.tiny :methods [#^{:static true} [binomial [int int] double]])) (defn binomial "Calculate the binomial coefficient." [nk] (let [a (inc n)] (loop [b 1 c 1] (if (> bk) c (recur (inc b) (* (/ (- ab) b) c)))))) (defn -binomial "A Java-callable wrapper around the 'binomial' function." [nk] (binomial nk)) (defn -main [] (println (str "(binomial 5 3): " (binomial 5 3))) (println (str "(binomial 10042 111): " (binomial 10042 111))) ) 

Se você executá-lo, você deve ver algo como:

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

E aqui está um programa em Java que chama a function -binomial no 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)); } } 

Sua saída é:

 (binomial 5 3): 10.0 (binomial 10042, 111): 4.9068389575068143E263 

A primeira mágica é usar a palavra-chave :methods na instrução gen-class . Isso parece ser necessário para permitir que você acesse a function Clojure de forma semelhante aos methods estáticos em Java.

A segunda coisa é criar uma function de wrapper que possa ser chamada por Java. Observe que a segunda versão do -binomial tem um traço na frente dele.

E, claro, o jarro do Clojure em si deve estar no caminho da class. Este exemplo usou o jarro Clojure-1.1.0.

Atualização : Esta resposta foi testada novamente usando as seguintes ferramentas:

  • Clojure 1.5.1
  • Leiningen 2.1.3
  • JDK 1.7.0 Atualização 25

A parte do Clojure

Primeiro crie um projeto e uma estrutura de diretório associada usando o Leiningen:

 C:\projects>lein new com.domain.tiny 

Agora, mude para o diretório do projeto.

 C:\projects>cd com.domain.tiny 

No diretório do projeto, abra o arquivo project.clj e edite-o de forma que o conteúdo seja o mostrado abaixo.

 (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) 

Agora, verifique se todas as dependencies (Clojure) estão disponíveis.

 C:\projects\com.domain.tiny>lein deps 

Você pode ver uma mensagem sobre o download do jarro Clojure neste momento.

Agora edite o arquivo Clojure C:\projects\com.domain.tiny\src\com\domain\tiny.clj modo que ele contenha o programa Clojure mostrado na resposta original. (Este arquivo foi criado quando Leiningen criou o projeto.)

Muita da mágica aqui está na declaração do namespace. O :gen-class diz ao sistema para criar uma class denominada com.domain.tiny com um único método estático chamado binomial , uma function que com.domain.tiny dois argumentos inteiros e retorna um double. Existem duas funções de nome similar binomial , uma function tradicional de Clojure e -binomial e wrapper acessíveis a partir de Java. Observe o hífen no nome da function -binomial . O prefixo padrão é um hífen, mas pode ser alterado para outra coisa, se desejado. A function -main faz apenas algumas chamadas para a function binomial para assegurar que estamos obtendo os resultados corretos. Para fazer isso, compile a class e execute o programa.

 C:\projects\com.domain.tiny>lein run 

Você deve ver a saída mostrada na resposta original.

Agora empacote em uma jarra e coloque em algum lugar conveniente. Copie o jarro Clojure lá também.

 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:\clojure-1.5.1.jar" target\lib\ 1 file(s) copied. 

A parte de Java

Leiningen tem uma tarefa embutida, lein-javac , que deve ser capaz de ajudar com a compilation Java. Infelizmente, parece estar quebrado na versão 2.1.3. Não consegue encontrar o JDK instalado e não consegue encontrar o repository Maven. Os caminhos para ambos têm espaços incorporados no meu sistema. Eu suponho que esse é o problema. Qualquer IDE Java também poderia manipular a compilation e o empacotamento. Mas para este post, estamos indo à velha escola e fazendo isso na linha de comando.

Primeiro crie o arquivo Main.java com o conteúdo mostrado na resposta original.

Para compilar a parte java

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

Agora crie um arquivo com algumas meta-informações para adicionar ao jar que queremos construir. No Manifest.txt , adicione o seguinte texto

 Class-Path: lib\com.domain.tiny-0.1.0-SNAPSHOT.jar lib\clojure-1.5.1.jar Main-Class: Main 

Agora empacote tudo em um arquivo jar grande, incluindo nosso programa Clojure e o jarro 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 executar o programa:

 C:\projects\com.domain.tiny\target>java -jar Interop.jar (binomial 5 3): 10.0 (binomial 10042, 111): 4.9068389575068143E263 

A saída é essencialmente idêntica àquela produzida somente pelo Clojure, mas o resultado foi convertido em um duplo Java.

Como mencionado, um IDE Java provavelmente cuidará dos argumentos de compilation desordenados e da embalagem.

A partir do Clojure 1.6.0, há uma nova maneira preferida de carregar e invocar funções do Clojure. Este método agora é preferido para chamar RT diretamente (e substitui muitas das outras respostas aqui). O javadoc está aqui – o principal ponto de input é o clojure.java.api.Clojure .

Para pesquisar e chamar uma function Clojure:

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

Funções no clojure.core são carregadas automaticamente. Outros namespaces podem ser carregados via require:

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

IFn s pode ser passado para funções de ordem superior, por exemplo, o exemplo abaixo passa 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]")); 

A maioria dos IFn no Clojure se refere a funções. Alguns, no entanto, referem-se a valores de dados não funcionais. Para acessá-los, use deref vez de fn :

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

Às vezes (se estiver usando alguma outra parte do tempo de execução do Clojure), você pode precisar garantir que o tempo de execução do Clojure esteja inicializado corretamente – chamar um método na class Clojure é suficiente para essa finalidade. Se você não precisa chamar um método no Clojure, simplesmente fazer com que a class seja carregada é suficiente (no passado, havia uma recomendação semelhante para carregar a class RT; agora é preferível):

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

EDIT Esta resposta foi escrita em 2010 e trabalhou naquele tempo. Veja a resposta de Alex Miller para uma solução mais moderna.

Que tipo de código está chamando de Java? Se você tiver class gerada com gen-class, simplesmente chame-a. Se você quiser chamar a function do script, observe o seguinte exemplo .

Se você quiser avaliar o código da string, dentro do Java, você pode usar o seguinte 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 [ab] (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); } } 

EDIT: eu escrevi essa resposta quase três anos atrás. No Clojure 1.6 existe uma API apropriada exatamente para o propósito de chamar o Clojure de Java. Por favor, a resposta de Alex Miller para informações atualizadas: https://stackoverflow.com/a/23555959/202121

Resposta original de 2011:

A meu ver, a maneira mais simples (se você não gerar uma class com compilation AOT) é usar clojure.lang.RT para acessar funções em clojure. Com ele você pode imitar o que você teria feito no Clojure (não é necessário compilar as coisas de maneiras especiais):

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

E em 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); 

É um pouco mais detalhado em Java, mas espero que esteja claro que os trechos de código são equivalentes.

Isso deve funcionar desde que o Clojure e os arquivos de origem (ou arquivos compilados) do seu código Clojure estejam no caminho de class.

Concordo com a resposta do clartaq, mas senti que os iniciantes também poderiam usar:

  • informações passo a passo sobre como realmente obter esta execução
  • informações atualizadas para o Clojure 1.3 e versões recentes do leiningen.
  • Um jarro Clojure que também inclui uma function principal, para que possa ser executado de forma independente ou vinculada como uma biblioteca.

Então eu cobri tudo isso neste blog .

O código Clojure parece com isso:

 (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." )) 

A configuração do projeto leiningen 1.7.1 é assim:

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

O código Java se parece com isto:

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

Ou você também pode obter todo o código deste projeto no github .

Isso funciona com o 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)")); } } 

Se o caso de uso include um JAR criado com o Clojure em um aplicativo Java, acho que ter um namespace separado para a interface entre os dois mundos é benéfico:

 (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))) 

O namespace do núcleo pode usar a instância injetada para realizar suas tarefas:

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

Para fins de teste, a interface pode ser stubbed:

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

Outra técnica que funciona também com outras linguagens sobre a JVM é declarar uma interface para as funções que você deseja chamar e, em seguida, usar a function ‘proxy’ para criar uma instância que as implemente.

Você também pode usar a compilation AOT para criar arquivos de class que representam seu código clojure. Leia a documentação sobre compilation, gen-class e amigos nos documentos da API do Clojure para obter detalhes sobre como fazer isso, mas, em essência, você criará uma class que chama funções clojure para cada chamada de método.

Outra alternativa é usar a nova funcionalidade defprotocol e deftype, que também exigirá compilation AOT, mas fornecerá melhor desempenho. Eu não sei os detalhes de como fazer isso ainda, mas uma pergunta na lista de discussão provavelmente faria o truque.