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]
(locals))
{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))]
~@body))
(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]
6
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.