Settings

Theme

Debugging in Clojure

blog.davemartin.me

124 points by DaveWM 4 years ago · 27 comments

Reader

fulafel 4 years ago

The first example about spyscope potentially confuses the reader a bit by using it in context of println debugging. The point of it is you can stick it in front of any form. For a better example, see https://github.com/dgrnbrg/spyscope#spyp

Otherwise great article, I hadn't heard about scope-capture.

  • DaveWMOP 4 years ago

    Thanks! Perhaps the wording isn't great in that paragraph, I intended the phrase "println debugging" to mean the general style of debugging, rather than specifically using the println function.

    • zurn 4 years ago

      Actually i misread the example as using println instead of str because my brain had been primed by println in the headline!

zurn 4 years ago

For those of us using CIDER don't forget #break / #dbg!

https://docs.cider.mx/cider/debugging/debugger.html#using-th...

  • geokon 4 years ago

    That allows you to step through code. but it doesn't break when you get a crash (like gdb). At least when I just tested:

         #dbg(defn get-first [input] (nth input 0))
         (get-first [4 2 3]) ;; 4
         (get-first 5) ;; gives me a stack trace and no program state
    
    So it's nice to have, but I think this is maybe only useful if you're working through someone else's code?
    • divs1210 4 years ago

      This is not such a huge issue in practice. It is often enough to step through the debugger and see exactly what is failing.

      In conjunction with scope-capture, it is trivial to run that failing code again to have a closer look, and modify it to your liking till it runs as expected.

      There are also conditional restart systems for Clojure that offer much more than this.

brokenkebab 4 years ago

Elephant in the room: Sayid. It's made for Emacs, and it's so powerful, you may want to switch to Emacs just because of it.

  • DaveWMOP 4 years ago

    Sayid does look really powerful. However, I declared Emacs bankruptcy years ago, and switched to Cursive. If I ever have the courage to switch back, I'll give Sayid a go :P

NightMKoder 4 years ago

This isn’t mentioned in the article, but I found debug-repl [1] to be amazing when debugging in the repl. For simple prints, wrapping expressions in (doto prn) has always been enough for me without custom readers. But when debugging something super odd, just stopping somewhere and evaluating a bunch of code (with locals in scope) to understand what you’re dealing with is invaluable. Clojure’s immutability makes it especially nice since you can just rerun (most) expressions to “see what happens.”

[1] https://github.com/gfredericks/debug-repl

  • DaveWMOP 4 years ago

    I've heard good things about debug-repl, although I haven't used it myself. From what I understand, it fills a similar niche to scope-capture. However you do it, being able to capture the state at a certain point in the code is essential.

slifin 4 years ago

Some additional conversation on this blog post here:

https://www.reddit.com/r/Clojure/comments/oe40af/debugging_i...

phoe-krk 4 years ago

For people who want to get a more CL-like debugging setup on Clojure, there is a CL-style condition system available as a library.

https://github.com/IGJoshua/farolero/

geokon 4 years ago

Could `scope-capture` be used to capture state before a crash?

What would be nice is a GDB-like state along with stacktraces when you get a crash. Clojure stack traces are notoriously long and spooky. You learn to read the tea leaves, but even if you manage to identify where the crash happened and what triggered it (not always obvious..) you then need to pepper things with `println` to figure out the last local state before the whole thing blew up.

It looks like once you've found the problem area you can use `sc.api/spy` ..? It'd at least solve half the problem

  • fulafel 4 years ago

    I find the stack traces to be fine as long as there's some middleware in use to filter out the Java stack frames from non-Clojure code, like eg CIDER does (and probably other envs can too). Otherwise they have some noise which is a bit annoying but not showstopper.

    println (or other logging/tracing, eg using tap) works for seeing where things go wrong, but stepping through code with a debugger works too if you're so inclined, or experimeting at the repl.

    Sure, it's not always obvious what caused bad input that evnetually leads to an uncaught exception, just like in other languages.. it might have come through an event loop or other layer of indirection that doesn't show the real caller, for example.

    • geokon 4 years ago

      right, it's not like you're completely stuck with no way to figure out what's causing your problems. In pretty much every language you can get a crash stack and pepper print/logging statements. And sure, the Clojure stacks aren't always impenetrable (unless it's blowing up in some call back or lazy evaluation.. then good luck). But I don't think it should be brushed under the rug that that the debug situation in Clojure is problematic.. and it's behind ancient creaking languages like Elisp and the much laughed at C++. The REPL is great for sure, and it makes punching out code much faster, but when you need to debug some deep problem I start to miss GDB a bit :)

      Next time I'm in a bind I'll have to try `scope-capture`. It looks like it might get me half way there

      • fulafel 4 years ago

        Valid point about laziness, it can make for head scratchy stack traces. A debugger works well for that as well. I think the lazy by default behaviour of many things in the stdlib is a questionable design decision, and try to use eager versions of functions my default (eg mapv and filterv).

  • DaveWMOP 4 years ago

    Assuming the crash doesn't cause the process to completely exit, you could indeed use `scope-capture` for this. This works well for local dev. In theory, you could use `sc.api/spy` in production code, and then attach a remote repl to diagnose any crashes. I wouldn't recommend this though, I think it would be best to use a good logging library like Mulog: https://github.com/BrunoBonacci/mulog

    • geokon 4 years ago

      Right, if you're doing web-tech stuff and have servers then this seems the right way to go. I'm usually more interested in local REPL development. I will have like GUIs and whatnot blow up on me. But I appreciate the info and the post

tankfeeder 4 years ago

Debugging in PicoLisp https://envs.net/~mpech/tut.html#dbg

billfruit 4 years ago

Does the Clojure debugger give a live Repl, where one can examine values of locals and restart execution if required, likes the elisp debugger in Emacs?

  • DaveWMOP 4 years ago

    Cursive's debugger doesn't, but as one of the other posters mentioned there's a library called debug-repl which gives you this: https://github.com/gfredericks/debug-repl.

    However, as I mentioned in the article, I've found it's usually better to use scope-capture than a debugger that pauses execution. The main reason is that I mainly work with Kafka Streams atm, and when the debugger pauses one thread other threads start timing out and throwing exceptions.

    • billfruit 4 years ago

      That seems to give a repl at a breakpoint, I was asking about getting a repl at a point of exception. That is what elisp gives.

      • DaveWMOP 4 years ago

        Ah sorry, I don't know of a way of doing that in Clojure. What I usually do is figure out where the exception was thrown, wrap that form in `sc.api/spy`, and then somehow re-execute it.

    • slifin 4 years ago

      Cursive's debugger does let you restart the execution?

      It's a normal step debugger, and you can run new expressions in context are we talking about lisp restarts?

  • geokon 4 years ago

    I asked something similar on the Cider Github and it sounds like it's not possible to get ELisp level debugging:

    https://github.com/clojure-emacs/cider/discussions/2999

    If anything Clojure needs this more than ELisp b/c of the crazy error stacks and b/c of how the laziness makes things blow up in all sorts of weird places

wedesoft 4 years ago

You can just use "(clojure.main/repl)" to start a repl with environment in your code.

Keyboard Shortcuts

j
Next item
k
Previous item
o / Enter
Open selected item
?
Show this help
Esc
Close modal / clear selection