Stack traces are already the wrong approach. Error handling is first. Stack traces may serve some purpose, but are secondary. Common Lisp was designed for interactive error handling.
An example. Let's say we have a function FAK and it returns the wrong result "0" for 0.
CL-USER 21 > (defun fak (n)
(if (zerop n)
"0"
(* n (fak (1- n)))))
FAK
Now we call it with the argument 10: CL-USER 22 > (fak 10)
Error: In * of (1 "0") arguments should be of type NUMBER.
1 (continue) Return a value to use.
2 Supply a new second argument.
3 (abort) Return to top loop level 0.
Type :b for backtrace or :c <option number> to proceed.
Type :bug-form "<subject>" for a bug report template or :? for other options.
The first thing you see: no stack trace. We get a clear error message and three options what to do: CONTINUE, supply a new argument, ABORT.But we also get another REPL, but this time one level down and the original REPL is still there, one level up. This means we can do some computations in the error REPL:
CL-USER 23 : 1 > (fak 0)
"0"
CL-USER 24 : 1 > (fak 1)
Error: In * of (1 "0") arguments should be of type NUMBER.
1 (continue) Return a value to use.
2 Supply a new second argument.
3 (abort) return to debug level 1.
4 Return a value to use.
5 Supply a new second argument.
6 Return to top loop level 0.
Type :b for backtrace or :c <option number> to proceed.
Type :bug-form "<subject>" for a bug report template or :? for other options.
OOPS: we have another error. (fak 1) already does not work. Now we have more options to continue, while we are another error level deeper: 2.So we decide to go one error level up and explore the problem there further. :c 3 chooses the restart to go to debug level 1.
CL-USER 25 : 2 > :c 3
Now we choose the CONTINUE restart from level 1. We just return 1 from the call, which was causing the error. The value 1 would be the correct result. Lisp now asks us for the value to use and we type 1: CL-USER 26 : 1 > :c 1
Supply a form to be evaluated and used: 1
3628800
So we explored the problem and got useful result without ever using a stack trace. Instead we used the tools: debug repls, clear error messages, restarts recovering from errors.Sure we can also get a stack trace - but the stacktrace is active in the context of the error - it's now a post-error stack trace - it's an in-error stack trace. Means we can, while we are in the error, see the stack trace and use it: change variables, restart start frames, return from stack frames, set break points to stack frames, ...
Let's say we are in the error again. Let's get a quick backtrace:
CL-USER 58 : 1 > :bq
ERROR <- * <- FAK <- FAK <- FAK <- FAK <- FAK <- FAK <- FAK <- FAK <- FAK <- FAK <- EVAL
<- CAPI::CAPI-TOP-LEVEL-FUNCTION <- CAPI::INTERACTIVE-PANE-TOP-LOOP <- MP::PROCESS-SG-FUNCTION
Now we can move in the backtrace down three times: CL-USER 59 : 1 > :n
Call to *
CL-USER 60 : 1 > :n
Interpreted call to FAK
CL-USER 61 : 1 > :n
Interpreted call to FAK
Let's see the variables in this stack frame: CL-USER 62 : 1 > :v
Interpreted call to FAK:
N : 2
Okay N is 2. So FAK from 2 should be 2. Let's try to return it. We call :ret 2, which will return the value 2 from the current stack frame. CL-USER 63 : 1 > :ret 2
3628800
This gave us the correct result. We used more tools: printing a stack trace overview, moving down the stack, looking at a stack frame's bindings and returning a value from a specific stack frame.There are lots of ways to work with a stack trace while we are in the error - the display of the stack trace is only a minor feature.