Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Executing code in another Clojure namespace, why is eval required?

Tags:

clojure

I'm looking at the implementation for the old contrib macro with-ns:

(defmacro with-ns
  "Evaluates body in another namespace. ns is either a namespace
   object or a symbol. This makes it possible to define functions in
   namespaces other than the current one."
  [ns & body]
  `(binding [*ns* (the-ns ~ns)]
     ~@(map (fn [form] `(eval '~form)) body)))

What I don't understand is the need to eval the body. In other words, why doesn't this work in the case where I want to access elements in the target namespace within the body w/o the eval (example below).

user=> (defmacro wns [ns & body] `(binding [*ns* (the-ns ~ns)] ~@body))
#'user/wns
user=> (create-ns 'boofar)
#<Namespace boofar>
user=> (in-ns 'boofar)
#<Namespace boofar>
boofar=> (clojure.core/refer-clojure)
nil
boofar=> (defn xx [a b] (str a b))
#'boofar/xx
boofar=> (xx 5 6)
"56"
boofar=> (in-ns 'user)
#<Namespace user>
user=> (with-ns 'boofar (println *ns*) (xx 5 6))
#<Namespace boofar>
"56"
user=> (wns 'boofar (println *ns*) (xx 5 6))

CompilerException java.lang.RuntimeException: 
    Unable to resolve symbol: xx in this context, 
    compiling:(blardy/blardy/form-init3758076021118964250.clj:1:29)
user=> (wns 'boofar (println *ns*))
#<Namespace boofar>
nil    

I'm OK with this. I really don't mind, but I'd like to understand what's going on here. xx clearly exists in the boofar namespace, and pushing the ns binding should put me there, yet I can't call it directly, however I can call something in clojure.core which has been referred. In case you're wondering, I tried re-writing the macro to use in-ns with the corresponding try/finally and the result is the same.

Thanks!

edit

Added example of the macro using in-ns. Results are the same as without.

user=> (defmacro wins [ns & body] 
  `(let [orig-ns# (ns-name *ns*)] 
     (try 
       (in-ns ~ns) 
       ~@body 
       (finally (in-ns orig-ns#)))))

The expanded macro...

user=> (pprint (macroexpand-all '(wins 'boofar2 (xx 7 8))))
(let*
 [orig-ns__4137__auto__ (clojure.core/ns-name clojure.core/*ns*)]
 (try
  (clojure.core/in-ns 'boofar2)
  (xx 7 8)
  (finally (clojure.core/in-ns orig-ns__4137__auto__))))

Using it...

user=> (defn xx [a b] (str "user/xx [" a " " b "]"))
user=> (in-ns 'boofar2)
boofar2=> (defn xx [a b] (str "boofar2/xx [" a " " b "]"))
boofar2=> (in-ns 'user)
user=> (wins 'boofar2 (xx 7 8))
"user/xx [7 8]"
like image 207
Bill Avatar asked Dec 23 '13 21:12

Bill


1 Answers

Setting ns with bind doesn't quite work that way, which is why the doc's for namespaces include this warning:

"The current namespace, *ns* can and should be set only with a call to in-ns or the ns macro, both of which create the namespace if it doesn't exist."

In your example the symbol xx is being resolved in the namespace user instead of the namespace that *ns*is bound to:

user> (defn xx [a b] "I'm the xx from ns user")
#'user/xx
user> (wns 'boofar (println *ns*) (xx 5 6))
#<Namespace boofar>
"I'm the xx from ns user"
like image 125
Arthur Ulfeldt Avatar answered Sep 22 '22 12:09

Arthur Ulfeldt