[java] 자바에서 클로저 호출
“java에서 clojure 호출”에 대한 Google의 인기 히트작 대부분이 오래되었으며 clojure.lang.RT
소스 코드를 컴파일하는 데 사용 하는 것이 좋습니다 . Clojure 프로젝트에서 jar를 이미 빌드하고 클래스 경로에 포함했다고 가정하면 Java에서 Clojure를 호출하는 방법에 대한 명확한 설명을 도울 수 있습니까?
답변
업데이트 :이 답변이 게시 된 이후 사용 가능한 도구 중 일부가 변경되었습니다. 원래 답변 후에 현재 도구로 예제를 작성하는 방법에 대한 정보가 포함 된 업데이트가 있습니다.
항아리를 컴파일하고 내부 메소드를 호출하는 것만 큼 간단하지 않습니다. 그래도 모두 작동하게 만드는 몇 가지 트릭이있는 것 같습니다. 다음은 jar로 컴파일 할 수있는 간단한 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...
그리고의 -binomial
함수 를 호출하는 Java 프로그램이 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
것입니다. Java의 정적 메소드와 같은 Clojure 함수에 액세스 할 수 있어야합니다.
두 번째는 Java에서 호출 할 수있는 랩퍼 함수를 작성하는 것입니다. 두 번째 버전은-binomial
앞에는 대시가 있습니다.
물론 Clojure jar 자체는 클래스 경로에 있어야합니다. 이 예제는 Clojure-1.1.0 jar을 사용했습니다.
업데이트 :이 답변은 다음 도구를 사용하여 다시 테스트되었습니다.
- 클로저 1.5.1
- 라이닝 겐 2.1.3
- JDK 1.7.0 업데이트 25
클로저 부분
먼저 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 jar 다운로드에 대한 메시지가 표시 될 수 있습니다.
이제 C:\projects\com.domain.tiny\src\com\domain\tiny.clj
원래 답변에 표시된 Clojure 프로그램이 포함 되도록 Clojure 파일을 편집하십시오 . (이 파일은 Leiningen이 프로젝트를 만들 때 생성되었습니다.)
여기서 마술의 대부분은 네임 스페이스 선언에 있습니다. 이 :gen-class
시스템은 두 개의 정수 인수를 취하고 double을 리턴하는 함수 인 com.domain.tiny
이라는 단일 정적 메소드로 이름이 지정된 클래스를 작성하도록 시스템에 지시합니다 binomial
. 이와 유사한 두 개의 함수 binomial
, 전통적인 Clojure 함수 및 -binomial
Java에서 액세스 가능한 랩퍼가 있습니다. 함수 이름의 하이픈에 유의하십시오 -binomial
. 기본 접두사는 하이픈이지만 원하는 경우 다른 것으로 변경할 수 있습니다. 이 -main
함수는 이항 함수를 몇 번 호출하여 올바른 결과를 얻도록합니다. 그렇게하려면 클래스를 컴파일하고 프로그램을 실행하십시오.
C:\projects\com.domain.tiny>lein run
원래 답변에 출력이 표시되어야합니다.
이제 항아리에 포장하고 편리한 곳에 두십시오. Clojure jar도 복사하십시오.
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.
자바 부분
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
이제 Clojure 프로그램과 Clojure jar를 포함하여 하나의 큰 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는 복잡한 컴파일 인수 및 패키징을 처리합니다.
답변
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
s는 고차 함수로 전달 될 수 있습니다. 예를 들어 아래 예제는 다음으로 전달 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")
답변
편집 이 답변은 2010 년에 작성되었으며 그 당시에 효과가있었습니다. 보다 현대적인 솔루션에 대해서는 Alex Miller의 답변을 참조하십시오.
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);
}
}
답변
편집 : 나는 거의 3 년 전에이 답변을 썼습니다. Clojure 1.6에는 Java에서 Clojure를 호출하기위한 정확한 API가 있습니다. 제발 알렉스 밀러의 대답 최신 정보를합니다.
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 코드의 소스 파일 (또는 컴파일 된 파일)이 클래스 경로에있는 한 작동합니다.
답변
나는 clartaq의 대답에 동의하지만 초보자도 사용할 수 있다고 생각했습니다.
- 실제로 실행하는 방법에 대한 단계별 정보
- Clojure 1.3 및 최신 버전의 leiningen에 대한 최신 정보.
- 기본 기능을 포함하는 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의이 프로젝트 에서 모든 코드를 가져올 수도 있습니다 .
답변
이것은 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)"));
}
}
답변
유스 케이스가 Java 애플리케이션에 Clojure로 빌드 된 JAR을 포함하는 경우 두 세계 간의 인터페이스에 유리한 별도의 네임 스페이스가 있음을 발견했습니다.
(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] ...)))