Problems with eval in a custom REPL application


#1

Okay, here is the practical problem I am trying to solve, which is related to my previous post:

I am trying to create an application with a custom REPL environment and I am having problem getting the custom eval function to work properly.

Here is a contrived example that illustrates the problem as minimally as I could write it:

(ns test.core
  (:gen-class))

  (def print-hello? false)

  (defn print-hello
    []
    "hello!!!")

  (defn test-read
    []
    (if (true? print-hello?)
      '(print-hello)
      (read)))

  (defn test-eval
    []
    (eval (test-read)))

  (defn test-print
    []
    (println (test-eval)))

  (defn test-loop
    []
    (loop []
      (test-print)
      (recur)))

  (defn test-exception
    [e]
    (println (.getMessage e)))

  (defn -main
    []
    (try
      (test-loop)
      (catch Exception e (test-exception e))))

If I run this program as is, I get a REPL environment as expected.

If I change the fourth line to

(def print-hello? true)

I expect to get an infinite looping of the string “hello!!!”.

Instead, I get:

java.lang.RuntimeException: Unable to resolve symbol: print-hello in this context, compiling:(NO_SOURCE_PATH:13:8)

I suspect this is a namespace issue. I expect '(print-hello) to be interpreted in the test.core namespace but I think, based on the responses to my previous posting, that it is being interpreted in some namespace other than test.core.

Or maybe my approach to creating a Clojure REPL application is completely wrong.

Any help appreciated.


#2

You’re right, it’s a namespace issue. As far as I know, eval executes in the current *ns*, so it tries to lookup print-hello in user namespace.

Haven’t thought about quoting in context of evaling things, but when writing macros and code-manipulating functions you’ll probably need not a quote (', equiv. to quote), but a backtick (`, equiv. to syntax-quote) - it preserves namespace information:

test.core=> (macroexpand '(print-hello))
(print-hello)
test.core=> (macroexpand `(print-hello))
(test.core/print-hello)

#3

Okay, passing `(print-hello) to eval does work. Thanks for the tip…or, rather, the tick :).


#4

I think in your case, you want to bind ns to your namespace, if you’d like the REPL context to be test.core. you can even bind your main, if you want it to be so.

That’s what most REPL like clojure.main does.

First you bind ns to itself. Then you call in-ns and switch the namespace to whichever you want.

(binding [*ns* *ns*]
  (in-ns 'test.core)
  (test-loop))

If you don’t create a binding for ns, it can’t be switched using in-ns and ns, because they rely on set!, and you can only set! bound dynamic Vars.

The root binding for ns is clojure.core.


#5

Thanks for the reply.

Just to make sure I understand correctly, this approach will eliminate the need to send all of my clojure forms to test-eval with a backtick because everything I do in the application will already have the namespace I expect it to have, namely test.core? I can go back to sending '(print-hello)?

I will take a look a look at some of the REPL code as well, that’s a good idea. Obviously, people have already solved the REPL implementation problems that I am running into and the code is freely available.

Thanks.


#6

Yes exactly.

So when you eval code, it runs inside whatever namespace ns is set too.

That said, code you eval run within a fresh local scope. So you can access global public namespace qualified Vars, as well as the top level Vars private or public of the namespace ns is set too, but you can’t access local vars such as:

(let [x 1]
  (eval 'x))