[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 함수 및 -binomialJava에서 액세스 가능한 랩퍼가 있습니다. 함수 이름의 하이픈에 유의하십시오 -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"));

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

IFnClojure의 대부분 은 함수를 나타냅니다. 그러나 일부는 비 기능 데이터 값을 나타냅니다. 이에 액세스하려면 다음 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] ...)))