Java'dan clojure çağrısı


165

"Java clojure çağırmak" için en iyi google hit çoğu eskimiş ve clojure.lang.RTkaynak kodunu derlemek için kullanmanızı öneririz . Clojure projesinden bir kavanoz oluşturduğunuzu ve sınıf yoluna eklediğinizi varsayarak, Clojure'u Java'dan nasıl arayacağınıza ilişkin açık bir açıklamada yardımcı olabilir misiniz?


8
Her zaman kaynak derleme gibi "modası geçmiş" olduğunu bilmiyorum. Bu bir tasarım kararı. Şimdi Clojure kodunu eski bir Java Netbeans projesine entegre etmeyi yaptığı için yapıyorum. Birden fazla derleme / bağlama adımı olmadan Clojure'u kitaplık olarak ekleyin, Clojure kaynak dosyası ekleyin, çağrıları ayarlayın ve anında Clojure desteği! Her uygulama başlangıcında ikinci bir gecikmenin bir kısmı pahasına.
Brian Knoblauch


2
Clojure 1.8.0 için son haberlere bakın - Clojure artık derleyici doğrudan bağlantısına sahiptir.
TWR Cole

Yanıtlar:


167

Güncelleme : Bu yanıt gönderildiği için mevcut bazı araçlar değişti. Orijinal cevaptan sonra, mevcut araçlarla örneğin nasıl oluşturulacağına dair bilgiler içeren bir güncelleme var.

Bir kavanoza derlemek ve iç yöntemleri çağırmak kadar basit değil. Her şeyin işe yaraması için birkaç püf noktası var gibi görünüyor. Bir kavanoza derlenebilecek basit bir Clojure dosyası örneği:

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

Eğer çalıştırırsanız, şöyle bir şey görmelisiniz:

(binomial 5 3): 10
(binomial 10042 111): 49068389575068144946633777...

Ve burada -binomialişlevi çağıran bir Java programı 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));
    }
}

Çıkışı:

(binomial 5 3): 10.0
(binomial 10042, 111): 4.9068389575068143E263

İlk sihir parçası, ifadedeki :methodsanahtar kelimeyi kullanmaktır gen-class. Clojure işlevine Java'daki statik yöntemler gibi bir şeye erişmenizi sağlamak için gerekli gibi görünüyor.

İkinci şey, Java tarafından çağrılabilen bir sarma işlevi oluşturmaktır. İkinci sürümünün -binomialönünde bir tire olduğuna dikkat edin .

Ve elbette Clojure kavanozunun kendisi sınıf yolunda olmalıdır. Bu örnekte Clojure-1.1.0 kavanozu kullanılmıştır.

Güncelleme : Bu yanıt aşağıdaki araçlar kullanılarak yeniden test edilmiştir:

  • Clojure 1.5.1
  • Leiningen 2.1.3
  • JDK 1.7.0 Güncelleme 25

Clojure Bölümü

İlk olarak Leiningen'i kullanarak bir proje ve ilişkili dizin yapısı oluşturun:

C:\projects>lein new com.domain.tiny

Şimdi proje dizinine geçin.

C:\projects>cd com.domain.tiny

Proje dizininde project.cljdosyayı açın ve içeriği aşağıda gösterildiği şekilde düzenleyin.

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

Şimdi, tüm bağımlılıkların (Clojure) kullanılabilir olduğundan emin olun.

C:\projects\com.domain.tiny>lein deps

Bu noktada Clojure kavanozunu indirmeyle ilgili bir mesaj görebilirsiniz.

Şimdi Clojure dosyasını C:\projects\com.domain.tiny\src\com\domain\tiny.cljorijinal yanıtta gösterilen Clojure programını içerecek şekilde düzenleyin . (Bu dosya Leiningen projeyi yarattığında oluşturulmuştur.)

Buradaki sihrin çoğu isim-alanı beyanındadır. :gen-classAdında bir sınıf oluşturmasını söyler com.domain.tinydenilen tek bir statik yöntemi ile binomial, bir işlevi, iki tamsayı argüman alarak ve bir çift dönen. Benzer şekilde adlandırılmış iki işlev vardır: binomialgeleneksel Clojure işlevi ve -binomialJava'dan erişilebilen sarıcı. İşlev adındaki tireye dikkat edin -binomial. Varsayılan önek kısa çizgidir, ancak istenirse başka bir şeyle değiştirilebilir. -mainFonksiyon sadece binom işlevine çağrı birkaç biz doğru sonuçlar elde edildiğinden emin olmak için yapar. Bunu yapmak için sınıfı derleyin ve programı çalıştırın.

