A summary of techniques to debug Clojure code via REPL (aka “the interactive console”, the “read-eval-print loop”) and Cider debugger.
Just print
As a beginner, it’s not clear how to print things from awkward points like inside of a (let [...])
binding declaration, or a (-> ...)
threading expression.
My initial journey landed on this article REPL Based Debugging in Clojure. It’s an easy start.
Fundamentals: the clojure.org docs
Enhancing your REPL workflow is a must read! You can skip the intro, straight to the Debugging section. Read about inspecting and logging with libraries like spyscope and clojure.tools.logging.
“Reproducing the context of an expression” is extremely useful when doing REPL development & debugging. Often times you work on a section of a function body. Evaluating that section alone will throw unable to resolve symbol
. The “fix” consists of simply defining the intermediary variables that are expected in that section. I discovered this technique accidentally and was wondering if it’s fair game. When the clojure docs sanctify it, I guess it’s fair.
More to read: inline-def debugging and repl debugging: no stacktrace required. Finally, try not to skip the “Community Resources” section.
Data vizualization at the REPL goes over some more REPL tricks:
*e
is bound to the last thrown error. Print a stacktrace with(pst *e)
*1
,*2
,*3
are bound to the last recent results in the REPL. Save for further use with(def foo *1)
- Reflection used to inspect mysterious values
(type v)
and(ancestors (type v))
show the type and the class hierarchy.clojure.reflect
like(reflect/reflect (type v))
.
The debugger: Cider
Cider is an Emacs debugger for Clojure. Highlighting some workflows from “using the debugger” section in the cider docs.
Instrument a defn
with C-u C-M-x
. All refs to the function will be highlighted now. The debugger will break when calling the instrumented function.
Once instrumented, invoking the debugger on the first (assert)
statement breaks and you’re dropped into the Cider debug menu.
To remove instrumentation, re-evaluate the defn
.
Alternatives
- place
#break
in front of the statement you want to break on, eg:(#break map foo [1 2 3])
C-M-x
debug at point
Evergreen authors
Aphyr: https://aphyr.com/posts/319-clojure-from-the-ground-up-debugging. Recommend going through the whole series on “Clojure from the Ground Up”.
Aphyr has done amazing work analyzing consistency properties on numerous popular database systems here: https://jepsen.io/analyses.
Eli Benderski: https://eli.thegreenplace.net/2017/notes-on-debugging-clojure-code/.
-
excellent article! on point about the main difficulties of debugging clojure code
-
(pst)
to print stacktrace for last error -
“Learning to map from Clojure values and types to the JVM’s expectations will take time and grit - especially if you (like me) don’t have much Java experience. I suggest doing a bit of reading on Clojure/Java interoperability, and about other Java-isms Clojure inherits; it ain’t pretty, and you may not always want to use it, but being familiar with the terms can go a long way in deciphering cryptic stack traces.”
-
(trace-forms (foo bar))
https://github.com/clojure/tools.trace#example-usage- “very useful when errors manifest as exceptions. Unfortunately, this isn’t sufficient for all cases”
-
(trace-vars (...))
-
diy Macros “I find that debuggers are even less useful in Clojure than in other languages. On the other hand, Clojure’s macros make it possible to trace / print stuff in a very nice way.”
I knew Eli’s blog from his series of articles on the Go compiler which I loved reading while digging through the code.
Extra refs
http://www.futurile.net/2020/05/16/clojure-observability-and-debugging-tools/. Part of a series, goes over a few popular observability libraries in clojure, with examples. There are good references at the end of the article.
Fin
Will add more content as I progress.