Вызов clojure из java

Большинство верхних хитов google для "вызова clojure из java" устарели и рекомендуют использовать clojure.lang.RT для компиляции исходного кода. Не могли бы вы помочь с ясным объяснением того, как вызывать clojure из Java, предположив, что вы уже создали jar из проекта clojure и включили его в путь к классам?

Ответ 1

Обновление. Поскольку этот ответ был опубликован, некоторые из доступных инструментов были изменены. После первоначального ответа есть обновление, включающее информацию о том, как создать пример с помощью текущих инструментов.

Это не так просто, как компиляция в банку и вызов внутренних методов. Кажется, есть несколько трюков, чтобы все это работало. Вот пример простого файла Clojure, который может быть скомпилирован в банку:

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

Если вы запустите его, вы должны увидеть что-то вроде:

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

И здесь программа Java, которая вызывает функцию -binomial в 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));
    }
}

На выходе:

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

Первая часть магии использует ключевое слово :methods в инструкции gen-class. Кажется, что вам требуется доступ к функции Clojure, как статические методы в Java.

Во-вторых, нужно создать функцию-оболочку, которую может вызывать Java. Обратите внимание, что вторая версия -binomial имеет перед ним черту.

И, конечно, сам баннер Clojure должен находиться на пути к классу. В этом примере используется Clojure -1.1.0 jar.

Обновить. Этот ответ был повторно протестирован с использованием следующих инструментов:

  • Clojure 1.5.1
  • Leiningen 2.1.3
  • Обновление JDK 1.7.0 25

Clojure Часть

Сначала создайте проект и связанную структуру каталогов с помощью Leiningen:

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

Теперь перейдите в каталог проекта.

C:\projects>cd com.domain.tiny

В каталоге проекта откройте файл project.clj и отредактируйте его так, чтобы содержимое было показано ниже.

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

Теперь убедитесь, что доступны все зависимости (Clojure).

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

В этот момент вы можете увидеть сообщение о загрузке баннера Clojure.

Теперь отредактируйте файл Clojure C:\projects\com.domain.tiny\src\com\domain\tiny.clj таким образом, чтобы он содержал программу Clojure, показанную в исходном ответе. (Этот файл был создан, когда Leiningen создал проект.)

Большая часть магии здесь содержится в объявлении пространства имен. :gen-class указывает системе создать класс с именем com.domain.tiny с помощью одного статического метода с именем binomial, который принимает два целочисленных аргумента и возвращает double. Существуют две аналогично названные функции binomial, традиционная функция Clojure и -binomial и оболочка, доступная из Java. Обратите внимание на дефис в имени функции -binomial. Префикс по умолчанию - дефис, но при желании его можно изменить на что-то другое. Функция -main просто делает пару вызовов биномиальной функции, чтобы убедиться, что мы получаем правильные результаты. Для этого скомпилируйте класс и запустите программу.

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

Вы должны увидеть результат, указанный в исходном ответе.

Теперь упакуйте его в банку и поместите его в удобное место. Скопируйте там же банку Clojure.

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.

Часть Java

Leiningen имеет встроенную задачу lein-javac, которая должна помочь в компиляции Java. К сожалению, это похоже на версию 2.1.3. Он не может найти установленный JDK, и он не может найти репозиторий Maven. Пути к обоим имеют встроенные пространства в моей системе. Я предполагаю, что это проблема. Любая Java IDE может обрабатывать компиляцию и упаковку. Но для этого поста мы учимся в старой школе и делаем это в командной строке.

Сначала создайте файл Main.java с содержимым, указанным в исходном ответе.

Скомпилировать java-часть

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

Теперь создайте файл с некоторой метаинформацией, чтобы добавить к банке, которую мы хотим построить. В Manifest.txt добавьте следующий текст

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

Теперь упакуйте все это в один большой файл jar, включая нашу программу Clojure и банку 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

Чтобы запустить программу:

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

Вывод по существу идентичен тому, который создается только с помощью Clojure, но результат был преобразован в Java double.

Как уже упоминалось, Java IDE, вероятно, позаботится о беспорядочных аргументах компиляции и упаковке.

Ответ 2

Как и в случае с Clojure 1.6.0, существует новый предпочтительный способ загрузки и вызова функций Clojure. Этот метод теперь предпочитает напрямую обращаться к RT (и заменяет многие другие ответы здесь). Здесь javadoc - главная точка входа clojure.java.api.Clojure.

