В common-lisp, как изменить часть параметра списка из функции без изменения исходного списка?

Я пытаюсь передать список функции в Lisp и изменить содержимое этого списка внутри функции, не затрагивая исходный список. Я читал, что шепелявость-это переходная ценность, и это правда, но есть еще кое-что, что я не совсем понимаю. Например, этот код работает так, как ожидалось:

(defun test ()
    (setf original '(a b c))
    (modify original)
    (print original))
(defun modify (n)
    (setf n '(x y z))
    n)

Если вы вызываете (test), он печатает (A b c), хотя (modify) возвращает (x y z).

однако это не работает, если вы пытаетесь изменить только часть списка. Я предполагаю, что это имеет какое-то отношение к спискам, которые имеют одинаковое содержимое, одинаковое в памяти везде или что-то в этом роде? Вот пример:

(defun test ()
    (setf original '(a b c))
    (modify original)
    (print original))
(defun modify (n)
    (setf (first n) 'x)
    n)

затем (тест) печатает (x b c). Итак, как изменить некоторые элементы параметра списка в функции, как если бы этот список был локальным для этой функции?

4 ответов


SETF изменяетместо. n может быть место. Первый элемент списка n точки также могут быть местом.

в обоих случаях в перечне original перешло к modify в качестве параметра n. Это означает, что оба original функции test и n функции modify Теперь провести тот же список, что означает, что оба original и n теперь на его первый элемент.

после изменения SETF n в первом случае он больше не указывает на этот список, а на новый список. Список, на который указывает original не влияет. Затем новый список возвращается modify, но так как это значение ничему не присваивается, оно исчезает из существования и вскоре будет собираться мусор.

во втором случае SETF изменяет not n, но первый элемент списка n указывает. Это тот же список original указывает на, Поэтому после этого вы также можете увидеть измененный список через эту переменную.

чтобы скопировать список, используйте КОПИРОВАТЬ-СПИСОК.


списки Lisp основаны на ячейках минусов. Переменные подобны указателям на ячейки минусов (или другие объекты Lisp). Изменение переменной не изменит другие переменные. Изменение ячеек минусов будет видно во всех местах, где есть ссылки на эти ячейки минусов.

хорошая книга, Touretzky, Common Lisp: нежное введение в символические вычисления.

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

если вы проходите перечислите такую функцию:

(modify (list 1 2 3))

тогда у вас есть три разных способа использовать список:

деструктивные изменения клеток против

(defun modify (list)
   (setf (first list) 'foo)) ; This sets the CAR of the first cons cell to 'foo .

совместное использование структуры

(defun modify (list)
   (cons 'bar (rest list)))

выше возвращает список, который разделяет структуру с переданным в списке: остальные элементы одинаковы в обоих списках.

копирование

(defun modify (list)
   (cons 'baz (copy-list (rest list))))

над функцией баз подобен бару, но ячейки списка не являются общими, так как список копируется.

Излишне говорить, что деструктивной модификации часто следует избегать, если нет реальной причины делать это (например, сохранять память, когда это того стоит).

Примечания:

никогда деструктивно не изменять списки литеральных констант

Dont ' do: (let ((l '(A b c))) (setf (первый l) 'bar))

причина: список может быть защищен от записи или может совместно использоваться с другими списками (аранжировано компилятором), etc.

также:

вводим переменные

такой

(let ((original (list 'a 'b 'c)))
   (setf (first original) 'bar))

или такой

(defun foo (original-list)
   (setf (first original-list) 'bar))

никогда не устанавливайте неопределенную переменную.


это почти то же самое, что и в этом примере в C:

void modify1(char *p) {
    p = "hi";
}

void modify2(char *p) {
    p[0] = 'h';
}

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


У вас, вероятно, есть проблемы, потому что, хотя Lisp является пропуском по значению, ссылки на объекты передаются, как в Java или Python. Ваши ячейки минусов содержат ссылки, которые вы изменяете, поэтому вы изменяете как исходный, так и локальный.

IMO, вы должны попытаться написать функции в более функциональном стиле, чтобы избежать таких проблем. Хотя общий Лисп является мульти-парадигмы функциональный стиль является более подходящим способом.

(defun изменить (n) (минусы x (cdr n))