Clojure Macros: Creating vars from a map

The other day I was looking for a way to turn a map into a bunch of vars. I know a macro is what will get the job done but I write macros so rarely that it always takes me a while to figure it out. In the end I ended up with something like this:

(defmacro def-all [m]
  (->> (for [[n v] m]
         `(def ~(symbol n) ~v))
       (into [])))

Using macroexpand you can see that this translates to the a bunch of def calls in a vector:

user=> (macroexpand '(def-all {:a 1 :b 2}))
[(def a 1) (def b 2)]

Like myself, you may wonder why the vector is needed. The issue is that for will return a list and that would result in the macro emitting the following Clojure code:

((def a 1) (def b 2))

After evaluating the two inner def forms, this will result in another function call where the return value of the first def is used as a function. Depending on what you are defining this may fail or lead to unexpected behavior.

After sharing my solution using (into []) in the Clojurians Slack I was made aware that instead if turning the thing into a vector you can also just prepend a do into that list, resulting in code that feels slightly more aligned with my intention:

(defmacro def-all [m]
  (->> (for [[n v] m]
         `(def ~(symbol n) ~v))
       (cons 'do)))

(macroexpand '(def-all {:a 1 :b 2}))
; returns
(do (def a 1) (def b 2))

I realize this is a super basic macro but I can totally see how that might be useful to people starting to write their own macros. If you're looking for a more full-fledged guide, Clojure for the Brave and True got you covered.

Thanks to Justin Smith for sharing his experience on Slack with me so many times.

@martinklepsch, February 2021