简体   繁体   中英

How to call Clojure Macros from Java?

Is there anyway to call Clojure macros from Java?

Here is what I am trying to do:

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 writes to *out* by default which is why I need to wrap it with the macro with-out-str which returns the string.

I am getting this error:

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

You'll have to roll your own withOutStr.

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

The problem is basic.

invoke (and its sister, apply) are used for functions.

Macros are not functions, so they can not be invoked. Macros need to be compiled. In normal Lisps, they could just be eval'd or macroexpanded or whatever. And in 10m of looking around, apparently Clojure doesn't have a simple RT.eval(String script) function to make this easy.

But, that's what needs to be done. You need to compile and execute this.

I saw a package that integrates Clojure with Java JSR 223, and IT has an eval (because 223 has an eval). But I don't know a) if the package is any good or b) if this is the direction you want to go.

Disclaimer: I know very little about clojure (my experience has been with other functional languages and Java)

My gut instinct however says the problem is around prxml.invoke() . The thought here being that that statement evaluates too soon, and sends the result to withOutStr (instead of letting withOutStr evaluate it).

Looking at the sources online alone... notably RT , Var & AFn as well as the clojure doc for with-out-str I would try something along the lines of:

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

Edit: Also I would suspect that it is able to call clojure macros from java otherwise the isMacro() function on Var seems rather silly...

Edit 2: Downloaded clojure, and tried it... doesn't work so ignore this for now.

Edit 3: with-out-str apparently requires 2 parameters so:

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

has an output of: [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__))]

I wonder if that will evaluate to something useful, or not (not sure if I'm correct on the bindings, have to figure out how to evaluate the cons through Java.

Edit 4: Poking through the Compiler and more code, it seems macros actually have 2 hidden parameters. See the commit 17a8c90

Copying the method in the compiler I have:

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

Which seems a bit more promising, but it still requires the evaluation of the let expression which seems to have a special case in the compiler.

If you want to execute Clojure code from C# or Java, use Clojure's load-string function. This will take an ordinary string and execute it exactly as if you'd typed the string in the REPL. That includes dealing with macros.

Here's a short version of the C# code. The Java version won't be far from that.

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

You can see the full version of the sample program here . It includes a lot of sample code to demonstrate Clojure interop.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM