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