奇怪的行为调用破坏性的公共LISP函数接收使用quote创建的列表作为参数

时间:2013-06-03 08:53:10

标签: list lisp common-lisp quote

我在调用破坏性定义时遇到一种奇怪的行为,接收一个局部变量作为参数,该变量的类型是用list创建的quote

破坏性功能:

(defun insert-at-pos (pos list elem)
  (if (= pos 0)
      (cons elem list)
      (let ((aux-list (nthcdr (1- pos) list)))
        (setf (rest aux-list) (cons elem (rest aux-list)))
        list)))

错误:局部变量是使用特殊运算符quote创建的列表。

(defun test ()
 (let ((l '(1 2 3)))
   (print l)
   (insert-at-pos 2 l 4)
   (print l))) 

> (test)

(1 2 3)
(1 2 4 3)
(1 2 4 3)

> (test)

(1 2 4 3)
(1 2 4 4 3)
(1 2 4 4 3)

> (test)

(1 2 4 4 3)
(1 2 4 4 4 3)
(1 2 4 4 4 3) 

CORRECT :局部变量是使用函数list创建的列表。

(defun test2 ()
 (let ((l (list 1 2 3)))
   (print l)
   (insert-at-pos 2 l 4)
   (print l)))

(defun test2 ()
 (let ((l '(1 2 3)))
   (print l)
   (setf l (cons (first l) (cons (second l) (cons 4 (nthcdr 2 l)))))
   (print l)))

> (test2)

(1 2 3)
(1 2 4 3)
(1 2 4 3)

> (test2)

(1 2 3)
(1 2 4 3)
(1 2 4 3)

> (test2)

(1 2 3)
(1 2 4 3)
(1 2 4 3)

有人知道这种奇怪行为的原因吗?

1 个答案:

答案 0 :(得分:8)

如果在函数中引用数据,则它是文字数据。在Common Lisp标准中,破坏性地修改这些文字数据的效果是不确定的。在您的示例中,所有函数调用共享相同的文字数据,并且实现不会警告您正在更改它。这就是大多数实现所做的。但是也可以想象一个实现将所有代码(及其文字数据)放入内存的只读部分。

你可以用这个来获得时髦的效果。

如果要破坏性地修改列表而不遇到潜在问题,则需要在运行时创建一个新副本。例如,通过调用LISTCOPY-LISTLIST将返回一份新的精选列表。

存在类似的陷阱。例如,想象一下具有以下定义的文件:

(defvar *foo* '(1 2 3 4 5 6 ... 10000))

(defvar *foo* '(0 1 2 3 4 5 6 ... 10000))

如果使用文件编译器编译这样的文件,则允许编译器创建编译文件,其中两个变量共享文字数据 - 节省空间。如果要更改任一列表中的元素,则两者都可能会更改。