C:\projects\com.domain.tiny>lein run

Orijinal yanıtta gösterilen çıktıyı görmelisiniz.

Şimdi bir kavanozda paketleyin ve uygun bir yere koyun. Clojure kavanozunu oraya da kopyalayın.

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 Bölümü

Leiningen, lein-javacJava derlemesine yardımcı olması gereken yerleşik bir göreve sahiptir . Ne yazık ki, 2.1.3 sürümünde bozuk görünüyor. Kurulu JDK'yı bulamaz ve Maven deposunu bulamaz. Her ikisinin de yolları sistemime katıştırılmış boşluklara sahip. Sorunun bu olduğunu düşünüyorum. Herhangi bir Java IDE derleme ve paketlemeyi de halledebilir. Ama bu yazı için eski okula gidiyoruz ve bunu komut satırında yapıyoruz.

Önce dosyayı Main.javaorijinal yanıtta gösterilen içerikle oluşturun .

Java bölümünü derlemek için

javac -g -cp target\com.domain.tiny-0.1.0-SNAPSHOT.jar -d target\src\com\domain\Main.java

Şimdi oluşturmak istediğimiz kavanoza eklemek için bazı meta bilgileri içeren bir dosya oluşturun. Alanına Manifest.txtaşağıdaki metni ekleyin

Class-Path: lib\com.domain.tiny-0.1.0-SNAPSHOT.jar lib\clojure-1.5.1.jar
Main-Class: Main

Şimdi hepsini Clojure programımız ve Clojure kavanoz dahil olmak üzere büyük bir kavanoz dosyasına paketleyin.

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

Programı çalıştırmak için:

C:\projects\com.domain.tiny\target>java -jar Interop.jar
(binomial 5 3): 10.0
(binomial 10042, 111): 4.9068389575068143E263

Çıktı, temel olarak yalnızca Clojure tarafından üretilenle aynıdır, ancak sonuç bir Java çiftine dönüştürülmüştür.

Belirtildiği gibi, bir Java IDE muhtemelen dağınık derleme argümanları ve ambalajı ile ilgilenecektir.


4
1. "ns" makrosu için örnek olarak clojuredocs.org'a örnek verebilir miyim ? 2. ": yöntemleri [# ^ {: statik true} [binom [int int] çift]]" önünde # ^ nedir (ben bir acemi miyim)?
Belun

5
@Belun, örnek olarak kullanabileceğinize eminim - gurur duyuyorum. "# ^ {: Static true}", binomun statik bir işlev olduğunu gösteren işleve bazı meta veriler ekler. Bu durumda gereklidir, çünkü Java tarafında işlevi main'den - statik bir işlev olarak çağırıyoruz. Binom statik değilse, Java tarafında ana işlevi derlemek "statik olmayan yöntem binomial (int, int) statik bir bağlamdan başvurulamaz" hakkında bir hata mesajı üretir. Object Mentor sitesinde ek örnekler var.
clartaq

4
Burada belirtilmeyen önemli bir şey var - Clojure dosyasını Java sınıfına derlemek için şunları yapmanız gerekir: ('com.etkialanı.tiny'yi derleyin)
Domchi

Clooture kaynağını nasıl derliyorsunuz? Burada sıkışıp kaldım.
Matthew Boston

@MatthewBoston Cevabın yazıldığı sırada NetBeans IDE için bir eklenti olan Enclojure kullandım. Şimdi, muhtemelen Leiningen kullanacaktım, ama denemedim veya test etmedim.
clartaq

119

Clojure 1.6.0'dan itibaren Clojure işlevlerini yüklemek ve çağırmak için tercih edilen yeni bir yol vardır. Bu yöntem artık RT'yi doğrudan çağırmak için tercih edilmektedir (ve burada diğer cevapların çoğunun yerine geçmektedir). Javadoc burada - ana giriş noktası clojure.java.api.Clojure.

Clojure işlevini aramak ve çağırmak için:

