Странное поведение, вызывающее деструктивную функцию 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)))

НЕПРАВИЛЬНО: локальная переменная - это список, созданный с помощью специального оператора 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)))

or

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

Кто-нибудь знает причину такого странного поведения?


person Paulo Tomé    schedule 03.06.2013    source источник
comment
во втором test2 вы setf переменную l, что совершенно нормально. В вашем первоначальном insert-at-pos определении вы setf место, часть структуры cons-узла, которая является запрещенной. (setf (rest aux-list) ... переводится в RPLACD звонок.   -  person Will Ness    schedule 03.06.2013
comment
возможный дубликат Неожиданное постоянство данных   -  person Joshua Taylor    schedule 09.09.2014


Ответы (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))

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

person Rainer Joswig    schedule 03.06.2013