如何让 Sly 的回溯变得合理?
How Can I Make Sly's Backtrace Be Sensible?
每当我遇到错误时,Sly 都会显示重新启动和回溯。我在互联网上看到有人拥有可读的回溯,这些回溯是明智的函数调用。当我在 repl 中输入 (fun duf)
时,我最终得到这样的结果:
Backtrace:
0: ((LAMBDA ()))
1: (SB-INT:SIMPLE-EVAL-IN-LEXENV (FUN DUF) #<NULL-LEXENV>)
2: (EVAL (FUN DUF))
3: ((LAMBDA NIL :IN SLYNK-MREPL::MREPL-EVAL-1))
4: (SLYNK::CALL-WITH-RETRY-RESTART "Retry SLY mREPL evaluation request." #<CLOSURE (LAMBDA NIL :IN SLYNK-MREPL::MREPL-EVAL-1) {100308AA1B}>)
5: ((LAMBDA NIL :IN SLYNK-MREPL::MREPL-EVAL-1))
6: ((LAMBDA NIL :IN SLYNK::CALL-WITH-LISTENER))
7: (SLYNK::CALL-WITH-BINDINGS ((*PACKAGE* . #<PACKAGE "COMMON-LISP-USER">) (* . 13) (** . 10) (***) (/ 13) (// 10) ...) #<CLOSURE (LAMBDA NIL :IN SLYNK::CALL-WITH-LISTENER) {100308A68B}>)
8: (SLYNK-MREPL::MREPL-EVAL-1 #<SLYNK-MREPL::MREPL mrepl-2-2> "(fun duf)")
9: (SLYNK-MREPL::MREPL-EVAL #<SLYNK-MREPL::MREPL mrepl-2-2> "(fun duf)")
...
还有很多。这使我无法调试我的代码。我试过:(declaim (optimize (debug 3)))
,但没有区别。此外,即使调用是实际函数,输出也不会更好。
我已经尝试过 gnuclisp 和 sbcl。
我认为你觉得你看到的很多东西都是“垃圾”,只是因为你还不知道它是什么。
- 在第 9 行和第 8 行,您会看到 REPL 用来处理您的输入的函数。在此阶段,输入是一个字符串,如您所见。
- 在第 7 行,有一些绑定放在堆栈上,以便使用正确的包,并且
*
到 ***
绑定到最后的结果(一个方便的功能REPL).
- 在第 6 行中,
call-with-listener
可能正在确定输出应该转到 Sly。
- 在第 5 行,这个匿名函数是包含在第 6 行作为参数的闭包中的那个。换句话说,一个实现细节。
- 在第 4 行中,
retry
重新启动已建立。如果您在调试器中调用 Retry
重新启动,则此框架将转移控制权。
- 在第 3 行,您再次看到正在执行的函数参数。请注意
call-with-…
命名约定。
- 在第 2 行,您看到您的输入已被 读取,i。 e.转化为 lisp 数据结构。现在使用标准函数
eval
. 对其进行评估
- 在第 1 行中,您会看到 SBCL 在这种情况下是如何做到这一点的——实现细节。
- 在第 0 行,您会看到一些匿名函数,同样是来自
eval
实现的函数。
这里没什么可看的,因为 eval
做的第一件事是评估 duf
,但失败了。
计算机不知道您正在尝试调试当前堆栈上下文的哪一部分。因此,它 必须 向您展示整个回溯,否则就会遭受那些得到他们感兴趣的信息的人的抱怨。
您需要学习的是识别您感兴趣的内容从哪里开始。在这里,所有 Slynk 的东西(显然?)都在您的上下文之下,因此您实际上只需要查看第一行 eval
之前的部分。在这种情况下,这只是第一行,这是有道理的,因为您输入的行在第一次查找时就失败了。对于 Sly 开发人员来说,那些较低的堆栈框架可能很有趣。
当然,在更现实的场景中,有些实现比其他实现具有更清晰的回溯。这不是 Sly 可以做的事情。它所做的是让你可以选择从回溯中的任何一行跳转到相应的源文件位置,如果可用的话(在 SLIME 中,它是 v
(查看),不知道 Sly)。您可以做的还有很多,例如。 G。检查本地 bindings/variables/arguments,并调用重新启动。看看说明书。
每当我遇到错误时,Sly 都会显示重新启动和回溯。我在互联网上看到有人拥有可读的回溯,这些回溯是明智的函数调用。当我在 repl 中输入 (fun duf)
时,我最终得到这样的结果:
Backtrace:
0: ((LAMBDA ()))
1: (SB-INT:SIMPLE-EVAL-IN-LEXENV (FUN DUF) #<NULL-LEXENV>)
2: (EVAL (FUN DUF))
3: ((LAMBDA NIL :IN SLYNK-MREPL::MREPL-EVAL-1))
4: (SLYNK::CALL-WITH-RETRY-RESTART "Retry SLY mREPL evaluation request." #<CLOSURE (LAMBDA NIL :IN SLYNK-MREPL::MREPL-EVAL-1) {100308AA1B}>)
5: ((LAMBDA NIL :IN SLYNK-MREPL::MREPL-EVAL-1))
6: ((LAMBDA NIL :IN SLYNK::CALL-WITH-LISTENER))
7: (SLYNK::CALL-WITH-BINDINGS ((*PACKAGE* . #<PACKAGE "COMMON-LISP-USER">) (* . 13) (** . 10) (***) (/ 13) (// 10) ...) #<CLOSURE (LAMBDA NIL :IN SLYNK::CALL-WITH-LISTENER) {100308A68B}>)
8: (SLYNK-MREPL::MREPL-EVAL-1 #<SLYNK-MREPL::MREPL mrepl-2-2> "(fun duf)")
9: (SLYNK-MREPL::MREPL-EVAL #<SLYNK-MREPL::MREPL mrepl-2-2> "(fun duf)")
...
还有很多。这使我无法调试我的代码。我试过:(declaim (optimize (debug 3)))
,但没有区别。此外,即使调用是实际函数,输出也不会更好。
我已经尝试过 gnuclisp 和 sbcl。
我认为你觉得你看到的很多东西都是“垃圾”,只是因为你还不知道它是什么。
- 在第 9 行和第 8 行,您会看到 REPL 用来处理您的输入的函数。在此阶段,输入是一个字符串,如您所见。
- 在第 7 行,有一些绑定放在堆栈上,以便使用正确的包,并且
*
到***
绑定到最后的结果(一个方便的功能REPL). - 在第 6 行中,
call-with-listener
可能正在确定输出应该转到 Sly。 - 在第 5 行,这个匿名函数是包含在第 6 行作为参数的闭包中的那个。换句话说,一个实现细节。
- 在第 4 行中,
retry
重新启动已建立。如果您在调试器中调用Retry
重新启动,则此框架将转移控制权。 - 在第 3 行,您再次看到正在执行的函数参数。请注意
call-with-…
命名约定。 - 在第 2 行,您看到您的输入已被 读取,i。 e.转化为 lisp 数据结构。现在使用标准函数
eval
. 对其进行评估
- 在第 1 行中,您会看到 SBCL 在这种情况下是如何做到这一点的——实现细节。
- 在第 0 行,您会看到一些匿名函数,同样是来自
eval
实现的函数。
这里没什么可看的,因为 eval
做的第一件事是评估 duf
,但失败了。
计算机不知道您正在尝试调试当前堆栈上下文的哪一部分。因此,它 必须 向您展示整个回溯,否则就会遭受那些得到他们感兴趣的信息的人的抱怨。
您需要学习的是识别您感兴趣的内容从哪里开始。在这里,所有 Slynk 的东西(显然?)都在您的上下文之下,因此您实际上只需要查看第一行 eval
之前的部分。在这种情况下,这只是第一行,这是有道理的,因为您输入的行在第一次查找时就失败了。对于 Sly 开发人员来说,那些较低的堆栈框架可能很有趣。
当然,在更现实的场景中,有些实现比其他实现具有更清晰的回溯。这不是 Sly 可以做的事情。它所做的是让你可以选择从回溯中的任何一行跳转到相应的源文件位置,如果可用的话(在 SLIME 中,它是 v
(查看),不知道 Sly)。您可以做的还有很多,例如。 G。检查本地 bindings/variables/arguments,并调用重新启动。看看说明书。