常见的Lisp共享结构混乱

时间:2019-05-17 10:45:38

标签: common-lisp practical-common-lisp

我正在阅读《实用的通用Lisp》一书,在第22章的脚注5(第284页)中,我看到了一个代码片段,使我感到困惑。

我知道变量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)

2 个答案:

答案 0 :(得分:3)

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

在每次迭代中,都会创建一个新的con单元格,并将新值保存为car。第一次通过list设置为此cons单元格,并且tail也设置为该状态。在所有后续迭代中,通过将最后一个单元格的cdr设置为新的cons单元格,然后将tail设置为新的单元格来重新创建不变式。

第一次迭代后:

 [ 0 | nil ]
   ^- list
   ^- tail

第二秒后:

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

第三:

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

答案 1 :(得分:2)

在您的示例中,do形式保留了指向尾部弊端的指针,从而使在列表末尾添加元素的操作变得廉价。否则,将需要一直遍历列表以找到最后一个缺点,例如使用appendnconc。另一种方法是在头部和末端收集新元素以反转结果列表。

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

宏形式

(loop for i upto 10 collect i)

可能会扩展为内部工作方式,类似于您的do示例。 loop的优点是您不需要实现跟踪尾巴的逻辑,因为这就是宏应该为您执行的操作。