I am trying to use Clojure as a scripting language from a host Java program. The idea being that the end user will be able to write Clojure scripting code that will call a domain-specific Java API. At runtime, the host Java program will evaluate the end-user's Clojure script (which will in turn call the domain APIs). So I started with a dead-simple prototype to explore the terrain.
domain
package a.problem.domain;
public class Domain {
public Domain() { }
public String defaultMsg() {
return "default";
}
public String passBackMsg(String s) {
return s;
}
}
Host Java program
(end user's Clojure script hard-coded for simplicity)
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)));
(code snippet taken from here)
So far so good.
However I couldn't find a way to invoke the second method (the one that requires an argument). Instead I resorted to dynamically generating the Clojure script at runtime and replacing a placeholder with a literal that represents the result of calling the domain method passBackMsg. Obviously, this is unsatisfactory and doesn't go very far (what if I want to pass a java.sql.Connection to my Clojure script ?).
So, how do I invoke the passBackMsg method from the host Java program?
When I try the following:
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
I get:
java.lang.IllegalStateException: Can't change/establish root binding of: *ns* with set
... on line-A. When I try without the ns and with:
RT.var("user", "numberToString").invoke(33)
("user" being a wild guess as I don't see a var method without a namespace argument)
I get an:
java.lang.IllegalStateException: Attempting to call unbound fn: #'user/numberToString"
... on line-B.
1 Answer 1
Try this:
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");
UPDATED: Below sample code works fine:
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"));
}
}
3 Comments
import as it may not take effect within the same form as it is being evaluated. Did you try it?
defna function through ascriptstring and theninvokeit just like you invokedread-string, by derefing its var?clojure.lang.RT.load(filename); clojure.lang.RT.var(mainNamespace, "main").invoke( stringArg );Loading from a file shouldn't make a fundamental difference.doaround everything becauseread-stringonly reads one form. Also,read-stringdoesn't evaluate it, it just returns what was read in. This is in contrast to my technique withload.