IFn plus = Clojure.var("clojure.core", "+");
plus.invoke(1, 2);

İçindeki işlevler clojure.coreotomatik olarak yüklenir. Diğer ad alanları aşağıdakiler aracılığıyla yüklenebilir:

IFn require = Clojure.var("clojure.core", "require");
require.invoke(Clojure.read("clojure.set"));

IFns geçiş aşağıdaki örnek, örneğin, daha yüksek dereceli işlevlerine geçirilebilir plusiçin read:

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

IFnClojure'daki çoğu işlevle ilgilidir. Bununla birlikte, birkaçı işlevsiz veri değerlerine atıfta bulunur. Bu erişmek için kullanmak derefyerine fn:

IFn printLength = Clojure.var("clojure.core", "*print-length*");
IFn deref = Clojure.var("clojure.core", "deref");
deref.invoke(printLength);

Bazen (Clojure çalışma zamanının başka bir bölümünü kullanıyorsanız), Clojure çalışma zamanının düzgün bir şekilde başlatılmasını sağlamanız gerekebilir - Clojure sınıfında bir yöntem çağırmak bu amaç için yeterlidir. Clojure'da bir yöntemi çağırmanız gerekmiyorsa, sadece sınıfın yüklenmesine neden olmak yeterlidir (geçmişte RT sınıfını yüklemek için benzer bir öneri vardı; bu şimdi tercih edilmektedir):

Class.forName("clojure.java.api.Clojure") 

1
Bu yaklaşımı kullanarak komut dosyası alıntı '() kullanamaz ve bir şey gerektirmez. Bunun için bir çözüm var mı?
Renato

2
Bence vars olarak var olmayan sadece birkaç özel form var. Geçici çözümlerden biri, ona erişmek Clojure.read("'(1 2 3"). Clojure.quote () sağlamak veya bir var olarak çalıştırarak bunu bir geliştirme isteği olarak dosyalamak makul olacaktır.
Alex Miller

1
@Renato Hiçbir şey teklif etmeye gerek yok, çünkü hiçbir şey Clojure'un değerlendirme kuralları tarafından hiçbir şekilde değerlendirilmiyor. 1-3 sayılarını içeren bir liste '(1 2 3)istiyorsanız, yazmak yerine böyle bir şey yazıyorsunuz Clojure.var("clojure.core", "list").invoke(1,2,3). Ve cevabın zaten nasıl kullanılacağına dair bir örneği requirevar: sadece diğerleri gibi.
amalloy

34

DÜZENLE Bu cevap 2010 yılında yazılmıştır ve o zaman çalıştı. Daha modern bir çözüm için Alex Miller'ın cevabına bakınız.

Java'dan ne tür kodlar çağırıyorsunuz? Eğer gen sınıfı ile oluşturulmuş bir sınıfınız varsa, sadece çağırın. Fonksiyonu komut dosyasından çağırmak istiyorsanız, aşağıdaki örneğe bakın .

Java içindeki dizeden kodu değerlendirmek istiyorsanız, aşağıdaki kodu kullanabilirsiniz:

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
İsim alanındaki def'd var'a (yani (def my-var 10)) erişmek istiyorsanız biraz genişletmek için bunu kullanın RT.var("namespace", "my-var").deref().
Ivan Koblik

Başlangıçta eklemeden benim için çalışmıyor RT.load("clojure/core");. Ne garip davranış?
hsestupin

Bu çalışıyor ve kullandığım şeye benziyor; en yeni teknik olup olmadığından emin değilim. Emin değilim Clojure 1.4 veya 1.6 kullanıyor olabilirim.
Yan King Yin

12

EDIT: Bu yanıtı neredeyse üç yıl önce yazdım. Clojure 1.6'da Java'dan Clojure'u çağırmak için tam olarak uygun bir API vardır. Güncel bilgiler için lütfen Alex Miller'ın cevabı .

2011'den orijinal cevap:

Gördüğüm gibi, en basit yol (AOT derlemesiyle bir sınıf oluşturmazsanız) clojure'daki işlevlere erişmek için clojure.lang.RT'yi kullanmaktır. Bununla birlikte, Clojure'da ne yapacağınızı taklit edebilirsiniz (bazı şeyleri özel şekillerde derlemeye gerek yoktur):

