Странное поведение, вызывающее деструктивную функцию Common LISP, получающую в качестве аргумента список, созданный с кавычками

Я получаю странное поведение, когда вызываю деструктивное определение, получая в качестве аргумента локальную переменную, тип которой представляет собой список, созданный с кавычкой.

Деструктивная функция:

(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)))

НЕПРАВИЛЬНО: Локальная переменная - это список, созданный со специальной кавычкой оператора.

(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) 

ПРАВИЛЬНО: Локальная переменная - это список, созданный с помощью списка функций.

(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 ответ

Решение

Если вы цитируете данные в функции, то это буквальные данные. Эффекты деструктивной модификации таких литеральных данных не определены в стандарте Common Lisp. В вашем примере все вызовы функций используют одни и те же литеральные данные, и реализация не предупреждает вас о том, что вы их меняете. Это то, что делают большинство реализаций. Но также возможно представить реализацию, которая помещает весь код (и его буквальные данные) в доступную только для чтения часть памяти.

Вы можете получить эффектные эффекты с этим.

Если вы хотите деструктивно изменить список, не сталкиваясь с потенциальными проблемами, то вам нужно создать свежую копию во время выполнения. Например, позвонив LIST или же COPY-LIST, LIST вернет новый список.

Есть похожие подводные камни. Например, представьте файл со следующими определениями:

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

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

Если вы скомпилируете такой файл с помощью файлового компилятора, то компилятору будет разрешено создать скомпилированный файл, в котором две переменные совместно используют литеральные данные - экономя место. Если вы измените элемент в любом списке, оба могут быть изменены.

Другие вопросы по тегам