6 ms·
Debugging in Clojure
- fulafel 5y agoThe 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 https://github.com/dgrnbrg/spyscope#spyp Otherwise great article, I hadn't heard about scope-capture.
- DaveWM 5y agoThanks! 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 5y agoActually i misread the example as using println instead of str because my brain had been primed by println in the headline!
- NightMKoder 5y agoThis 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 https://github.com/gfredericks/debug-repl
- DaveWM 5y agoI'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.
- zurn 5y agoFor those of us using CIDER don't forget #break / #dbg! https://docs.cider.mx/cider/debugging/debugger.html#using-the-debugger https://docs.cider.mx/cider/debugging/debugger.html#using-th...
- geokon 5y agoThat 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 5y agoThis 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.
- geokon 5y agoCould `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
- DaveWM 5y agoAssuming 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 https://github.com/BrunoBonacci/mulog
- geokon 5y agoRight, 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
- fulafel 5y agoI 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.
- slifin 5y agoSome additional conversation on this blog post here: https://www.reddit.com/r/Clojure/comments/oe40af/debugging_in_clojure_dave_martins_blog/?sort=confidence https://www.reddit.com/r/Clojure/comments/oe40af/debugging_i...
- phoe-krk 5y agoFor 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/ https://github.com/IGJoshua/farolero/
- DaveWM 5y agoNice!
- brokenkebab 5y agoElephant 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.
- DaveWM 5y agoSayid 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
- billfruit 5y agoDoes 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?
- DaveWM 5y agoCursive'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 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 5y agoThat 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.
- DaveWM 5y agoAh 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 5y agoCursive'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 5y agoI 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 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
- tankfeeder 5y agoDebugging in PicoLisp https://envs.net/~mpech/tut.html#dbg https://envs.net/~mpech/tut.html#dbg
- wedesoft 5y agoYou can just use "(clojure.main/repl)" to start a repl with environment in your code.