Javaからclojureを呼び出す


165

「Javaからのclojureの呼び出し」に関するGoogleの上位ヒットのほとんどは古くclojure.lang.RT、ソースコードのコンパイルに使用することを推奨しています。Clojureプロジェクトからすでにjarを構築し、それをクラスパスに含めていると仮定して、JavaからClojureを呼び出す方法の明確な説明を手伝っていただけませんか?


8
毎回ソースをコンパイルすること自体が「時代遅れ」であることは知りません。それは設計上の決定です。ClojureコードをレガシーJava Netbeansプロジェクトに簡単に統合できるので、今それを行っています。Clojureをライブラリーとして追加し、Clojureソースファイルを追加し、呼び出しをセットアップし、複数のコンパイル/リンク手順なしでClojureを即座にサポートします。各アプリの起動に数分の1秒の遅延が発生します。
Brian Knoblauch、2011

Clojure 1.6.0の最新版を参照してください。
A. Webb 2014年

2
Clojure 1.8.0の最新版をご覧ください— Clojureにコンパイラー直接リンクが追加されました
TWR Cole

回答:


167

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

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

最初の魔法は:methodsgen-classステートメントでキーワードを使用することです。これは、Javaの静的メソッドのようなClojure関数にアクセスするために必要なようです。

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

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

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

  • Clojure 1.5.1
  • ライニンゲン2.1.3
  • JDK 1.7.0 Update 25

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関数、および-binomialJavaからアクセス可能なラッパーがあります。関数名のハイフンに注意してください-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はおそらく厄介なコンパイル引数とパッケージ化を処理します。


4
1. 「ns」マクロの例として、clojuredocs.orgに例を記載できますか?2.「:methods [#^ {:static true} [binomial [int int] double]]」の前の#^とは何ですか(私は初心者です)?
ベルン2010

5
@Belun、例としてそれを使用できることを確認してください-私はお世辞です。"#^ {:static true}"は、メタデータを関数に付加して、二項式が静的関数であることを示します。この場合、Java側では、静的関数であるmainから関数を呼び出しているため、これが必要です。二項式が静的でない場合、Java側でメイン関数をコンパイルすると、「非静的メソッドbinomial(int、int)を静的コンテキストから参照できない」というエラーメッセージが表示されます。Object Mentorサイトに追加の例があります。
clartaq

4
(コンパイル「com.domain.tinyは):JavaクラスにClojureのファイルをコンパイルするためにあなたがする必要があり、 -ここでは言及していないという重要な事があります
Domchi

ClojureソースをAOTでコンパイルする方法を教えてください。これは私が行き詰まっているところです。
マシューボストン

@MatthewBoston回答が書かれたときに、NetBeans IDEのプラグインであるEnclojureを使用しました。今、私はおそらくライニンゲンを使用するでしょうが、それを試したりテストしたりはしていません。
clartaq 2012年

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はより高次の関数に渡すことができます。たとえば、以下の例はに渡さplusreadます。

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としても存在しない特別な形式は2つだけあると思います。回避策の1つは、を介してそれにアクセスすること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です。
アマロイ2017

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))にアクセスする場合に少し拡張するには、これを使用しRT.var("namespace", "my-var").deref()ます。
Ivan Koblik、2014年

RT.load("clojure/core");最初に追加しないと、私にはうまくいきません。奇妙な行動は何ですか?
hsestupin 2012

これは機能し、私が使用してきたものと似ています。最新のテクニックかどうかはわかりません。Clojure 1.4または1.6を使用している可能性があります。
Yan King Yin

12

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


1
このアドバイスはClojure 1.6の時点で古くなっています-代わりにclojure.java.api.Clojureを使用してください。
Alex Miller

現時点では悪い答えではありませんが、Clojure 1.6の信頼できる答えとして、stackoverflow.com / a / 23555959/1756702を奨励するつもりでした。明日再試行します...
A. Webb

アレックスの答えは確かに賞金に値する!必要に応じて報奨金の送金をお手伝いできるかどうかお知らせください。
raek 2014年

@raek私はあなたが私の過剰カフェイン化された人差し指から少しボーナスを得たことを気にしません。Clojureタグの周りでまたお会いしましょう。
A. Webb

10

clartaqの答えには同意しますが、初心者も使用できると思いました。

  • 実際にこれを実行する方法に関する段階的な情報
  • Clojure 1.3およびレイニンゲンの最新バージョンの最新情報。
  • main関数も含む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." ))

ライニンゲン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を使用したのですか?プログラムがプラットフォームに依存しなくなったのではないでしょうか?
エドワード

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

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

0

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


-1

AOTコンパイルを使用して、clojureコードを表すクラスファイルを作成することもできます。これを行う方法の詳細については、Clojure APIドキュメントのコンパイル、genクラス、およびフレンドに関するドキュメントをお読みください。ただし、基本的には、メソッドの呼び出しごとにclojure関数を呼び出すクラスを作成します。

もう1つの方法は、新しいdefprotocolおよびdeftype機能を使用することです。これもAOTコンパイルが必要ですが、パフォーマンスが向上します。これを行う方法の詳細はまだわかりませんが、メーリングリストで質問をするとうまくいくでしょう。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.