Как вызвать 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.