Java -> Clojure -> Java

7
我正在尝试将Clojure作为主机Java程序的脚本语言。这样,最终用户就可以编写Clojure脚本代码来调用特定领域的Java API。在运行时,主机Java程序将评估最终用户的Clojure脚本(这将依次调用领域API)。因此,我从一个非常简单的原型开始,以探索该领域。

领域

package a.problem.domain;

public class Domain {

    public Domain() { }

    public String defaultMsg() {
        return "default";
    }

    public String passBackMsg(String s) {
        return s;
    }
}

托管Java程序

(为简单起见,最终用户的Clojure脚本是硬编码的)

String script = "(do                                    "+
                "  (import '(a.problem.domain Domain))  "+
                "  (.defaultMsg (Domain.))              "+
                ")                                      ";
System.out.println(RT.var("clojure.core", "eval").invoke(RT.var("clojure.core","read-string").invoke(script)));

(代码片段取自这里)

到目前为止还不错。

然而,我找不到一种调用第二个需要参数的方法的方式。相反,我在运行时动态生成Clojure脚本,并将占位符替换为表示调用域方法passBackMsg结果的文字。显然,这是不令人满意的,并且无法很好地解决问题(如果我想将java.sql.Connection传递给我的Clojure脚本怎么办?)。

那么,如何从主机Java程序中调用passBackMsg方法呢?

当我尝试以下操作时:

String script = "(ns foo)                                   "+ 
                "(import '(a.problem.domain Domain))        "+
                "(defn numberToString [s]  (                "+
                "  (.passBackMsg (Domain.) s)               "+
                "))                                         ";
RT.var("clojure.core", "eval").invoke(RT.var("clojure.core","read-string").invoke(script)); // line-A
System.out.println(RT.var("foo", "numberToString").invoke(33)); // line-B

我获得:

java.lang.IllegalStateException: Can't change/establish root binding of: *ns* with set

当我尝试使用ns和不使用ns时,在line-A发生了错误。

RT.var("user", "numberToString").invoke(33)

(“user”只是一个猜测,因为我没有看到没有命名空间参数的var方法)
我遇到了一个:
java.lang.IllegalStateException: Attempting to call unbound fn: #'user/numberToString"

...在第B行。


1
你能否通过一个script字符串来定义一个函数,并通过解引用其变量来调用它,就像调用read-string一样,而不是使用defn - Marko Topolnik
我尝试过了,但是无法使其工作。首先它会接受“defn”,而必须使用“(let (fn ))”代替。这不是一个阻塞问题,但很奇怪。然后我遇到了其他一些问题。我不想复制失败的代码,以免帖子太长,但在收到您的消息后,我可能会添加它。 - Marcus Junius Brutus
我在我的一个项目中已经实现了这个功能:clojure.lang.RT.load(filename); clojure.lang.RT.var(mainNamespace, "main").invoke( stringArg ); 从文件加载不应该有根本性的区别。 - Marko Topolnik
更新关于defn的内容: 原来使用defn并不是问题,因此我已经相应地更新了演示的脚本,但是"Can't change/establish root binding of: ns with set"仍然存在。 - Marcus Junius Brutus
你肯定需要在每个东西周围加上 do,因为 read-string 只读取一个表单。此外,read-string 不会评估它,它只返回所读取的内容。这与我使用 load 的技术形成对比。 - Marko Topolnik
1个回答

4

试试这个:

String script = "(do                                    "+
                "  (import '(a.problem.domain Domain))  "+
                "  (fn [s]                " +
                "   (.passBackMsg (Domain.) s)               "+
                "))                                         ";

IFn fn = (IFn)RT.var("clojure.core", "eval").invoke(RT.var("clojure.core","read-string").invoke(script));

fn.invoke("hello");

更新: 以下示例代码运行良好:

package hello_clj;

import clojure.lang.RT;
import clojure.lang.IFn;

public class Main {

    public String passBackMsg(String s) {
        return s;
    }

    public static void main(String[] args) {
        String script = "(do (import 'hello_clj.Main) (fn [s] " + 
                        "(.passBackMsg (Main.) s) ))";

        IFn fn = (IFn)RT.var("clojure.core", "eval").invoke(RT.var("clojure.core","read-string").invoke(script));
        System.out.print(fn.invoke("Hello"));
    }

}

最好不要使用import,因为它可能在同一表单中被评估时不会生效,这样做更加可靠。你试过了吗? - Marko Topolnik
java.lang.ClassCastException: java.lang.String 无法转换为 clojure.lang.IFn(在行-A抛出) - Marcus Junius Brutus
更新了答案并添加了一个运行示例代码。旧代码在fn周围有额外的参数,这就是问题所在。 - Ankur

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接