SETF既不会终止也不会报告错误

hay*_*rdi 1 common-lisp setf

我是Common Lisp的初学者,遇到了这段代码:

(let ((foo (list 42)))
  (setf (rest foo) foo))
Run Code Online (Sandbox Code Playgroud)

在尝试执行它时,REPL似乎永远循环.

cor*_*ump 8

什么是FOO

FOO最初是一个新的清单,(42).在Lisp中,列表由cons单元表示,包含每个a CARCDRslot 的可变内存块.打印出来的另一种方法是(42 . NIL),在CARCDR上点的两侧.这也可以描绘如下:

  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会永远循环?

"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#自身.