5
votes

I often want to run a small snippet of code in another namespace - perhaps a copy/pasted snippet of DSL code for example, and I'd like to avoid having to either:

  • Add a bunch of use clauses to my current namespace declaration. This makes the ns declaration messy, adds extra maintenance work and sometimes risks name clashes.
  • Add require clauses and be forced to add a namespace qualifier or alias to everything. Now my DSL code is much messier.

Ideally I'd prefer to be able to do something like:

(with-ns my.namespace
  (foo bar baz))

Where foo, bar might be symbols within my.namespace, but baz is a symbol in the current (enclosing) namespace. So the code is running in something like a "local" namespace that "uses" my-namespace within its scope but otherwise doesn't affect the surrounding namespace.

Is there a standard/better way to do this? Or is this a crazy thing to want to do?

3
I use load-file to sort of get this, it's a hack :-/ So you're not the only one, it could still be crazy though. - Arthur Ulfeldt
@Arthur - hmmm see how that could work but often the snippet isn't in a self contained file, also it seems like abuse of the filesystem when we dont need to. Good meeting you at the Conj BTW! - mikera
It is limiting because the DSL uses (in my case network+vm definitions need to be one per file so it's not a good answer. It was cool to meet you as well :) - Arthur Ulfeldt

3 Answers

4
votes

Try this one:

(defmacro with-ns [[namespace symbols] & body]
  `(do (use '[~namespace :only ~symbols])
       (let [result# (do ~@body)]
         (doseq [sym# (map #(:name (meta (val %)))
                           (filter #(= (name '~namespace)
                                       (str (:ns (meta (val %)))))
                                   (ns-refers *ns*)))]
           (ns-unmap *ns* sym#))
         result#)))

(with-ns [clojure.string [split upper-case]]
  (split (upper-case "it works!") #" "))
-> ["IT" "WORKS!"]

After work it removes used symbols from current ns.

3
votes

This can be achieved using a macro as shown below.

NOTE: It may break in certain cases as I just tried it with a simple example

;Some other ns
(ns hello)
(def h1 10) ;in hello
(def h2 11) ;in hello

;main ns in which executing code
(ns user)


(defmacro with-ns [target-ns body]
  (clojure.walk/postwalk
   (fn [val]
     (if (symbol? val)
       (if (resolve (symbol (str target-ns "/" val)))
         (symbol (str target-ns "/" val))
         val) val)) body))

(def u1 100) ;in user

(with-ns hello (do (+ h1 u1))) ;110
0
votes

I eventually found a macro in the old Clojure contrib that does part of this quite neatly:

(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)))