web-dev-qa-db-ja.com

javaからclojureを呼び出す

「Javaからclojureを呼び出す」というGoogleのヒットのほとんどは時代遅れであり、clojure.lang.RTソースコードをコンパイルします。 JavaからClojureを呼び出す方法の明確な説明を手伝ってもらえますか?

160
Arthur Ulfeldt

更新:この回答が投稿されてから、利用可能なツールの一部が変更されました。元の回答の後、現在のツールを使用してサンプルをビルドする方法に関する情報を含む更新があります。

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

そして、これは-binomialtiny.jar関数を呼び出すJavaプログラムです。

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から呼び出すことができるラッパー関数を作成することです。 -binomialの2番目のバージョンの前にダッシュがあることに注意してください。

そしてもちろん、Clojure jar自体はクラスパス上になければなりません。この例では、Clojure-1.1.0 jarを使用しました。

更新:この回答は、次のツールを使用して再テストされました。

  • Clojure 1.5.1
  • ライニンゲン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

この時点で、Clojure jarのダウンロードに関するメッセージが表示される場合があります。

元の回答に示されているClojureプログラムが含まれるように、ClojureファイルC:\projects\com.domain.tiny\src\com\domain\tiny.cljを編集します。 (このファイルは、Leiningenがプロジェクトを作成したときに作成されました。)

ここでの魔法の多くは、名前空間宣言にあります。 :gen-classは、2つの整数引数を取り、doubleを返す関数であるbinomialという単一の静的メソッドを使用して、com.domain.tinyという名前のクラスを作成するようシステムに指示します。従来のClojure関数であるbinomialと、Javaからアクセス可能な-binomialおよびラッパーの2つの類似した名前の関数があります。関数名-binomialのハイフンに注意してください。デフォルトのプレフィックスはハイフンですが、必要に応じて別のプレフィックスに変更できます。 -main関数は、2項関数を2、3回呼び出すだけで、正しい結果が得られるようにします。それを行うには、クラスをコンパイルしてプログラムを実行します。

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には、Javaコンパイルを支援できる組み込みタスクlein-javacがあります。残念ながら、バージョン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はおそらく、乱雑なコンパイル引数とパッケージングを処理します。

156
clartaq

Clojure 1.6.0の時点で、Clojure関数をロードして呼び出す新しい推奨される方法があります。このメソッドは、RTを直接呼び出すよりも優先されます(ここで他の多くの回答に取って代わります)。javadocは here です-メインエントリポイントは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は高次関数に渡すことができます。以下の例では、plusreadに渡します。

IFn map = Clojure.var("clojure.core", "map");
IFn inc = Clojure.var("clojure.core", "inc");
map.invoke(inc, Clojure.read("[1 2 3]"));

ClojureのほとんどのIFnsは関数を参照します。ただし、いくつかは非関数データ値を参照します。これらにアクセスするには、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") 
116
Alex Miller

[〜#〜] edit [〜#〜]この回答は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);
  }
}
34
Alex Ott

EDIT:ほぼ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コードのソースファイル(またはコンパイル済みファイル)がクラスパスにある限り機能します。

12
raek

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のこのプロジェクト からすべてのコードを取得することもできます。

10
sandover

これは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)"));
    }
}
3
KIM Taegyoon

ユースケースが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] ...)))
1
jstaffans

JVM上で他の言語でも機能する他の手法は、呼び出したい関数のインターフェースを宣言し、「プロキシ」関数を使用してそれらを実装するインスタンスを作成することです。

0
Petr Gladkikh