2

I'm calling Clojure from Java and calling eval on a string passed in. The Java code will hold the objects, and the client code can specify strings of Clojure code to run on an object. I know how to call the Clojure code from Java, but how do I pass a variable in?

Here's what I have. First, a simple object to work on:

public class Helloer {
    public String getGreeting() { return "Hello"; }
}

Then some boilerplate code to call a Clojure method.

public static String call(Helloer helloer, String expression) throws Exception {
    RT.loadResourceScript("EvalObject.clj");
    final Var schrodEval = RT.var("eval-object", "eval-string");
    final String result = (String) schrodEval.invoke(expression, helloer);
    return result;
}

But then I get stuck on the Clojure code. The object is passed in fine, but how do I pass the value into the eval?

Here's what I've tried:

(ns eval-object)

(defn eval-string [string this]
  (eval (read-string string)))

(defn eval-string2 [string value]
  (def this)
  (binding [this value]
    (eval (read-string string))))

(defn eval-string3 [string value]
  (def this)
  (eval (list 'binding (vector 'this 5) (read-string string))))

These give:

java.lang.Exception: Unable to resolve symbol: this in this context (NO_SOURCE_FILE:0)

So then I tried constructing a binding clause that defines this:

(defn eval-string4 [string value]
  (def this)
  (eval (list 'do (list 'defonce 'this nil)
        (list 'binding (vector 'this value) (read-string string)))))

But now I get:

java.lang.RuntimeException: Can't embed object in code, maybe print-dup not defined: Helloer@638bd7f1 (NO_SOURCE_FILE:0)

Am I missing something? Is it possible to pass objects from Java, to Clojure, and into an eval?

2 Answers 2

3

It's not totally clear what you mean by "pass objects into an eval". Perhaps you could clarify the objective a bit more?

However it could be that you are looking for something like this:

(defn call-eval [helloer]
  (eval `(.getGreeting ~helloer)))

(call-eval some-helloer)
=> "Hello"

A couple of things to note:

  • When you do an eval, you need to quote the form
  • To pass in the "object" you need to unquote it (~) within this form so that the object is used directly rather than just as a symbol
  • You don't need eval for this since (.getGreeting helloer) works just as well (and is much more efficient since it doesn't need to compile the form. Usually, eval is only needed when you are dynamically generating new code at runtime or if you want to read in and accept arbitrary code as input (e.g. the REPL itself).
Sign up to request clarification or add additional context in comments.

1 Comment

I'm doing the second case for eval. I want to read in and accept arbitrary code. And I want that arbitrary code to be able to access the helloer object as this.
2

It turns out the error message was correct. I needed to define print-dup. I implemented a simple ID lookup for the Helloer class, and defined print-dup as:

(defmethod print-dup com.ziroby.Helloer [h stream]
  (.write stream "#=(com.ziroby.Helloer/getById ")
  (.write stream (str (.getId h)))
  (.write stream ")"))

Apparently, Clojure requires some way to print an object to be able to put it into an eval statement. This print-dup creates a Clojure statement to "create" the object by looking it up via Helloer.getById (which just looks up the object in a hashmap).

As @mikera says, you only need to jump thru these hoops if you're accepting arbitrary code from your caller or dynamically generating code.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.