سؤال

معظم أفضل زيارات Google لـ "استدعاء Clojure من Java" قديمة وتوصي باستخدامها clojure.lang.RT لتجميع رمز المصدر. هل يمكنك المساعدة في شرح واضح لكيفية استدعاء Clojure من Java على افتراض أنك قمت بالفعل ببناء جرة من مشروع Clojure وأدرجته في ClassPath؟

هل كانت مفيدة؟

المحلول

تحديث: منذ نشر هذه الإجابة ، تغيرت بعض الأدوات المتاحة. بعد الإجابة الأصلية ، يوجد تحديث يتضمن معلومات حول كيفية إنشاء المثال باستخدام الأدوات الحالية.

ليس الأمر بسيطًا تمامًا مثل التجميع إلى جرة واستدعاء الأساليب الداخلية. يبدو أن هناك بعض الحيل لجعل كل شيء يعمل. إليك مثال على ملف 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...

وهنا برنامج Java يدعو إلى -binomial وظيفة في 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 بيان. يبدو أن هذا مطلوب للسماح لك بالوصول إلى وظيفة clojure شيء مثل الطرق الثابتة في Java.

الشيء الثاني هو إنشاء وظيفة غلاف يمكن أن يطلق عليها Java. لاحظ أن الإصدار الثاني من -binomial لديه اندفاعة أمامه.

وبالطبع يجب أن تكون جرة clojure نفسها على مسار الفصل. استخدم هذا المثال جرة clojure-1.1.0.

تحديث: تم إعادة اختبار هذه الإجابة باستخدام الأدوات التالية:

  • Clojure 1.5.1
  • لينينينين 2.1.3
  • JDK 1.7.0 تحديث 25

الجزء clojure

قم أولاً بإنشاء مشروع وهيكل الدليل المرتبط باستخدام Lininingen:

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 في هذه المرحلة.

الآن قم بتحرير ملف clojure C:\projects\com.domain.tiny\src\com\domain\tiny.clj بحيث يحتوي على برنامج clojure الموضح في الإجابة الأصلية. (تم إنشاء هذا الملف عندما أنشأ Lininingen المشروع.)

الكثير من السحر هنا في إعلان مساحة الاسم. ال :gen-class يخبر النظام بإنشاء فصل مسمى com.domain.tiny مع طريقة ثابتة واحدة تسمى binomial, ، وظيفة تأخذ وسيطتين صحيحتين وإعادة مزدوجة. هناك وظيفتان مسماة بالمثل binomial, ، وظيفة clojure التقليدية ، و -binomial والركب يمكن الوصول إليه من جافا. لاحظ الواصلة في اسم الوظيفة -binomial. البادئة الافتراضية هي الواصلة ، ولكن يمكن تغييرها إلى شيء آخر إذا رغبت في ذلك. ال -main تقوم الوظيفة فقط ببعض المكالمات إلى وظيفة ذات الحدين للتأكد من أننا نحصل على النتائج الصحيحة. للقيام بذلك ، قم بتجميع الفصل وتشغيل البرنامج.

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

يجب أن ترى الإخراج يظهر في الإجابة الأصلية.

الآن قم بتعبئتها في جرة ووضعها في مكان مناسب. انسخ جرة clojure هناك أيضا.

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.

جزء جافا

لدى لينينينغن مهمة مدمجة ، 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

الآن قم بإنشاء ملف مع بعض المعلومات الوصفية لإضافته إلى الجرة التي نريد إنشاءها. في Manifest.txt, ، أضف النص التالي

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

الآن قم بتعبئة كل شيء في ملف JAR كبير واحد ، بما في ذلك برنامج Clojure و Clojure 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.

كما ذكرنا ، من المحتمل أن تعتني 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 يتم تحميلها تلقائيا. يمكن تحميل مساحات الأسماء الأخرى عن طريق الطلب:

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

معظم IFnS في 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") 

تعديل تمت كتابة هذه الإجابة في عام 2010 ، وعملت في ذلك الوقت. انظر إجابة أليكس ميلر للحصول على حل أكثر حداثة.

ما نوع الكود الذي يتصل به من جافا؟ إذا كان لديك فئة تم إنشاؤها مع Gen-Class ، فما عليك سوى تسميتها. إذا كنت ترغب في الاتصال بالوظيفة من البرنامج النصي ، فابحث عن مثال التالي.

إذا كنت ترغب في تقييم التعليمات البرمجية من String ، داخل 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);
  }
}

تعديل: كتبت هذه الإجابة منذ ما يقرب من ثلاث سنوات. في Clojure 1.6 ، هناك واجهة برمجة تطبيقات مناسبة تمامًا لغرض استدعاء Clojure من Java. رجاء إجابة أليكس ميلر لتحديث المعلومات.

الإجابة الأصلية من 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);

إنه أكثر مطوّلة في جافا ، لكن آمل أن يكون من الواضح أن أجزاء الكود مكافئة.

يجب أن يعمل هذا طالما أن clojure والملفات المصدر (أو الملفات المترجمة) من رمز clojure الخاص بك موجود على classpath.

وأنا أتفق مع إجابة Clartaq ، لكنني شعرت أن المبتدئين يمكنهم أيضًا استخدام:

  • معلومات خطوة بخطوة حول كيفية تشغيل هذا بالفعل
  • المعلومات الحالية لـ Clojure 1.3 والإصدارات الحديثة من Lininingen.
  • جرة clojure تتضمن أيضًا وظيفة رئيسية ، بحيث يمكن تشغيلها مستقلة أو مرتبطة كمكتبة.

لذلك غطيت كل ذلك في منشور المدونة هذا.

يبدو رمز 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." ))

يبدو أن إعداد مشروع Lininingen 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);
    }
}

أو يمكنك أيضًا الحصول على جميع الكود من هذا المشروع على جيثب.

هذا يعمل مع 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)"));
    }
}

إذا كانت حالة الاستخدام يجب أن تضم جرة مصممة مع clojure في تطبيق Java ، فقد وجدت أن مساحة اسم منفصلة للواجهة بين العالمين لتكون مفيدة:

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

تقنية أخرى تعمل أيضًا مع لغات أخرى أعلى JVM هي إعلان واجهة للوظائف التي تريد الاتصال بها ثم استخدام وظيفة "الوكيل" لإنشاء مثيل ينطوي عليها.

يمكنك أيضًا استخدام تجميع AOT لإنشاء ملفات فئة تمثل رمز clojure الخاص بك. اقرأ الوثائق حول التجميع ، Gen-Class والأصدقاء في مستندات API Clojure للحصول على تفاصيل حول كيفية القيام بذلك ، ولكن في جوهرها ستنشئ فئة تستدعي وظائف Clojure لكل طريقة استدعاء.

بديل آخر هو استخدام وظائف DepProtocol و Deftype الجديدة ، والتي ستتطلب أيضًا تجميع AOT ولكنها توفر أداء أفضل. لا أعرف تفاصيل كيفية القيام بذلك حتى الآن ، ولكن من المحتمل أن يقوم سؤال في القائمة البريدية بالخدعة.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top