dbg: A Cool Little Clojure Macro

Here’s the development of a tiny little macro that is actually pretty useful. The post is quite verbose, because I explain a lot.

I want a debug function that first prints the “quoted” (unevaluated) code and then what it evaluates to, so I can write something like this:

user> (dbg (+ 1 2))
dbg: (+ 1 2)=3
3

This can be useful in iterations, for example, to check whether you get what you expect in each step:

user> (for [i (range 5)] (dbg (+ 1 i)))
(dbg: (+ 1 i)=1
dbg: (+ 1 i)=2
dbg: (+ 1 i)=3
dbg: (+ 1 i)=4
dbg: (+ 1 i)=5
1 2 3 4 5)

Quoting is best explained using an example:

user> (+ 1 2 3)
6

user> (quote (+ 1 2 3))
(+ 1 2 3)

I’m also going to use the let form, to get convenient temporary access to an expression for the duration of the let scope. Outside the let scope, the symbol is not available:

user> (let [x (+ 1 2 3)] (println x))
6
nil

user> x
Unable to resolve symbol: x in this context

So what I want is code that looks something like this:

  (let [x body]
    (println (str "dbg: " (quote body) "=" x))
    x)

In order to get the code in, we declare it as an argument using the varargs symbol (&), which will give us body as a list of forms (expressions):

(defsomething dbg [& body]
  (let [x body]
    (println (str "dbg: " (quote body) "=" x))
    x))

If we declare this as a function using defn, the arguments (body) will be evaluated before the function is called. We don’t want that, so we instead use defmacro to get control over the evaluation. Using the macro syntax quote (`), we can actually write the code exactly as we want it, and then simply unquote (~) the body symbol to get the symbol value:

(defmacro dbg [& body]
  `(let [x ~body]
     (println (str "dbg: " (quote ~body) "=" x))
     x))

However, varargs are delivered as a list, so simply unquoting body will give us some strange result. We can check it using macroexpand-1. You’ll see that the macro tries to evaluate body by placing the list inside a list: ((+ 1 2)):

user> (macroexpand-1 '(dbg (+ 1 2)))
(let [user/x ((+ 1 2))]
  (println (str "dbg: " (quote ((+ 1 2)))) "=" user/x))
  user/x)

What we want is the contents of the varargs list as separate elements, not as a list. We change the unquote operator (~) to the unquote-splicing operator (~@):

