exupero's blog

Debugging macros

In the previous post I showed a few simple macros that I've occasionally found useful. Here are three more sophisticated (yet still succinct) macros that can be helpful when debugging.

Macros receive a hidden &env argument that can be used to capture local scope:

(defmacro locals []
  (into {}
        (map (fn [v]
               `['~v ~v]))
        (keys &env)))
(let [a 1
      b 2]
{a 1, b 2}

Most macros use syntax quote to expand into generated code, but locals doesn't. Instead it builds a hash-map of quoted symbols to unquoted symbols, and evaluation of the macro's result turns the unquoted symbols into their local values.

To debug let bindings that aren't behaving as expected, you can use let-debug, which prints bindings after they're made:

(defmacro let-debug [bindings & body]
  `(let [~@(mapcat
             (fn [[n v]]
               [n v '_ `(println (str (pr-str '~n) ": " ~v))])
             (partition 2 bindings))]
(let-debug [a 1
            b 2
            c (+ a b)
            [e f] [c 4]]
  (+ a b c))
a: 1
b: 2
c: 3
[e f]: [3 4]

I don't recall if I wrote let-debug myself or found it somewhere, so if you've seen something like it, email me.

Finally, here's a macro to drop into a REPL with local bindings predefined (courtesy of the locals macro above):

(defn defs [vars]
  (doseq [[k v] vars]
    (eval (list 'def (symbol k) (list 'quote v)))))
(defmacro repl []
  `(let [ls# (locals)]
     (clojure.main/repl :init #(defs ls#))))

You can use repl like JavaScript's debugger statement to get an interactive prompt in a running program.