muhuk's blog

Nature, to Be Commanded, Must Be Obeyed

August 05, 2014

Clojure Macro Spotted In Wild: ? of useful

Let’s take a look at ? macro of useful this time. The docstring tells the purpose of this macro succinctly:

A useful debugging tool when you can’t figure out what’s going on: wrap a form with ?, and the form will be printed alongside its result. The result will still be passed along.

Before taking a look at the source code let’s go through some usage examples since this one’s a bit more tricky than defproject:

user=> (str (list 1 2 (+ 1 2)))
"(1 2 3)"

user=> (str (? (list 1 2 (? (+ 1 2)))))
(+ 1 2) is 3
(list 1 2 (? (+ 1 2))) is (1 2 3)
"(1 2 3)"

The second form is the same with the first one except list and + invocations are wrapped with our ? macro. The results are exactly the same. If you are not familiar with the REPL; the result is "(1 2 3)" and it’s printed when the execution is completed. The second form prints out two more lines. These are the debug messages from the forms we have wrapped. We will revisit the results soon. What I would like to draw your attention to is that those two debug messages explain what happens during the execution.

Here is another example, this time we are using a looping construct:

user=> (map #(* % %) (range 10))
(0 1 4 9 16 25 36 49 64 81)

user=> (map #(? (* % %)) (range 10))
((* p1__1723# p1__1723#) is 0
(* p1__1723# p1__1723#) is 1
(* p1__1723# p1__1723#) is 4
(* p1__1723# p1__1723#) is 9
(* p1__1723# p1__1723#) is 16
(* p1__1723# p1__1723#) is 25
(* p1__1723# p1__1723#) is 36
(* p1__1723# p1__1723#) is 49
(* p1__1723# p1__1723#) is 64
(* p1__1723# p1__1723#) is 81
0 1 4 9 16 25 36 49 64 81)

As expected there are 10 debug lines. As expected there are funny symbols because of the anonymous function. But something is strange with the last line; the result is missing the opening paren. A closer look would reveal that it is in the first line. This is because map returns a lazy-seq. There is nothing wrong with ?, it is just the way REPL evaluates return values:

user=> (def result (doall (map #(? (* % %)) (range 10))))
(* p1__1788# p1__1788#) is 0
(* p1__1788# p1__1788#) is 1
(* p1__1788# p1__1788#) is 4
(* p1__1788# p1__1788#) is 9
(* p1__1788# p1__1788#) is 16
(* p1__1788# p1__1788#) is 25
(* p1__1788# p1__1788#) is 36
(* p1__1788# p1__1788#) is 49
(* p1__1788# p1__1788#) is 64
(* p1__1788# p1__1788#) is 81
#'user/result

user=> result
(0 1 4 9 16 25 36 49 64 81)

user=> (def result-lazy (map #(? (* % %)) (range 10)))
#'user/result

user=> (first result-lazy)
(* p1__1808# p1__1808#) is 0
(* p1__1808# p1__1808#) is 1
(* p1__1808# p1__1808#) is 4
(* p1__1808# p1__1808#) is 9
(* p1__1808# p1__1808#) is 16
(* p1__1808# p1__1808#) is 25
(* p1__1808# p1__1808#) is 36
(* p1__1808# p1__1808#) is 49
(* p1__1808# p1__1808#) is 64
(* p1__1808# p1__1808#) is 81
0

user=> (second result-lazy)
1

This is how it works. Now let’s take a look at the code:

(require '[clojure.pprint :as p])
(require '[clojure.stacktrace :as s])

(letfn [(interrogate-form [list-head form]
          `(let [display# (fn [val#]
                            (let [form# (with-out-str
                                          (clojure.pprint/with-pprint-dispatch
                                            clojure.pprint/code-dispatch
                                            (clojure.pprint/pprint '~form)))
                                  val# (with-out-str (clojure.pprint/pprint val#))]
                              (~@list-head
                              (if (every? (partial > clojure.pprint/*print-miser-width*)
                                          [(count form#) (count val#)])
                                (str (subs form# 0 (dec (count form#))) " is " val#)
                                (str form# "--------- is ---------\n" val#)))))]
            (try (doto ~form display#)
                  (catch Throwable t#
                    (display# {:thrown t#
                              :trace (with-out-str
                                        (clojure.stacktrace/print-cause-trace t#))})
                    (throw t#)))))]

  (defmacro ?
    "A useful debugging tool when you can't figure out what's going on:
  wrap a form with ?, and the form will be printed alongside
  its result. The result will still be passed along."
    [val]
    (interrogate-form `(print) val))

  (defmacro ^{:dont-test "Complicated to test, and should work if ? does"}
    ?!
    ([val] `(?! "/tmp/spit" ~val))
    ([file val]
      (interrogate-form `(#(spit ~file % :append true)) val))))

I started with the usage because the code is not very easy to read. But it’s not rocket surgery either. The general shape of it is something like this:

(letfn [...definition-of-interrogate-form..]
  (defmacro ? [val]
    (interrogate-form `(print) val))
  (defmacro ?! ...))

As you can see interrogate-form does the heavy lifting. It builds the code that does these three things:

  • Execute the form.
  • Output the form and it’s result.
  • Return the result.

I find it fascinating that doto fits the bill perfectly:

user=> (defn debug [val] (println :debug val))
#'user/debug

user=> (doto (+ 1 2) debug)
:debug 3
3

This is the expanded form for a simple application of ?:

user=> (p/pprint (macroexpand '(? (+ 1 2))))
(let*
[display__1194__auto__
  (clojure.core/fn
  [val__1195__auto__]
  (clojure.core/let
    [form__1196__auto__
    (clojure.core/with-out-str
      (clojure.pprint/with-pprint-dispatch
      clojure.pprint/code-dispatch
      (clojure.pprint/pprint '(+ 1 2))))
    val__1195__auto__
    (clojure.core/with-out-str
      (clojure.pprint/pprint val__1195__auto__))]
    (clojure.core/print
    (if
      (clojure.core/every?
      (clojure.core/partial
        clojure.core/>
        clojure.pprint/*print-miser-width*)
      [(clojure.core/count form__1196__auto__)
        (clojure.core/count val__1195__auto__)])
      (clojure.core/str
      (clojure.core/subs
        form__1196__auto__
        0
        (clojure.core/dec (clojure.core/count form__1196__auto__)))
      " is "
      val__1195__auto__)
      (clojure.core/str
      form__1196__auto__
      "--------- is ---------\n"
      val__1195__auto__)))))]
(try
  (clojure.core/doto (+ 1 2) display__1194__auto__)
  (catch
  java.lang.Throwable
  t__1197__auto__
  (display__1194__auto__
    {:trace
    (clojure.core/with-out-str
      (clojure.stacktrace/print-cause-trace t__1197__auto__)),
    :thrown t__1197__auto__})
  (throw t__1197__auto__))))
nil

Here is the same thing, but I have cleaned it up for readability:

(let [display# (fn [val#]
                 (let [form# (with-out-str
                               (p/with-pprint-dispatch
                                 p/code-dispatch
                                 (p/pprint '(+ 1 2))))
                       val# (with-out-str (p/pprint val#))]
                   (print
                     (if (every? (partial > p/*print-miser-width*)
                                 [(count form#) (count val#)])
                       (str (subs form# 0 (dec (count form#))) " is " val#)
                       (str form# "--------- is ---------\n" val#)))))]
  (try
    (doto (+ 1 2) display#)
    (catch java.lang.Throwable t#
      (display# {:trace (with-out-str (clojure.stacktrace/print-cause-trace t#)),
                 :thrown t#})
      (throw t#))))

The core of this code is (doto (+ 1 2) display#). This is wrapped with a try. If the form is executed successfully, the result is run through display# and then returned. If it throws an exception then the stacktrace is run through display# and then the exception is re-thrown.

Function display# is not complicated. It might look a bit imposing the the original source but the only difference from my simplified version above is the quoted forms. ?! macro writes the output to a file by using `(#(spit ~file % :append true)) as the list-head while ? simply uses `(println).

Biggest advantage of ? is that it makes surgical debugging very easy. You can wrap and unwrap your forms easily especially if you are using an editor with paredit support. Even though it produces side-effects, there are no modifications in the global/namespace scope. For that I would classify ? as a functional macro.

There are all kinds of interesting utilities in useful. Even if you are not shopping, I would suggest you to take a look around for educational purposes.

If you have any questions, suggestions or corrections feel free to drop me a line.