Common Lisp共享结构混乱

Common Lisp shared structure confusion

我正在看《Practical Common Lisp》这本书,在第22章第284页的脚注5中,看到一段代码让我很困惑。

我知道变量list和tail有一个共同的链表结构, 但是我很疑惑既然tail每次迭代都会被赋值为'new',为什么(setf(cdr tail)new)会影响变量列表的状态?

(do ((list nil) (tail nil) (i 0 (1+ i)))
    ((> i 10) list)
  (let ((new (cons i nil)))
    (if (null list)
        (setf list new)
        (setf (cdr tail) new))
    (setf tail new)))

;;; => (0 1 2 3 4 5 6 7 8 9 10)

不变量是 tail 始终是 list 的最后一个 cons 单元格。

在每次迭代中,都会创建一个新的 cons 单元格,并将新值保存为 car。第一次,list 被设置到这个 cons 单元格,tail 也是。在所有后续迭代中,通过将最后一个单元格的 cdr 设置到它,然后将 tail 设置到新单元格,以重新创建不变量来附加新的 cons 单元格。

第一次迭代后:

 [ 0 | nil ]
   ^- list
   ^- tail

秒后:

 [ 0 | -]--->[ 1 | nil ]
   ^- list     ^- tail

第三名:

 [ 0 | -]--->[ 1 | -]--->[ 2 | nil ]
   ^- list                 ^- tail

示例中的 do 表单保留指向尾部缺点的指针,以使将元素附加到列表末尾成为一种廉价操作。否则,需要一直遍历列表以找到最后的缺点——例如使用 appendnconc。另一种方法是在头部和末尾收集新元素以反转结果列表。

人们会期望 LOOP 宏通过将更高级别的循环表达式转换为高效代码来实现一些高效的东西。

宏形式

(loop for i upto 10 collect i)

可能会扩展为与您的 do 示例在内部工作类似的东西。 loop 的优点是你不需要实现跟踪尾巴的逻辑,因为这是宏应该为你做的。