我是一个 Common Lisp 初学者,遇到了这段代码:
(let ((foo (list 42)))
(setf (rest foo) foo))
REPL 在尝试执行时似乎只是永远循环。
我是一个 Common Lisp 初学者,遇到了这段代码:
(let ((foo (list 42)))
(setf (rest foo) foo))
REPL 在尝试执行时似乎只是永远循环。
FOO
?FOO
最初是一个新列表,(42)
. 在 Lisp 中,列表由cons cellsCAR
表示,即包含每个 a和一个CDR
slot的可变内存块。另一种打印方式是(42 . NIL)
,CAR
和CDR
位于点的每一侧。这也可以如下图所示:
car cdr
------------
| 42 | NIL |
------------
^
|
FOO
当您SETF
使用(rest foo)
位置和foo
值调用时,您是说您希望cdr单元格FOO
保存该值FOO
。事实上,这个 的出现SETF
很可能会宏扩展为对 的调用RPLACD
。
------------
| 42 | FOO |
------------
^
|
FOO
“REPL”(打印)的“P”部分尝试打印您的圆形结构。这是因为SETF
' 的值是从被评估的表单返回的值,而返回SETF
的值是其第二个参数的值,即FOO
。想象一下,你想用一个简单的算法编写一个 cons cell X:
1. PRINT "("
2. PRINT the CAR of X
3. PRINT " . "
4. PRINT the CDR of X
5. PRINT ")"
但是,对于foo
,第 4 步将递归打印相同的结构,并且永远不会终止。
首先尝试设置*PRINT-CIRCLE*
为 T:
(setf *print-circle* t)
现在,您的 REPL 应该很高兴:
CL-USER> (let ((foo (list 42)))
(setf (rest foo) foo))
#1=(42 . #1#)
“ Sharpsign 等号”表示法允许读者将表单的一部分影响到(读者)变量,例如#1=...
,并在之后重用它,例如#1#
。这使得在读取或打印期间表示数据之间的循环交叉引用成为可能。在这里,我们可以看到变量#1#
表示一个 cons-cell,其中CAR
是 42 而CDR
是#1#
本身。