Debugging Lisp: trace options, break on conditions
lisp-journey.gitlab.io
lisp-journey.gitlab.io
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
I feel like there must be some way to do this that I just haven't found yet.
* (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.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?
This will be something to keep in mind whenever trying out different REPL offerings outside of emacs / slime ... thank you for the tip!
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.
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.
What CL has over Clojure is mostly the condition system I think.
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.
https://www.gnu.org/software/emacs/manual/html_node/elisp/Br...
You can also get something like arbitrary computation at function calls using :around methods.
- 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?
And we have the right to restart the image. For example, I run the tests from the terminal, hence from scratch, from time to time, typically before pushing my commits. I have a CI that tests and builds the program. I deploy a static build. We don't use images coming from development here. However, I can connect to a running image in prod and tweak settings if I want, or just look around[]. I could very easily change the code, but I'll do that in my sources and do a clean deploy. Or not. I too heard about people who mold a running image for years, their sources totally out of sync O_o
[]: there's a trading startup that posts screenshots of their Sly REPL from prod, that's where they ask their system for data. They didn't have to setup another complex layer just to see data from prod.
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.
whatever you typed, extract a patch and send it up to your editor for faster reconciliation