我是Common Lisp的初学者,并且遇到了这段代码:
(let ((foo (list 42)))
(setf (rest foo) foo))
当试图执行它时,REPL似乎永远循环。
答案 0 :(得分:8)
FOO
? FOO
最初是一个新的列表,(42)
。在Lisp中,列表由cons cells表示,可变内存块包含每个CAR
和CDR
个插槽。
另一种打印方式是(42 . NIL)
,其中CAR
和CDR
位于点的每一侧。这也可以如下图所示:
car cdr
------------
| 42 | NIL |
------------
^
|
FOO
当您使用(rest foo)
地点和foo
值拨打SETF
时,您说您想要 cdr FOO
的单元格,用于保存值FOO
。事实上,SETF
的这种情况可能会宏观地扩展到对RPLACD
的调用中。
------------
| 42 | FOO |
------------
^
|
FOO
" P" " REPL"的一部分(打印)尝试打印您的圆形结构。这是因为SETF
的值是从被评估的表单返回的值,SETF
返回的值是其第二个参数的值,即FOO
。想象一下,你想用一个天真的算法写一个cons单元格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 Equal-Sign"符号允许读者将表单的一部分影响到(读者)变量,例如#1=...
,并在之后重复使用,例如#1#
。这使得可以在读取或打印期间表示数据之间的圆形交叉引用。在这里,我们可以看到变量#1#
表示一个cons-cell,CAR
为42,CDR
本身为#1#
。