Search code examples
clojuremacrosclojurescript

Pass data to macro via variable in Clojure(Script)


Is there any trick to make this work without passing the data directly or using eval?

(defmacro pair-defs [data]
  (cons 'do
    (for [[k v] data]
      `(def ~k ~v))))

(def data '((a 1) (b 2) (c 3)))
(pair-defs data)

Solution

  • If your data var is defined in namespace before the macro call,you can use some namespace manipulating functions to resolve it's value by name:

    (defmacro pair-defs [data]
      `(do ~@(for [[k v] @(ns-resolve *ns* data)]
               `(def ~k ~v))))
    
    user> (def data [['a 10] ['b 20]])
    #'user/data
    
    user> (pair-defs data)
    #'user/b
    

    or to handle both literal data and data by var name:

    (defmacro pair-defs [data]
      (let [dt (if (symbol? data) @(ns-resolve *ns* data) data)]
        `(do ~@(for [[k v] dt]
                 `(def ~k ~v)))))
    

    the call is expanded to the desired form: (do (def a 10) (def b 20))

    So resolving the namespace val without dirty tricks like eval is totally possible, but i find it to be quite an unneeded usage of macros. For example your task is easily replaceable by plain function call.

    (defn pair-defs1 [data]
      (doseq [[k v] data]
        (intern *ns* k v)))
    

    it would work on any data sequence, be it local or global, literal or generated