Вызов clojure из java

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


person Arthur Ulfeldt    schedule 02.02.2010    source источник
comment
Я не знаю, что компиляция исходников каждый раз устарела как таковая. Это дизайнерское решение. Я делаю это сейчас, поскольку это упрощает интеграцию кода Clojure в устаревший проект Java Netbeans. Добавьте Clojure в качестве библиотеки, добавьте исходный файл Clojure, настройте вызовы и мгновенную поддержку Clojure без выполнения нескольких этапов компиляции / компоновки! Ценой задержки в доли секунды при запуске каждого приложения.   -  person Brian Knoblauch    schedule 28.12.2011
comment
См. последнюю версию Clojure 1.6.0.   -  person A. Webb    schedule 09.05.2014
comment
См. последнее для Clojure 1.8.0 - Clojure теперь имеет компилятор прямая ссылка.   -  person T.W.R. Cole    schedule 16.02.2016


Ответы (9)


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

Это не так просто, как компиляция в 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 стоит тире.

И, конечно же, сама банка 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

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

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

На этом этапе вы можете увидеть сообщение о загрузке jar-файла Clojure.

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

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

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

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

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

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

person clartaq    schedule 02.02.2010
comment
1. Могу ли я разместить ваш пример на clojuredocs.org в качестве примера для макроса ns? 2. что # ^ перед: методы [# ^ {: static true} [binomial [int int] double]] (я новичок)? - person Belun; 20.09.2010
comment
@Belun, конечно, ты можешь использовать это в качестве примера - я польщен. # ^ {: Static true} присоединяет к функции некоторые метаданные, указывающие, что биномиальная функция является статической. В данном случае это необходимо, потому что на стороне Java мы вызываем функцию из main - статическую функцию. Если бы биномиальные числа не были статическими, компиляция основной функции на стороне Java выдала бы сообщение об ошибке о том, что на нестатический метод, на биномиальный (int, int) нельзя ссылаться из статического контекста. На сайте Object Mentor есть дополнительные примеры. - person clartaq; 20.09.2010
comment
Здесь не упоминается одна важная вещь - чтобы скомпилировать файл Clojure в класс Java, вам необходимо: (скомпилировать com.domain.tiny) - person Domchi; 22.10.2011
comment
как вы AOT компилируете исходный код Clojure? Вот где я застрял. - person Matthew Boston; 16.05.2012
comment
@MatthewBoston На момент написания ответа я использовал Enclojure, плагин для IDE NetBeans. Я бы, вероятно, использовал Leiningen, но не пробовал и не тестировал его. - person clartaq; 21.06.2012

Начиная с 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") 
person Alex Miller    schedule 09.05.2014
comment
При таком подходе скрипт не может использовать кавычки '() и требовать вещи. Есть ли решение для этого? - person Renato; 19.01.2017
comment
Я думаю, что есть только пара особых форм, которые также не существуют как вары. Одно из решений - получить к нему доступ через Clojure.read("'(1 2 3"). Было бы разумно подать это как запрос на улучшение, хотя бы предоставить Clojure.quote () или заставить его работать как var. - person Alex Miller; 19.01.2017
comment
@Renato Нет необходимости цитировать что-либо, потому что все равно ничего не оценивается правилами оценки Clojure. Если вам нужен список, содержащий числа от 1 до 3, то вместо записи '(1 2 3) вы пишете что-то вроде Clojure.var("clojure.core", "list").invoke(1,2,3). И в ответе уже есть пример использования require: это просто переменная, как и все остальные. - person amalloy; 27.09.2017

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

Какой код звонит с Явы? Если у вас есть класс, созданный с помощью 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);
  }
}
person Alex Ott    schedule 02.02.2010
comment
Чтобы немного расширить, если вы хотите получить доступ к def'd var в пространстве имен (т.е. (def my-var 10)), используйте этот RT.var("namespace", "my-var").deref(). - person Ivan Koblik; 14.11.2012
comment
У меня не работает без добавления RT.load("clojure/core"); в начале. Что за странное поведение? - person hsestupin; 21.12.2012
comment
Это работает и похоже на то, что я использовал; не уверен, что это новейшая техника. Возможно, я использую Clojure 1.4 или 1.6, но не уверен. - person Yan King Yin; 09.12.2014

РЕДАКТИРОВАТЬ: Я написал этот ответ почти три года назад. В 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 находятся в пути к классам.

person raek    schedule 20.06.2011
comment
Этот совет устарел в Clojure 1.6 - используйте вместо него clojure.java.api.Clojure. - person Alex Miller; 12.05.2014
comment
Неплохой ответ в то время, но я хотел наградить stackoverflow.com/a/23555959/1756702 как авторитетный ответ для Clojure 1.6. Попробую завтра ... - person A. Webb; 12.05.2014
comment
Ответ Алекса действительно заслуживает награды! Пожалуйста, дайте мне знать, если я могу помочь с переводом награды, если это необходимо. - person raek; 13.05.2014
comment
@raek Я не возражаю, что ты получил небольшой бонус от моего триггерного пальца с избытком кофеина. Надеюсь снова увидеть вас вокруг тега Clojure. - person A. Webb; 13.05.2014

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

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

Я рассмотрел все это в это сообщение в блоге.

Код 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.

person sandover    schedule 29.03.2012
comment
Почему вы использовали AOT? Разве это не приводит к тому, что программа больше не зависит от платформы? - person Edward; 15.10.2014

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

Если вариант использования состоит в том, чтобы включить 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] ...)))
person jstaffans    schedule 08.01.2016

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

person Petr Gladkikh    schedule 17.02.2010

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

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

person rosejn    schedule 02.02.2010