1

タイプが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) 

: ローカル変数は関数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)

誰かがこの奇妙な動作の理由を知っていますか?

4

1 に答える 1

8

関数内でデータを引用する場合、それはリテラル データです。このようなリテラル データを破壊的に変更した場合の影響は、Common Lisp 標準では定義されていません。あなたの例では、すべての関数呼び出しが同じリテラルデータを共有しており、実装はそれを変更していることを警告しません。それがほとんどの実装で行われていることです。しかし、すべてのコード (およびそのリテラル データ) をメモリの読み取り専用部分に配置する実装を想像することもできます。

これでファンキーな効果が得られます。

潜在的な問題に遭遇することなくリストを破壊的に変更したい場合は、実行時に新しいコピーを作成する必要があります。たとえば、LISTまたはを呼び出しCOPY-LISTます。LIST新しいconsedリストを返します。

似たような落とし穴があります。たとえば、次の定義を持つファイルを想像してください。

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

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

このようなファイルをファイル コンパイラでコンパイルすると、コンパイラは、2 つの変数がリテラル データを共有するコンパイル済みファイルを作成できるため、スペースを節約できます。いずれかのリストの要素を変更すると、両方が変更される可能性があります。

于 2013-06-03T09:11:37.220 に答える