在SBCL中为何LET绑定*print-circle*无效,SETF却能正常打印循环列表?
为什么LET绑定print-circle时打印循环列表会无限挂起?
先看两种不同的执行现象:
正常工作的场景
用setf将全局的*print-circle*设为T后,创建并返回循环列表能正常打印,不会挂起:
CL-USER> (setf *print-circle* t) T CL-USER> (let ((x (list 1))) (setf (cdr x) x) x) #1=(1 . #1#)
出现问题的场景
先把*print-circle*设回nil,再用let临时绑定这个变量,执行后程序会无限挂起:
CL-USER> (setf *print-circle* nil) NIL CL-USER> (let ((*print-circle* t) (x (list 1))) (setf (cdr x) x) x) ;; hangs indefinitely
问题原因
核心是动态绑定的生效时机和REPL打印返回值的时机不匹配:
*print-circle*确实是支持动态绑定的特殊变量,但let对它的绑定仅在let的代码体执行期间有效。当let里的最后一个表达式x执行完成后,let的作用域就结束了,*print-circle*会恢复为原来的nil状态。- Lisp REPL打印返回值的操作,是在
let的作用域之外进行的——此时*print-circle*还是nil,打印循环列表时无法识别循环引用,会无限递归遍历列表,最终导致程序挂起。
而用setf直接修改全局的*print-circle*时,REPL打印返回值的环境中该变量已经是T,所以能正确识别循环引用,生成带标记的输出。
解决方法
如果想让let里的动态绑定在打印时生效,需要把打印操作放到let的代码体内部,确保打印时*print-circle*的绑定仍有效:
CL-USER> (setf *print-circle* nil) NIL CL-USER> (let ((*print-circle* t) (x (list 1))) (setf (cdr x) x) (print x)) #1=(1 . #1#) #1=(1 . #1#)
内容的提问来源于stack exchange,提问作者xdavidliu
相关产品推荐
相关产品推荐

