【问题标题】:SETF neither terminates nor reports an errorSETF 既不终止也不报告错误
【发布时间】:2017-04-16 18:57:56
【问题描述】:

我是一名 Common Lisp 初学者,偶然发现了这段代码:

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

REPL 在尝试执行时似乎永远循环。

【问题讨论】:

    标签: common-lisp setf


    【解决方案1】:

    什么是FOO

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

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

    当您使用(rest foo) placefoo 值调用SETF 时,您是说您希望FOOcdr 单元格保持值FOO。事实上,SETF 的出现很可能会宏扩展为对RPLACD 的调用。

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

    为什么 REPL 永远循环?

    “REPL”(打印)的“P”部分尝试打印您的循环结构。这是因为SETF 的值是从被评估的表单返回的值,而SETF 返回的值是它的第二个参数的值,即FOO。想象一下,你想用一个简单的算法编写一个 cons cell 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# 本身。

    【讨论】:

    • 漂亮的答案!
    猜你喜欢
    • 2014-02-14
    • 1970-01-01
    • 1970-01-01
    • 2018-07-01
    • 2019-12-26
    • 2013-07-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多