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

时间:2016-12-01 23:51:25

标签: common-lisp setf

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

(let ((foo (list 42)))
  (setf (rest foo) foo))

当试图执行它时,REPL似乎永远循环。

1 个答案:

答案 0 :(得分:8)

什么是FOO

FOO最初是一个新的列表,(42)。在Lisp中,列表由cons cells表示,可变内存块包含每个CARCDR个插槽。 另一种打印方式是(42 . NIL),其中CARCDR位于点的每一侧。这也可以如下图所示:

  car  cdr
------------
| 42 | NIL |
------------
     ^
     |
    FOO

当您使用(rest foo) 地点foo值拨打SETF时,您说您想要 cdr FOO的单元格,用于保存值FOO。事实上,SETF的这种情况可能会宏观地扩展到对RPLACD的调用中。

------------
| 42 | FOO |
------------
     ^
     |
    FOO

为什么REPL会永远循环?

" 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#