3

I've got the following in a clojure file:

(ns helloworld
  (:gen-class
    :main -main))

(defn hello-world-fn []
  (println "Hello World"))

(defn -main [& args]
  (eval (read-string "(hello-world-fn)")))

and I'm running it with

lein run helloworld

and I'm getting the following error:

Exception in thread "main" java.lang.RuntimeException: Unable to resolve symbol:
 helloworld in this context, compiling:(helloworld.clj:12)

I have a feeling I need to do something with ns-resolve or resolve but I haven't had any success. I've tried the following in the main function:

(let [call-string  (read-string "(hello-world-fn)")
      func (resolve  (symbol (first call-string)))
      args (rest call-string)]
   (apply func args))

Without success.

Can someone (a) point me in the right direction; and (b) explain precisely what is going on in the Clojure reader when this occurs?

hawkeye
  • 34,745
  • 30
  • 150
  • 304

2 Answers2

6

Try to see what the actual namespace is inside your -main.

(defn -main [& args]
  (prn *ns*)
  (eval (read-string "(hello-world-fn)")))

It outputs #<Namespace user> before bombing out with the exception. This hints that execution of programs with lein run starts out in the user namespace, which obviously does not contain the mapping for your hello-world-fn symbol. You'll need to explicitly qualify it.

(defn -main [& args]
  (eval (read-string "(helloworld/hello-world-fn)")))
Daniel Janus
  • 637
  • 6
  • 10
  • When calling `(hello-world-fn)` directly, it works. Why does it behave different than `eval`? – viebel Feb 07 '12 at 06:22
  • @YehonathanSharvit Because the symbol `hello-world-fn` will be resolved at compile-time in that case, so the namespace declaration near the top the source file is relevant. `"(hello-world-fn)"`, on the other hand, is just a string. The compiler does not look into strings to namespace-qualify them, because it would be wrong to do so; therefore, `eval`, which runs at run-time, needs to resolve name references by looking at the `*ns*` variable when executed. – Matthias Benkard Feb 07 '12 at 11:49
  • Really like the debugging approach and solving it directly. I thought the other one was more out of the box and more elegant. Appreciate @MatthiasBenkard explaining the compile phases. – hawkeye Feb 11 '12 at 10:23
3

You can solve your challenge, in a very elegant way, using macros. In fact, you can write a macro that mimics eval.

(defmacro my-eval [s] `~(read-string s))
(my-eval "(hello-world-fn)")); "Hello World"

It works better that eval because the symbol resolution of s occurs in the context that calls my-eval. Thanks to @Matthias Benkard for the clarifications.

You can read about macros and their syntax in http://clojure.org/reader

viebel
  • 19,372
  • 10
  • 49
  • 83
  • Why does this solve the namespace issue automatically when ns-resolve doesn't? – hawkeye Feb 07 '12 at 10:51
  • 1
    @hawkeye Because `resolve` works at run-time and will resolve symbols within the *current namespace*, which is whatever the `*ns*` variable is pointing to at the time, whereas macro-expanded code within the context of a file is processed by the file compiler, which resolves symbols according to the namespace declarations within the file. – Matthias Benkard Feb 07 '12 at 11:53
  • Thanks - so if `*ns*` is already pointing to helloworld in the example about - why doesn't it `resolve` the function? – hawkeye Feb 07 '12 at 20:58
  • @hawkeye You need to distinguish strictly between run-time and compile-time. At compile-time, the namespace something is being compiled within is known, but after compilation, the code is fully resolved and namespace-qualified, and thus independent of the run-time *current namespace*. Code passed to `eval` is resolved based on the `*ns*` variable *at that point in time*, that is, at run-time, not at compile-time. The `*ns*` variable doesn't magically change when you call a function compiled within another namespace (that would be pretty weird behavior, IMHO). You have to bind `*ns*` manually. – Matthias Benkard Feb 08 '12 at 09:39
  • Much more elegant and flexible. Really like the explanations from @MatthiasBenkard as well. – hawkeye Feb 11 '12 at 10:22