Debugging Clojure at the REPL using tap>
Tags: clojure, debugging
Published: 2021-09-15 23:56:26

Sometimes, old habits die hard.  For a long time, I've done a lot of debugging in my Clojure code at the REPL, using `prn`.

With Clojure, debugging by writing to the console is not as primitive as it may sound; sure, you can fire up the debugger and set a break point, but by injecting a `prn` at just the right spot, and with just the right data, you often get just exactly the useful data you need to diagnose a problem.

Other times, with meatier data structures, I will break out `clojure.pprint/pprint` ... but this requires more work, to import the namespace, and means I also have to cleanup the `ns` declaration.

Now, I noticed a ways back that Clojure 1.10 added the [`tap>`](https://clojure.github.io/clojure/clojure.core-api.html#clojure.core/tap%3E) function, and I vaguely knew it would be helpful for this kind of thing; I finally got around to trying it out to debug some hairy NullPointerException bugs in [Lacinia](https://github.com/walmartlabs/lacinia/pull/379).

What does `tap>` do?  By default ... nothing. It's just a function you can pass a value to, but nothing happens when you do (it also returns true).

`tap>` is hardly useful until you add a tap with [add-tap](https://clojure.github.io/clojure/clojure.core-api.html#clojure.core/add-tap); a tap is any function that takes a single argument.

Invoking `tap>` will cause the function (or functions, if `add-tap` is called multiple times) to be invoked asynchronously.

So if you, for example, `(add-tap clojure.pprint/pprint)` each subsequent call to `tap>` will be pretty-printed to the console.  And the `tap>` function is in core, so no require needed.

I've found that I don't want to `tap>` simple values, because single values do not provide much context into whatever problem I'm trying to solve. Instead I build a map that is tapped as a unit:

```clojure
(defn fn-i-want-to-debug 
  [simple-arg sometimes-nil-but-too-large-to-print-arg]
  (tap> {:in `fn-i-want-to-debug
         :simple simple-arg
         :nil? (nil? sometimes-nil-but-too-large-to-print-arg})
   ...)
```

Because of course in Clojure, it's always easy to just make a new map on the fly.  Here, the backquote (it's the [syntax quote](https://www.clojure.org/reference/reader#syntax-quote)) will ensure that the function name is a fully namespace qualified symbol.

As the above example shows, sometime an argument is too large to effectively print but maybe, for your debugging, you just need to know if it is nil or not ... or maybe you want to `dissoc` some keys, or otherwise prepare the data that gets output.  That's fine, that's why REPL oriented development can be faster and better than firing up the debugger.

A debugger can only show you *one* thing at a time; and even using Cursive, it can be a lot of clicks and a lot of squinting at the screen to see exactly what's going on.

By comparison, console output (via `tap>`) can be a little history of what's going on in your program leading up to a problem.  

Further, nothing is stopping you from using a debugger and `tap>` together (but you may find that you don't need that nearly as often as you'd think).

`tap>` is better than straight-up calls to `prn` or `pprint` because you can leave the calls to `tap>` in your code with little or no cost; you can `add-tap` when you need to ... and in fact, you can also `remove-tap` when you want to continue developing without the extra output, all without restarting your REPL.

That's pretty classic Clojure for you ... a minimal and practical tool that becomes significantly more useful because Clojure and its REPL are so well wedded together.