Pergunta

A maioria dos principais hits do Google para "Calling Clojure de Java" está desatualizada e recomendando o uso clojure.lang.RT Para compilar o código -fonte. Você poderia ajudar com uma explicação clara de como chamar Clojure de Java, assumindo que você já construiu um frasco do projeto Clojure e o incluiu no caminho de classe?

Foi útil?

Solução

Atualizar: Como essa resposta foi publicada, algumas das ferramentas disponíveis mudaram. Após a resposta original, há uma atualização, incluindo informações sobre como criar o exemplo com as ferramentas atuais.

Não é tão simples quanto compilar em um frasco e chamar os métodos internos. Parece haver alguns truques para fazer tudo funcionar. Aqui está um exemplo de um simples arquivo de clojure que pode ser compilado em uma jarra:

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

Se você executar, você deve ver algo como:

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

E aqui está um programa Java que chama o -binomial função 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));
    }
}

A saída é:

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

A primeira peça de magia é usar o :methods palavra -chave no gen-class declaração. Isso parece ser necessário para permitir que você acesse o clojure função algo como métodos estáticos no Java.

A segunda coisa é criar uma função de invólucro que possa ser chamada por Java. Observe que a segunda versão de -binomial tem uma corrida na frente dele.

E, claro, o próprio jarro de clojure deve estar no caminho da classe. Este exemplo usou o jar clojure-1.1.0.

Atualizar: 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 da clojure

Primeiro, crie um projeto e uma estrutura de diretório associada usando 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 project.clj Arquive e edite de modo que o conteúdo seja como 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 dependências (clojure) estão disponíveis.

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

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

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

Grande parte da magia aqui está na declaração de namespace. o :gen-class diz ao sistema para criar uma classe nomeada com.domain.tiny com um único método estático chamado binomial, uma função assumindo dois argumentos inteiros e retornando um duplo. Existem duas funções nomeadas igualmente binomial, uma função tradicional de clojure, e -binomial e invólucro acessível a partir de Java. Observe o hífen no nome da função -binomial. O prefixo padrão é um hífen, mas pode ser alterado para outra coisa, se desejar. o -main A função apenas faz algumas chamadas para a função binomial para garantir que estamos obtendo os resultados corretos. Para fazer isso, compile a classe e execute o programa.

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

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

Agora o empacote em uma jarra e coloque -o em algum lugar conveniente. Copie o frasco de 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:<path to clojure jar>\clojure-1.5.1.jar" target\lib\
        1 file(s) copied.

A parte java

Leiningen tem uma tarefa embutida, lein-javac, isso deve ser capaz de ajudar com a compilação Java. Infelizmente, parece estar quebrado na versão 2.1.3. Ele não consegue encontrar o JDK instalado e não consegue encontrar o repositório Maven. Os caminhos para ambos têm espaços incorporados no meu sistema. Presumo que esse seja o problema. Qualquer IDE Java também poderia lidar com a compilação e embalagem. Mas para este post, estamos indo para a 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 Java Part

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 frasco que queremos construir. Dentro 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 grande JAR, incluindo nosso programa de clojure e o jarro de 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 à produzida apenas pelo clojure, mas o resultado foi convertido em um duplo Java.

Como mencionado, um Java IDE provavelmente cuidará dos argumentos de compilação confusos e da embalagem.

Outras dicas

A partir de clojure 1.6.0, existe uma nova maneira preferida de carregar e invocar funções de clojure. Este método agora é preferido em chamar RT diretamente (e substitui muitas das outras respostas aqui). O javadoc é aqui - O ponto de entrada principal é clojure.java.api.Clojure.

Para procurar e chamar uma função de clojure:

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

Funções em clojure.core são carregados automaticamente. Outros namespaces podem ser carregados via requer:

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

IFns 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 IFns em clojure refere -se a funções. Alguns, no entanto, se referem a valores de dados não funções. Para acessar, use deref ao invés 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 outra parte do tempo de execução do clojure), pode ser necessário garantir que o tempo de execução do clojure seja inicializado corretamente - chamar um método na classe Clojure é suficiente para esse fim. Se você não precisar chamar um método no Clojure, simplesmente fazer com que a classe seja suficiente seja suficiente (no passado, houve uma recomendação semelhante para carregar a classe RT; agora isso é preferido):

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

EDITAR Esta resposta foi escrita em 2010 e trabalhou na época. Veja a resposta de Alex Miller para obter uma solução mais moderna.

Que tipo de código está chamando de Java? Se você tem classe gerada com a classe Gen, basta chamá-la. Se você quiser chamar a função do script, procure A seguir.

Se você deseja avaliar o código da String, dentro do Java, poderá 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 [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);
  }
}

EDITAR: Eu escrevi essa resposta há quase três anos. No Clojure 1.6, existe uma API adequada exatamente com o objetivo de chamar Clojure de Java. Por favor Resposta de Alex Miller Para informações atualizadas.

Resposta original de 2011:

Eu o vejo, a maneira mais simples (se você não gerar uma classe com compilação AOT) é usar clojure.lang.rt para acessar funções no clojure. Com ele, você pode imitar o que teria feito no Clojure (não há necessidade de 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 as partes do código são equivalentes.

Isso deve funcionar enquanto o clojure e os arquivos de origem (ou arquivos compilados) do seu código de clojure estiverem no caminho de classe.

Eu concordo com a resposta de Clartaq, mas senti que os iniciantes também poderiam usar:

  • informações passo a passo sobre como realmente fazer isso em execução
  • Informações atuais para o Clojure 1.3 e versões recentes de Leiningen.
  • um frasco de clojure que também inclui uma função principal, para que possa ser executado independente ou ligado como uma biblioteca.

Então eu cobri tudo isso em esta postagem do blog.

O código do clojure se parece com o seguinte:

(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 se parece com a seguinte:

(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 o seguinte:

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 de Este projeto no Github.

Isso funciona com 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 for incluir um frasco construído com clojure em um aplicativo Java, descobri ter um espaço de nome separado para a interface entre os dois mundos para ser 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 espaço nomes principal 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 arrancada:

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

Outra técnica que funciona também com outros idiomas no topo da JVM é declarar uma interface para as funções que você deseja chamar e, em seguida, usar a função 'proxy' para criar uma instância que as impele.

Você também pode usar a compilação AOT para criar arquivos de classe que representam seu código de clojure. Leia a documentação sobre compilação, classe Gen e amigos na API Clojure Docs para obter detalhes sobre como fazer isso, mas, em essência, você criará uma classe que chama funções de clojure para cada invocação do método.

Outra alternativa é usar a nova funcionalidade DeFprotocol e Deftype, que também exigirá compilação AOT, mas fornecerá melhor desempenho. Ainda não sei os detalhes de como fazer isso, mas uma pergunta na lista de discussão provavelmente faria o truque.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top