Для поиска и вызова функции Clojure:

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

Функции в clojure.core автоматически загружаются. Другие пространства имен могут быть загружены через require:

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

IFn может передаваться функциям более высокого порядка, например. пример ниже проходит от plus до read:

IFn map = Clojure.var("clojure.core", "map");
IFn inc = Clojure.var("clojure.core", "inc");
map.invoke(inc, Clojure.read("[1 2 3]"));

Большинство IFn в Clojure относятся к функциям. Однако некоторые из них относятся к значениям нефункциональных данных. Для доступа к ним используйте deref вместо fn:

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

Иногда (если используется какая-то другая часть среды выполнения Clojure), вам может потребоваться обеспечить правильную инициализацию среды выполнения Clojure - для этого достаточно вызвать метод класса Clojure. Если вам не нужно вызывать метод на Clojure, то просто вызвать загрузку класса достаточно (в прошлом была аналогичная рекомендация по загрузке класса RT, теперь это предпочтительнее):

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

Ответ 3

EDIT Этот ответ был написан в 2010 году и работал в то время. См. Ответ Алекс Миллер для более современного решения.

Какой код звонит с Java? Если у вас есть класс, сгенерированный с gen-классом, просто назовите его. Если вы хотите вызвать функцию из script, посмотрите в в следующем примере.

Если вы хотите оценить код из строки, внутри Java, то вы можете использовать следующий код:

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);
  }
}

Ответ 4

РЕДАКТИРОВАТЬ: я написал этот ответ почти три года назад. В Clojure 1.6 существует соответствующий API именно для вызова Clojure из Java. Пожалуйста, ответите Алексу Миллеру для получения актуальной информации.

Оригинальный ответ с 2011 года:

На мой взгляд, самый простой способ (если вы не генерируете класс с компиляцией AOT) - это использовать clojure.lang.RT для доступа к функциям в clojure. С его помощью вы можете имитировать то, что вы сделали бы в Clojure (не нужно специально компилировать вещи):

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

И на Яве:

// 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);

Это немного более многословно в Java, но я надеюсь, что ясно, что части кода эквивалентны.

Это должно работать, пока Clojure и исходные файлы (или скомпилированные файлы) вашего кода Clojure находятся в пути к классам.

Ответ 5

Я согласен с ответом clartaq, но я чувствовал, что новички могут также использовать:

  • пошаговая информация о том, как на самом деле запустить этот
  • информация, актуальная для Clojure 1.3 и последних версий leiningen.
  • a Clojure jar, который также включает основную функцию, поэтому его можно запустить автономно или связать как библиотеку.

Итак, я рассмотрел все это в этом сообщении в блоге.

Код Clojure выглядит следующим образом:

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

Настройка проекта leiningen 1.7.1 выглядит следующим образом:

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

Код Java выглядит следующим образом:

import ThingOne.*;

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

Или вы также можете получить весь код из этого проекта в github.

Ответ 6

Это работает с 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)"));
    }
}

Ответ 7

Если вариант использования состоит в том, чтобы включить JAR, построенный с помощью Clojure в Java-приложении, я нашел наличие отдельного пространства имен для интерфейса между двумя мирами, чтобы быть полезным:

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

Основное пространство имен может использовать инъецируемый экземпляр для выполнения своих задач:

(ns example-app.core)

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

В целях тестирования интерфейс может быть заглушен:

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

Ответ 8

Другой метод, который работает также с другими языками поверх JVM, заключается в объявлении интерфейса для функций, которые вы хотите вызвать, а затем использовать функцию "прокси" для создания экземпляра, который имплементирует их.

Ответ 9

Вы также можете использовать компиляцию AOT для создания файлов классов, представляющих ваш код clojure. Прочтите документацию о компиляции, gen-class и друзей в документах API clojure для получения подробной информации о том, как это сделать, но по существу вы создадите класс, который вызывает функции clojure для каждого вызова метода.

Другой альтернативой является использование новых функций defprotocol и deftype, которые также потребуют компиляции AOT, но обеспечивают лучшую производительность. Я не знаю подробностей о том, как это сделать, но вопрос в списке рассылки, вероятно, сделает трюк.