「Javaからのclojureの呼び出し」に関するGoogleの上位ヒットのほとんどは古くclojure.lang.RT
、ソースコードのコンパイルに使用することを推奨しています。Clojureプロジェクトからすでにjarを構築し、それをクラスパスに含めていると仮定して、JavaからClojureを呼び出す方法の明確な説明を手伝っていただけませんか?
「Javaからのclojureの呼び出し」に関するGoogleの上位ヒットのほとんどは古くclojure.lang.RT
、ソースコードのコンパイルに使用することを推奨しています。Clojureプロジェクトからすでにjarを構築し、それをクラスパスに含めていると仮定して、JavaからClojureを呼び出す方法の明確な説明を手伝っていただけませんか?
回答:
更新:この回答が投稿されてから、使用可能なツールの一部が変更されました。元の回答の後に、現在のツールを使用して例を構築する方法に関する情報を含む更新があります。
jarにコンパイルして内部メソッドを呼び出すほど簡単ではありません。ただし、すべてを機能させるためのいくつかのトリックがあるようです。次に、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関数にアクセスするために必要なようです。
2つ目は、Javaから呼び出すことができるラッパー関数を作成することです。の2番目のバージョンの-binomial
前にダッシュがあることに注意してください。
そしてもちろん、Clojure jar自体がクラスパス上になければなりません。この例では、Clojure-1.1.0 jarを使用しました。
更新:この回答は、次のツールを使用して再テストされています。
Clojureパート
まず、ライニンゲンを使用してプロジェクトと関連するディレクトリ構造を作成します。
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ファイルを編集します。(このファイルは、ライニンゲンがプロジェクトを作成したときに作成されました。)
ここでの魔法の多くは、名前空間宣言にあります。:gen-class
は、com.domain.tiny
と呼ばれる単一の静的メソッドで名前が付けられたクラスを作成するようにシステムに指示します。このbinomial
関数は、2つの整数引数を取り、doubleを返します。2つの類似した名前の関数binomial
、従来のClojure関数、および-binomial
Javaからアクセス可能なラッパーがあります。関数名のハイフンに注意してください-binomial
。デフォルトのプレフィックスはハイフンですが、必要に応じて他の名前に変更できます。この-main
関数は、2項関数を数回呼び出すだけで、正しい結果が得られることを保証します。そのためには、クラスをコンパイルしてプログラムを実行します。
C:\projects\com.domain.tiny>lein run
元の回答に示されている出力が表示されます。
次に、それをjarファイルにパッケージ化し、便利な場所に置きます。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.
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
次に、ClojureプログラムとClojure jarを含む1つの大きな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")
Clojure.read("'(1 2 3")
です。Clojure.quote()を提供するか、varとして機能させることにより、これを拡張リクエストとして提出するのが妥当です。
'(1 2 3)
ようなものを書きますClojure.var("clojure.core", "list").invoke(1,2,3)
。そして答えはすでに使用方法の例を持っていますrequire
:それは他のように単なるvarです。
編集この回答は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);
}
}
RT.var("namespace", "my-var").deref()
ます。
RT.load("clojure/core");
最初に追加しないと、私にはうまくいきません。奇妙な行動は何ですか?
編集:私はほぼ3年前にこの回答を書きました。Clojure 1.6には、JavaからClojureを呼び出すための適切なAPIがあります。最新情報については、Alex Millerの回答をご覧ください。
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コードのソースファイル(またはコンパイル済みファイル)がクラスパス上にある限り機能します。
clartaqの答えには同意しますが、初心者も使用できると思いました。
それで、私はこのブログ記事でそれらすべてをカバーしました。
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." ))
ライニンゲン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でビルドされたJARをJavaアプリケーションに含めることである場合、2つの世界の間のインターフェースに別個の名前空間があると便利です。
(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] ...)))
AOTコンパイルを使用して、clojureコードを表すクラスファイルを作成することもできます。これを行う方法の詳細については、Clojure APIドキュメントのコンパイル、genクラス、およびフレンドに関するドキュメントをお読みください。ただし、基本的には、メソッドの呼び出しごとにclojure関数を呼び出すクラスを作成します。
もう1つの方法は、新しいdefprotocolおよびdeftype機能を使用することです。これもAOTコンパイルが必要ですが、パフォーマンスが向上します。これを行う方法の詳細はまだわかりませんが、メーリングリストで質問をするとうまくいくでしょう。