我是Common Lisp的初学者,遇到了这段代码:
(let ((foo (list 42)))
(setf (rest foo) foo))
Run Code Online (Sandbox Code Playgroud)
在尝试执行它时,REPL似乎永远循环.
FOO?FOO最初是一个新的清单,(42).在Lisp中,列表由cons单元表示,包含每个a CAR和CDRslot 的可变内存块.打印出来的另一种方法是(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,其中CAR42 CDR是#1#自身.