Странное поведение, вызывающее деструктивную функцию 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))
Если вы скомпилируете такой файл с помощью файлового компилятора, то компилятору будет разрешено создать скомпилированный файл, в котором две переменные совместно используют литеральные данные - экономя место. Если вы измените элемент в любом списке, оба могут быть изменены.