(defmacro dbg [& body]
  `(let [x ~@body]
     (println (str "dbg: " (quote ~@body) "=" x))
     x))

We expand the macro again, and now it looks better:

user> (macroexpand-1 '(dbg (+ 1 2)))
(let [user/x (+ 1 2)]
  (println (str "dbg: " (quote (+ 1 2)))) "=" user/x))
  user/x)

However, note the fully namespace-qualified symbol user/x. Clojure will not allow this due to the risks of name conflicts. When we try to run this macro, we will get an exception: Can’t let qualified name: user/x. We need some way of generating a unique symbol on the fly. We use the symbol generation operator (#):

(defmacro dbg [& body]
  `(let [x# ~@body]
     (println (str "dbg: " (quote ~@body) "=" x#))
     x#))

We test it:

user> (macroexpand-1 '(dbg (+ 1 2)))
(let [x__2071__auto__ (+ 1 2)]
  (println (str "dbg: " (quote (+ 1 2)) "=" x__2071__auto__))
  x__2071__auto__)

OK, so the final macro looks like this:

(defmacro dbg [& body]
  `(let [x# ~@body]
     (println (str "dbg: " (quote ~@body) "=" x#))
     x#))

This boils down to two very simple rules:

  • Any temporary symbols must be appended with # to become unique symbols
  • Any varargs arguments must be unquote-spliced using ~@

Let’s try our macro:

user> (dbg (+ 1 2))
(+ 1 2)=3
3

Let’s confirm that the debug functionality does not affect others using the dbg’d code:

user> (* 3 (dbg (+ 1 2)))
dbg: (+ 1 2)=3
9

Now, let’s try the dbg macro on some more advanced code. Here’s an implementation of factorial using loop-recur:

(def factorial
  (fn [n]
    (loop [cnt n acc 1]
      (if (zero? cnt)
        acc
        (recur (dec cnt) (* acc cnt))))))

To check what happens inside the recur function, I add calls to dbg:

(def factorial
  (fn [n]
    (loop [cnt n acc 1]
      (if (zero? cnt)
        acc
        (recur (dbg (dec cnt)) (dbg (* acc cnt)))))))

Calling it gives:

user> (factorial 5)
dbg: (dec cnt)=4
dbg: (* acc cnt)=5
dbg: (dec cnt)=3
dbg: (* acc cnt)=20
dbg: (dec cnt)=2
dbg: (* acc cnt)=60
dbg: (dec cnt)=1
dbg: (* acc cnt)=120
dbg: (dec cnt)=0
dbg: (* acc cnt)=120
120

Update:

There’s actually no point in having body be a varargs. In fact, it’s incorrect to allow more than one form, since the binding in the let form expects a single form. The macro should be:

(defmacro dbg [body]
  `(let [x# ~body]
     (println (str "dbg: " (quote ~body) "=" x#))
     x#))

It could be shortened further by using the short form for quote:

(defmacro dbg [body]
  `(let [x# ~body]
     (println (str "dbg: " '~body "=" x#))
     x#))

We can also skip the str call, and accept the println formatting:

(defmacro dbg [body]
  `(let [x# ~body]
     (println "dbg:" '~body "=" x#)
     x#))

Let’s verify that it still works:

user> (* 3 (dbg (+ 1 2)))
dbg: (+ 1 2) = 3
9

This Post Has 5 Comments

  1. Hi Ulrick..thanks for this post..the biggest think than I don’t like about clojure is the hard this and the few tools than clojure has for debug for a beginner…I’m reading the joy of clojure and I would debug the sorting algorithm..It isn’t so simple for a beginner like me:

    (defn sort-parts
    “Lazy, tail-recursive, incremental quicksort. Works against
    and creates partitions based on the pivot, defined as ‘work’.”
    [work]
    (lazy-seq
    (loop [[part & parts] work]
    (if-let [[pivot & xs] (seq part)]
    (let [smaller? #(< % pivot)]
    (recur (list*
    (filter smaller? xs)
    pivot
    (remove smaller? xs)
    parts)))
    (when-let [[x & parts] parts]
    (cons x (sort-parts parts)))))))

    in other language it would be simple..only set a couple of breakpoints and problem solved..but with clojure is more difficult…How you solve this?

    would be usefull a macro than let debug inside a let binding like
    (let [[(dbg a)] vector] (…)) is it possible??

    thanks for your patience and for this post…good luck

  2. I’ve been using a similar macro for a while that’s slightly simpler. But I’m a clojure beginner, so I’m not certain it’s equivalent. Any thoughts on this version?

    (defmacro debug [body]
    `(let [x# ~body]
    (println “DEBUG” ~(str body) “=” x#)
    x#))

  3. @Kris, you’re right in that it could be made simpler. I’m not sure that ‘str’ on a form is equivalent to quoting it, though.

    Come to think of it, I don’t see the point in having the body being a varargs. I could also skip the intermediate ‘str’ and just use ‘println’, like you do:

    (defmacro dbg [body]
    `(let [x# ~body]
    (println “dbg:” (quote ~body) “=” x#)
    x#))

    The short form of ‘quote’ is shorter than ‘str’:

    (defmacro dbg [body]
    `(let [x# ~body]
    (println “dbg:” ‘~body “=” x#)
    x#))

    Very similar to your version.

  4. Dear Ulrik,

    Your lessons are simple and clear, meaning You know the problem in depths.

    You are the first one to let me undestand clojure macros, its reasoning and structures, despite i read a lot of other lessons as a beginner.

    Thank You
    your fan from Poland :-)

Leave a Reply

Close Menu