(or emacs irrelevant)

Debug Clojure with CIDER and lispy

To commemorate the release of CIDER 0.9.0, I've just added the ability to debug-step-in Clojure expressions to lispy.

This ability was present for Elisp for a very long time, and it's instrumental to my Elisp output. So now I've added exactly the same thing to Clojure.

How it works

Suppose that you have this function (borrowed from The Joy of Clojure):

(defn l->rfix
  ([a op b]
   (op a b))
  ([a op1 b op2 c]
   (op2 c (op1 a b)))
  ([a op1 b op2 c op3 d]
   (op3 d (op2 c (op1 a b)))))

An important thing is that the function needs to be loaded (probably with cider-load-file), in order for Clojure to know its location. If you only evaluate the function with C-x C-e, it won't work. Actually, the same applies to Elisp.

And then you have the corresponding function call (| is the point, as usual):

|(l->rfix 10 * 2 + 3)

As you press xj (lispy-debug-step-in), the following code is evaluated on the Clojure side:

(do
  (def a 10)
  (def op1 *)
  (def b 2)
  (def op2 +)
  (def c 3))

At the same time, you are taken to the second branch of the body of l->rfix - exactly the one that corresponds to 5 arguments. And that's it: you now have a, op1 etc defined to their proper values. You can now continue within the function body with many possible follow-ups. I'll just list the eval-related ones:

  • Use e to evaluate expression at point.
  • If you want to evaluate a symbol, mark it with M-m, end evaluate with e. Actually, I prefer to mark stuff with m and hjkl arrows, using i to mark the first element of the region. It's also possible to mark with a, and 2m, 3m etc.
  • If you want to bind a symbol in a let binding, mark both the symbol and its value, and press e. If there are many let bindings, you can navigate to the next one with either jj or 2j.
  • You can debug-step-in again if needed with xj.
  • You can flatten a function or a macro call with xf.
  • You can eval-and-insert with E.
  • You can eval-and-commented-insert with 2e.

Just to add, any of these will work properly and switch to an appropriate body branch:

(l->rfix 1 * 1 + 1)
(l->rfix 2 + 7)
(l->rfix 1 + 2 + 3 + 4)
(l->rfix (str "a" "b" "c") + 7)

Outro

Enjoy the new code, but be mindful that it's really fresh, so it will certainly have some quirks. For instance, function arguments as a map won't work since I haven't programmed for that yet. Big thanks to @bbatsov and all CIDER contributors.

A really cool thing that I hope to get in the future, is to make Z (lispy-edebug-stop) also work in Clojure. What it does currently for Elisp, is to use edebug to setup the function arguments. It may be possible to use @Malabarba's new debugger implementation for the same thing.

The advantages of using lispy-style debugger are the following:

  • You can navigate your code the way you want, not just in a way that the debugger allows you to.
  • You can multi-debug, and have sessions last for days.
  • You can edit the code as you debug. For this new code, I started debugging when there was only a function name. I added the body code expression-by-expression, simultaneously debugging it.

The disadvantage is a bit of namespace pollution, but I think it's more or less acceptable.