Как вызвать Clojure Макросы из Java?

Есть ли все-таки вызов макросов Clojure из Java?

Вот что я пытаюсь сделать:

RT.var("clojure.core", "require").invoke(Symbol.create("clojure.contrib.prxml"));
Var prxml = RT.var("clojure.contrib.prxml", "prxml");
Var withOutStr = RT.var("clojure.core", "with-out-str");
String stringXML = (String) withOutStr.invoke((prxml.invoke("[:Name \"Bob\"]")));

prxml записывает в * out * по умолчанию, поэтому мне нужно обернуть его макросом с out-str, который возвращает строку.

Я получаю эту ошибку:

 [java] java.lang.IllegalArgumentException: Wrong number of args (1) passed to: core$with-out-str
 [java]     at clojure.lang.AFn.throwArity(AFn.java:437)
 [java]     at clojure.lang.RestFn.invoke(RestFn.java:412)
 [java]     at clojure.lang.Var.invoke(Var.java:365)
 [java]     at JavaClojure.xml.main(Unknown Source)

Ответ 1

Вам придется сворачивать свой собственный с помощью OutStr.

class YourClass {
    static final Var withBindings = RT.var("clojure.core", "with-bindings*");
    static final Var list = RT.var("clojure.core", "list*");
    static final Var out = RT.var("clojure.core", "*out*");
    static final Var prxml = RT.var("clojure.contrib.prxml", "prxml");

    static String withOutStr(IFn f, Object args...) {
        StringWriter wtr = new StringWriter();
        withBindings.applyTo(list.invoke(RT.map(out, wtr), f, args));
        return wtr.toString();
    }

    ...

    String stringXML = withOutStr(prxml, "[:Name \"Bob\"]");
}

Ответ 2

Основная проблема.

invoke (и его сестра, применить) используются для функций.

Макросы не являются функциями, поэтому их нельзя вызвать. Макросы должны быть скомпилированы. В нормальных Lisps они могут быть просто оценены или макрорасширены или что угодно. И в 10 м оглядеться, очевидно, что Clojure не имеет простой функции RT.eval(String script), чтобы сделать это легко.

Но это то, что нужно сделать. Вам необходимо скомпилировать и выполнить это.

Я увидел пакет который объединяет Clojure с Java JSR 223, а у ИТ есть eval (потому что у 223 есть eval). Но я не знаю, а) если пакет хорош или б) если это направление, в которое вы хотите пойти.

Ответ 3

Отказ от ответственности: я очень мало знаю о clojure (мой опыт был связан с другими функциональными языками и Java)

Однако мой инстинкт кишки говорит, что проблема вокруг prxml.invoke(). Мысль здесь заключается в том, что это утверждение слишком быстро оценивается и отправляет результат в withOutStr (вместо того, чтобы позволить с помощью OutStr его оценивать).

Глядя на источники только в Интернете... особенно RT, Var и AFn, а также clojure doc для with-out-str Я бы попробовал что-то по строкам:

String stringXML = (String) withOutStr.invoke(RT.list(prxml,"[:Name \"Bob\"]"));

Изменить: Также я подозреваю, что он может вызывать макросы clojure из java, иначе функция isMacro() на Var кажется довольно глупым...

Отредактируйте 2: Загрузите clojure и попробуйте... не работает, так что игнорируйте это сейчас.

Edit 3: with-out-str, по-видимому, требует 2 параметра, поэтому:

final Cons consXML = (Cons) withOutStr.invoke(prxml, RT.list("[:Name \"Bob\"]"));
final Object[] objs = RT.seqToArray(consXML);
System.out.println(Arrays.toString(objs));

имеет выход: [clojure.core/let, [s__4095__auto__ (new java.io.StringWriter)], (clojure.core/binding [clojure.core/*out* s__4095__auto__] (clojure.core/str s__4095__auto__))]

Интересно, будет ли это оценивать что-то полезное или нет (не уверен, что я прав в привязках, нужно выяснить, как оценить минусы с помощью Java.

Редактирование 4: Прошивка через компилятор и больше кода, похоже, у макросов есть 2 скрытых параметра. См. commit 17a8c90

Копирование метода в компиляторе у меня:

final ISeq form = RT.cons(withOutStr, RT.cons(prxml, RT.cons("[:Name \"Bob\"]", null)));
final Cons consXML = (Cons) withOutStr.applyTo(RT.cons(form, RT.cons(null, form.next())));
System.out.println(consXML.toString());
// Output: (clojure.core/let [s__4095__auto__ (new java.io.StringWriter)] (clojure.core/binding [clojure.core/*out* s__4095__auto__] #'clojure.contrib.prxml/prxml "[:Name \"Bob\"]" (clojure.core/str s__4095__auto__)))

Что кажется немного более перспективным, но по-прежнему требует оценки выражения let, которое, похоже, имеет особый случай в компиляторе.

Ответ 4

Если вы хотите выполнить код Clojure с С# или Java, используйте Clojure load-string. Это займет обычную строку и выполнит ее точно так же, как если бы вы ввели строку в REPL. Это относится к макросам.

Здесь короткая версия кода С#. Версия Java не будет далека от этого.

    private static readonly IFn LOAD_STRING = Clojure.var("clojure.core", "load-string");

    private void executeButton_Click(object sender, EventArgs e)
    {
        try
        {
            object result = LOAD_STRING.invoke(sourceTextBox.Text);
            if (null == result)
                resultTextBox.Text = "null";
            else
                resultTextBox.Text = result.ToString() + " (" + result.GetType().Name + ")";
        }
        catch (Exception ex)
        {
            resultTextBox.Text = ex.ToString();
        }
    }

Вы можете увидеть полную версию примера программы здесь. Он включает в себя множество примеров кода для демонстрации Clojure interop.