Вопрос-ответ

Calling clojure from java

Вызов clojure из java

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

Переведено автоматически
Ответ 1

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

Это не так просто, как скомпилировать в jar и вызвать внутренние методы. Кажется, есть несколько хитростей, чтобы заставить все это работать. Вот пример простого файла Clojure, который можно скомпилировать в 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)))
)

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

(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 перед ней стоит прочерк.

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

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


  • Clojure 1.5.1

  • Leiningen 2.1.3

  • Обновление JDK 1.7.0 25

Часть Clojure

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

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

Now, change to the project directory.

C:\projects>cd com.domain.tiny

In the project directory, open the project.clj file and edit it such that the contents are as shown below.

(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 jar.

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

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

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

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

Теперь упакуйте это в jar и поместите в удобное место. Скопируйте jar 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

Теперь создайте файл с некоторой метаинформацией для добавления в jar, который мы хотим создать. В 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 jar.

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

IFns могут быть переданы функциям более высокого порядка, например, приведенный ниже пример передает 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

РЕДАКТИРОВАТЬ Этот ответ был написан в 2010 году и работал в то время. Смотрите Ответ Алекса Миллера для получения более современного решения.

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

Если вы хотите вычислить код из строки внутри 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)

И в 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);

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

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

java