[HN Gopher] Debugging Lisp: trace options, break on conditions
       ___________________________________________________________________
        
       Debugging Lisp: trace options, break on conditions
        
       Author : akkartik
       Score  : 85 points
       Date   : 2023-01-04 22:25 UTC (2 days ago)
        
 (HTM) web link (lisp-journey.gitlab.io)
 (TXT) w3m dump (lisp-journey.gitlab.io)
        
       | zrkrlc wrote:
       | I wonder what it would take to bring these things to Clojure. Its
       | REPL experience is miles ahead of non-lispy languages but I do
       | feel a pang of grass-is-greener whenever I hear about CL's
       | debugging tooling. Hell, the JVM has a great debugger as well (or
       | so I hear), so why is that difficult to port over?
        
         | redpenguin101 wrote:
         | I would love to know the answer to this! It sounds like
         | something that would be both possible and very useful. I assume
         | there's a good reason why it can't be done based on how the
         | Clojure / JVM execution model works, but I don't know nearly
         | enough about it to hazard a guess.
        
         | simongray wrote:
         | You can just use e.g. Intellij's debugger with Clojure? What
         | exactly do you need ported?
         | 
         | Personally, I don't really see the value though. I prefer to
         | create modules of mostly pure functions, testing them in the
         | REPL using Rich comment blocks.
        
           | billfruit wrote:
           | One thing is that when an unhandled exception occurs the
           | clojure program is terminated, where as lisps usually does
           | allow one to restart the program at the point of exception
           | after examining what caused the exception and changing the
           | values of local variables to rectify that. Can the intellij
           | debugger do that?
        
         | fulafel wrote:
         | There's some good debugging tooling for Clojure as well. A
         | recent entrant is https://github.com/jpmonettas/flow-storm-
         | debugger and of course there's the estabilished pretty full
         | featured debugging features in CIDER [1](Emacs), Calva [2] (VS
         | Code) and Cursive (IntelliJ, using std Java JDI debugging). And
         | for barebones tracing from REPL there's goo old
         | clojure.tools.trace. And a bunch of others (sayid, postmortem,
         | cljs-devtools for ClojureScript together with browser debugger,
         | etc)
         | 
         | What CL has over Clojure is mostly the condition system I
         | think.
         | 
         | [1] https://docs.cider.mx/cider/debugging/debugger.html
         | 
         | [2] https://calva.io/debugger/
        
         | sph wrote:
         | Same, I've been torn between learning Clojure--which I think is
         | brilliantly designed, but I dislike its reliance on the JVM and
         | exceptions vs conditions/restarts--and Common Lisp, that comes
         | with a standard library that feels older than POSIX and C, but
         | a REPL experience that is unrivalled and decades ahead any
         | compiled language.
         | 
         | I decided to go with Common Lisp for its REPL and inside-out
         | development experience, and just hope there's someone that will
         | eventually take Clojure and CL, and fuse the two together.
         | 
         | The more I dive into Lisp and Smalltalk, the more I am
         | convinced we are in the stone age of computing. We have reached
         | the local maximum of UNIX and the basic abstractions over
         | machine code we call compiled languages.
        
       | vindarel wrote:
       | Related to debugging, the follow up article: how to fix and
       | resume a program from any point in the stack. Have the debugger
       | pop-up, read the error message, go to the erroneous line ("v"),
       | fix the code, compile the function (C-c C-c), come back to the
       | debugger, press "r" on any point in the stack, see the program
       | succeed. You did not have to re-run everything from zero.
       | 
       | https://lisp-journey.gitlab.io/blog/debugging-lisp-fix-and-r...
       | 
       | https://www.youtube.com/watch?v=jBBS4FeY7XM
       | 
       | and some more: https://lispcookbook.github.io/cl-
       | cookbook/debugging.html
        
       | _mhr_ wrote:
       | Slightly OT, but when you edit code in the REPL this way using
       | conditions/restarts, how do you avoid getting your code out of
       | sync with the REPL's state? It seems with a long-running REPL,
       | you eventually run into:
       | 
       | - Out of order execution means we don't know what order our code
       | should be run in to achieve the current state
       | 
       | - If we run some code in the REPL and never save it in a file,
       | then our state is also out of sync
       | 
       | - Finally, if we redefine some code, there's no way the state can
       | be in sync with the code if started from the beginning
       | 
       | How do Common Lispers typically deal with these issues for a REPL
       | that's been running for days or weeks, short of just saving the
       | image and never turning it off?
        
         | taeric wrote:
         | This is true of any image based development, as well. You can
         | get the same thing in Java with hotswapping code, even.
         | 
         | That is to say, this is a bit of a concern, but isn't typically
         | as large of one as you'd think. There are plenty of ways to
         | make it so that you can't reason about a program. In general,
         | you avoid doing those things.
         | 
         | Specifically to your question, I think, the biggest trick is
         | that you rarely use the REPL as where you type your code. For
         | that, you typically still use files and eval the file into the
         | environment controlled by a repl. Even in emacs, you rarely
         | just execute elisp from the scratch buffer. Unless you know it
         | is something you don't care to keep, of course. Instead, you
         | are working with files and evaluate the file on a regular
         | basis.
        
       | pfdietz wrote:
       | Note that the extra arguments to trace are not defined in the
       | standard, but are allowed by the standard. They are
       | implementation specific.
       | 
       | You can also get something like arbitrary computation at function
       | calls using :around methods.
        
       | gibsonf1 wrote:
       | I'm a very big fan of using sbcl's (sb-profile:profile function1
       | function2) (sb-profile:report) and (sb-profile:reset) and finally
       | (sb-profile:unprofile)
       | 
       | For large production systems that may be running some functions
       | millions to billions of times during a given process, the
       | profiler is amazing for reporting how many times each function
       | ran, how long each run took and the ranking from top to bottom of
       | slowest to fastest enabling finding and fixing the slowest
       | functions and achieving incredible application efficiency gains.
        
       | superdisk wrote:
       | I like Lisp but I've had one perpetual problem with debugging it
       | that makes me feel like I'm doing something wrong. Once I've hit
       | a breakpoint, and SLIME or whatever presents its options, HOW can
       | I get into a REPL in the _context_ of the stack frame, so I can
       | eval expressions that reference variables in scope there? The
       | best I can find is pressing `e` over the stack frame to evaluate
       | one expression, but that 's garbage compared to the full repl
       | (and hotkeys that send expressions to it like C-c C-e).
       | 
       | I feel like there must be some way to do this that I just haven't
       | found yet.
        
         | lispm wrote:
         | That's SLIME specific. SBCL's own REPL has it.
         | * (defun foo (x)           (declare (optimize (debug 3)))
         | (let ((n 10))             (break)             (* x n)))
         | FOO            * (foo 3)   ; calling foo       (foo 3)
         | 
         | We get a break repl:                 debugger invoked on a
         | SIMPLE-CONDITION            restarts (invokable by number or by
         | possibly-abbreviated name):         0: [CONTINUE] Return from
         | BREAK.         1: [ABORT   ] Exit debugger, returning to top
         | level.            (FOO 3)          source: (BREAK)
         | 
         | Let's change the local variable N of the running function FOO:
         | 0] (setf n 12)       (setf n 12)            12
         | 
         | Return from the break:                 0] 0          ; restart
         | 0       0       36
         | 
         | Note that you may want to reduce the optimization level of
         | SBCL, since by default everything is compiled, and its
         | optimizing compiler might get rid of a few of the variables
         | during compilation. For example the compiler might detect that
         | N's literal value 10 is not changed in the function and replace
         | the variable access to N with the compile-time value. That's
         | why I used the DEBUG quality 3 (highest) to tell the compiler
         | to not optimize the code.
        
           | kagevf wrote:
           | > We get a break repl:
           | 
           | How? I ran the same function, brought up the debugger, if I
           | try to type something emacs says the buffer is read-only. Was
           | I supposed to push e first?
        
             | lispm wrote:
             | SBCL's REPL. Outside GNU Emacs.
        
               | kagevf wrote:
               | ah, got it! and was able to reproduce.
               | 
               | This will be something to keep in mind whenever trying
               | out different REPL offerings outside of emacs / slime ...
               | thank you for the tip!
        
           | superdisk wrote:
           | Fair enough, and I've done it just in SBCL like that before,
           | but everyone always sings the praises of Lisp's (+ SLIME's)
           | amazing debugging, and without the ability to stop at a
           | certain stack frame and explore, I feel like I'm debugging
           | with a blindfold on at all times.
        
             | lispm wrote:
             | one of the reasons SLIME is not my favorite dev environment
        
         | taeric wrote:
         | I suspect the main blocker here is that it wouldn't know which
         | "frame" to eval in. Since, from the debugger, you can eval in
         | any frame on the stack?
        
         | vindarel wrote:
         | I think you are doing it right, I don't know if it's possible
         | to get a REPL in the context of the stack frame either. It
         | would be handy and the workflow would resemble other
         | ecosystems'.
        
       | mickeyp wrote:
       | Emacs, of course, can also do this for its own code with `trace-
       | function-[background/foreground]' in addition to its usual
       | `edebug' feature.
        
       | distcs wrote:
       | This was a nice read! Thanks for sharing OP! Need more of these
       | articles. Practical HOWTOs like this go much further along in
       | convincing people to use Lisp than plain advocacy which more
       | often than not rubs people the wrong away. If there are more
       | articles like this that simply show what can be done and how it
       | can be done there is not going to be a need for advocacy. A good
       | HOWTO is worth a thousand words of advocacy!
        
       ___________________________________________________________________
       (page generated 2023-01-06 23:01 UTC)