;; Example usage of the "bar-fn" function from the "foo.ns" namespace from Clojure
(require 'foo.ns)
(foo.ns/bar-fn 1 2 3)

Ve Java'da:

// 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'da biraz daha ayrıntılı, ancak umarım kod parçalarının eşdeğer olduğu açıktır.

Bu, Clojure ve Clojure kodunuzun kaynak dosyaları (veya derlenmiş dosyaları) sınıf yolunda olduğu sürece çalışmalıdır.


1
Bu tavsiye Clojure 1.6'dan itibaren güncel değildir - lütfen bunun yerine clojure.java.api.Clojure kullanın.
Alex Miller

O zaman kötü bir cevap değil, ama Clojure 1.6 için yetkili cevap olarak stackoverflow.com/a/23555959/1756702 ödülünü kastetmiştim . Yarın tekrar deneyecek ...
A. Webb

Alex'in cevabı gerçekten de lütfu hak ediyor! Gerekirse ödülün aktarılmasına yardımcı olup olamayacağımı lütfen bize bildirin.
raek

@raek Aşırı kafeinli tetik parmağımdan küçük bir bonus aldığını umursamıyorum. Seni tekrar Clojure etiketi etrafında görmeyi umuyorum.
A. Webb

10

Clartaq'ın cevabına katılıyorum, ancak yeni başlayanların da kullanabileceğini hissettim:

  • bunun nasıl çalıştırılacağına dair adım adım bilgiler
  • Clojure 1.3 ve leiningen'in son sürümleri için geçerli olan bilgiler.
  • ana işlevi de içeren bir Clojure kavanozu, bağımsız olarak çalıştırılabilir veya kitaplık olarak bağlanabilir.

Bu blog yazısında bunların hepsini ele aldım .

Clojure kodu şöyle görünür:

(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 proje kurulumu şöyle görünür:

(defproject ThingOne "1.0.0-SNAPSHOT"
  :description "Hello, Clojure"
  :dependencies [[org.clojure/clojure "1.3.0"]]
  :aot [ThingOne.core]
  :main ThingOne.core)

Java kodu şuna benzer:

import ThingOne.*;

class HelloJava {
    public static void main(String[] args) {
        System.out.println("Hello from Java!");
        core.foo (12345);
    }
}

Ya da github üzerinde bu projeden tüm kodu alabilirsiniz .


Neden AOT'yi kullandınız? Programın artık platformdan bağımsız olmamasına neden değil mi?
Edward

3

Bu Clojure 1.5.0 ile çalışır:

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

Kullanım durumu, bir Java uygulamasında Clojure ile oluşturulmuş bir JAR'ı içeriyorsa, iki dünya arasındaki arayüzün faydalı olması için ayrı bir ad alanına sahip olduğunu gördüm:

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

Çekirdek ad alanı, görevlerini gerçekleştirmek için enjekte edilen örneği kullanabilir:

(ns example-app.core)

(defn send-mails 
  [{:keys [weather-forecast]}]
  (let [temp (.getTemperature weather-forecast)] ...)) 

Test amacıyla, arabirim saplanabilir:

(example-app.core/send-mails 
  (reify com.example.WeatherForecast (getTemperature [this] ...)))

0

JVM'nin üstündeki diğer dillerle de çalışan diğer teknik, aramak istediğiniz işlevler için bir arabirim bildirmek ve daha sonra bunları uygulayan örnek oluşturmak için 'proxy' işlevini kullanmaktır.


-1

Clojure kodunuzu temsil eden sınıf dosyaları oluşturmak için AOT derlemesini de kullanabilirsiniz. Bunun nasıl yapılacağıyla ilgili ayrıntılar için Clojure API belgelerinde derleme, gen sınıfı ve arkadaşlar hakkındaki belgeleri okuyun, ancak aslında her yöntem çağırma için clojure işlevlerini çağıran bir sınıf oluşturacaksınız.

Başka bir alternatif de, AOT derlemesi gerektiren ancak daha iyi performans sağlayan yeni defprotokol ve ustaype işlevselliğini kullanmaktır. Henüz bunun nasıl yapılacağının ayrıntılarını bilmiyorum, ancak posta listesindeki bir soru muhtemelen işe yarayacaktır.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.