Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java -> Clojure -> Java

Tags:

java

clojure

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.

like image 652
Marcus Junius Brutus Avatar asked Feb 20 '13 19:02

Marcus Junius Brutus


People also ask

Is clojure better than Java?

Clojure and Java can be categorized as "Languages" tools. "It is a lisp", "Concise syntax" and "Persistent data structures" are the key factors why developers consider Clojure; whereas "Great libraries", "Widely used" and "Excellent tooling" are the primary reasons why Java is favored.

Is clojure interoperable with Java?

Overview. Clojure was designed to be a hosted language that directly interoperates with its host platform (JVM, CLR and so on). Clojure code is compiled to JVM bytecode. For method calls on Java objects, Clojure compiler will try to emit the same bytecode javac would produce.

Is clojure easier than Java?

For these Clojure defenders, this programming language is better than Java for many reasons. For example, they state that with Clojure, they can write better and more flexible programs.

Does clojure need Java?

Clojure supports the creation, reading and modification of Java arrays. It is recommended that you limit use of arrays to interop with Java libraries that require them as arguments or use them as return values. Note that many other Clojure functions work with arrays such as via the seq library.


1 Answers

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

}
like image 95
Ankur Avatar answered Sep 26 '22 00:09

Ankur