자바에서 클로저 호출


165

"java에서 clojure 호출"에 대한 Google의 인기 히트작 대부분이 오래되었으며 clojure.lang.RT소스 코드를 컴파일하는 데 사용 하는 것이 좋습니다 . Clojure 프로젝트에서 jar를 이미 빌드하고 클래스 경로에 포함했다고 가정하면 Java에서 Clojure를 호출하는 방법에 대한 명확한 설명을 도울 수 있습니까?


8
나는 매번 소스를 컴파일하는 것이 "오래된"것이라는 것을 모른다. 디자인 결정입니다. Clojure 코드를 레거시 Java Netbeans 프로젝트에 통합하는 것이 간단 해짐에 따라 지금하고 있습니다. 라이브러리로 Clojure를 추가하고, Clojure 소스 파일을 추가하고, 호출을 설정하고, 여러 컴파일 / 링크 단계없이 즉각적인 Clojure 지원! 각 앱 시작마다 약간의 지연이 발생합니다.
Brian Knoblauch


2
Clojure 1.8.0의 최신 정보를 참조하십시오. — Clojure에는 이제 컴파일러 직접 링크가 있습니다.
TWR Cole

답변:


167

업데이트 :이 답변이 게시 된 이후 사용 가능한 도구 중 일부가 변경되었습니다. 원래 답변 후에 현재 도구로 예제를 작성하는 방법에 대한 정보가 포함 된 업데이트가 있습니다.

항아리를 컴파일하고 내부 메소드를 호출하는 것만 큼 간단하지 않습니다. 그래도 모두 작동하게 만드는 몇 가지 트릭이있는 것 같습니다. 다음은 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는 복잡한 컴파일 인수 및 패키징을 처리합니다.


4
1. "ns"매크로 의 예제로 clojuredocs.org 에 예제를 넣을 수 있습니까? 2. ": methods [# ^ {: static true} [binomial [int int] double]]" "앞에있는 # ^는 무엇입니까?
Belun

5
@Belun, 당신은 그것을 예를 들어 사용할 수 있습니다-나는 아첨합니다. "# ^ {: static true}"는 이항이 정적 함수임을 나타내는 일부 메타 데이터를 함수에 첨부합니다. 이 경우 Java 측에서는 정적 함수 인 main 함수를 호출하기 때문에 필요합니다. 이항이 정적이 아닌 경우 Java 측의 기본 함수를 컴파일하면 "정적 컨텍스트에서 비 정적 메소드 binomial (int, int)을 참조 할 수 없습니다"라는 오류 메시지가 표시됩니다. Object Mentor 사이트에 추가 예제가 있습니다.
clartaq

4
Clojure 파일을 Java 클래스로 컴파일하려면 여기에 언급되지 않은 중요한 사항이 있습니다. (compile 'com.domain.tiny)
Domchi

Clojure 소스를 어떻게 컴파일합니까? 내가 붙어있는 곳입니다.
Matthew Boston

@MatthewBoston 답변을 작성할 당시 NetBeans IDE 용 플러그인 Enclojure를 사용했습니다. 이제 Leiningen을 사용하지만 시도하거나 테스트하지는 않았습니다.
clartaq

119

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

1
이 방법을 사용하면 스크립트는 따옴표 '()를 사용할 수 없으며 요구할 사항이 있습니다. 그에 대한 해결책이 있습니까?
Renato

2
나는 var로 존재하지 않는 두 가지 특수 형식 만 있다고 생각합니다. 한 가지 해결 방법은을 통해 액세스하는 것 Clojure.read("'(1 2 3")입니다. Clojure.quote ()를 제공하거나 var로 작동하도록하여 개선 요청으로이를 제출하는 것이 합리적입니다.
Alex Miller

1
@Renato 어쨌든 Clojure의 평가 규칙에 의해 평가되는 것은 없기 때문에 아무것도 인용 할 필요가 없습니다. 1-3의 숫자를 포함하는 목록을 원한다면 쓰지 않고 '(1 2 3)다음과 같이 쓰십시오 Clojure.var("clojure.core", "list").invoke(1,2,3). 그리고 그 대답은 이미 사용 방법의 예를 가지고 있습니다 require: 그것은 다른 것과 마찬가지로 var입니다.
amalloy

34

편집 이 답변은 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);
  }
}

1
네임 스페이스에서 def'd var (예 : (def my-var 10))에 액세스하려는 경우 조금 확장하려면 this을 사용하십시오 RT.var("namespace", "my-var").deref().
Ivan Koblik

RT.load("clojure/core");처음에 추가하지 않으면 작동하지 않습니다 . 이상한 행동?
hsestupin

이것은 작동하며 내가 사용한 것과 비슷합니다. 최신 기술인지 확실하지 않습니다. Clojure 1.4 또는 1.6을 사용하고있을 수 있습니다.
Yan King Yin

12

편집 : 나는 거의 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 코드의 소스 파일 (또는 컴파일 된 파일)이 클래스 경로에있는 한 작동합니다.


1
이 조언은 Clojure 1.6에서 오래되었습니다. 대신 clojure.java.api.Clojure를 사용하십시오.
Alex Miller

당시 나쁜 대답은 아니지만 Clojure 1.6의 권위있는 답변으로 stackoverflow.com/a/23555959/1756702 를 현상금 으로 바꿨습니다. ... 내일 다시 시도 할 것이다
A. 웹이에게

Alex의 대답은 실제로 현상금이 필요합니다! 필요한 경우 현상금을 이전하는 데 도움을 줄 수 있는지 알려주십시오.
raek

@raek 나는 카페인이 과도하게 함유 된 방아쇠 손가락에서 약간의 보너스를 받았다고 생각하지 않습니다. Clojure 태그를 다시 you기를 바랍니다.
A. Webb

10

나는 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의이 프로젝트 에서 모든 코드를 가져올 수도 있습니다 .


왜 AOT를 사용 했습니까? 프로그램이 더 이상 플랫폼 독립적이지 않습니까?
Edward

3

이것은 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)"));
    }
}

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] ...)))

0

JVM 위에서 다른 언어와 함께 작동하는 다른 기술은 호출하려는 함수에 대한 인터페이스를 선언 한 다음 '프록시'함수를 사용하여이를 구현하는 인스턴스를 작성하는 것입니다.


-1

AOT 컴파일을 사용하여 클로저 코드를 나타내는 클래스 파일을 만들 수도 있습니다. 이를 수행하는 방법에 대한 자세한 내용은 Clojure API 문서에서 컴파일, gen-class 및 친구에 대한 문서를 읽으십시오. 그러나 본질적으로 각 메소드 호출에 대해 clojure 함수를 호출하는 클래스를 작성합니다.

또 다른 대안은 새로운 defprotocol 및 deftype 기능을 사용하는 것인데, AOT 컴파일이 필요하지만 더 나은 성능을 제공합니다. 이 작업을 수행하는 방법에 대한 자세한 내용은 아직 알지 못하지만 메일 링리스트에 관한 질문이있을 수 있습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.