Я учусь шепелявить, и я новичок в этом, поэтому мне было интересно ...
если я сделаю это:
(defparameter *list-1* (list 1 2))
(defparameter *list-2* (list 2 3))
(defparameter *list-3* (append *list-1* *list-2*))
А потом
(setf (first *list-2*) 1)
*list-3*
Я получу (1 2 1 4)
Я знаю, что это связано с тем, что добавление будет «экономить ресурсы» и создавать новый список для первого фрагмента, но на самом деле будет просто указывать на второй фрагмент, потому что если я это сделаю:
(setf (first *list-1*) 0)
*list-3*
Я получу (1 2 1 4) вместо более логичного (0 2 1 4)
Итак, мой вопрос: какие еще случаи похожи на это в шепелявке, как вы, шепелявцы с черным поясом, знаете, как справляться с этим, что не является интуитивно понятным или непротиворечивым?





Функция append должна сделать копию своего первого аргумента, чтобы избежать изменения существующих структур данных. В результате теперь у вас есть два сегмента списка, которые выглядят как (1 2 ...), но являются частью разных списков.
В общем, любой список может быть хвостом любого другого списка, но у вас не может быть единственного объекта списка, который служит главой нескольких списков.
Хм, в первую очередь мы узнаем, как это работает, так что то, что мы воображаем, непоследовательно имеет смысл.
Что вам нужно сделать, так это найти несколько устаревших диаграмм блоков и указателей, которые я не могу легко нарисовать, но давайте разберемся.
После первого параметра def у вас есть список-1, который
(1 . 2 . nil)
в точечной нотации; список-2 - это
(2 . 3 . nil)
Нет, скорее (1. (2. Nil))
Вы должны думать о списках с точки зрения cons-ячеек. Когда вы определяете список 1 и список 2, это похоже на:
(defparameter *list-1* (cons 1 (cons 2 nil)))
(defparameter *list-2* (cons 2 (cons 3 nil)))
Затем, когда вы добавите:
(defparameter *list-3* (cons 1 (cons 2 *list-2*)))
По сути, cons-ячейка состоит из двух частей; значение (автомобиль) и указатель (cdr). Append определено так, чтобы не изменять первый список, поэтому он копируется, но затем последний cdr (обычно nil) изменяется так, чтобы указывать на второй список, а не на копию второго списка. Если бы вы хотели уничтожить первый список, вы бы использовали nconc.
Попробуй это:
(defparameter *list-3* (nconc *list-1* *list-2*))
Затем обратите внимание на значение *list-1*, оно равно (1 2 2 3), как и *list-3*.
Общее правило состоит в том, что неразрушающие функции (append) не будут уничтожать существующие данные, в то время как деструктивные функции (nconc) будут. Однако то, что делает будущая деструктивная функция ((setf cdr)), не является обязанностью первой неразрушающей функции.
Одна из защитных тактик - избегать разделения структуры.
(defparameter *list-3* (append *list-1* *list-2* '()))
или же
(defparameter *list-3* (append *list-1* (copy-list *list-2*)))
Теперь структура нового *list-3* совершенно новая, и модификации *list-3* не повлияют на *list-2* и наоборот.
So my question is, what other cases are like this in lisp, how do you black belt lispers know how to deal with this stuff that is not intuitive or consistent?
Читая прекрасное руководство? Гиперпсек явно указывает:
[...] the list structure of each of lists except the last is copied. The last argument is not copied; it becomes the cdr of the final dotted pair of the concatenation of the preceding lists, or is returned directly if there are no preceding non-empty lists.
Цитировать:
So my question is, what other cases are like this in lisp, how do you black belt lispers know how to deal with this stuff that is not intuitive or consistent?
Я думаю, что вы здесь немного суровы с предметом, который намного больше, чем вы можете себе представить. Списки - довольно сложная концепция в Лиспе, и вам нужно понимать, что это не какой-то простой массив. Стандарт предоставляет много функций для управления списками любым способом. В этом случае вам нужно:
(concatenate 'list *list-1* *list-2*)
Итак, почему есть еще append? Что ж, если вы можете не копировать последний список, а все задействованные символы по-прежнему возвращают правильные данные, это может значительно повысить производительность как при расчете времени, так и при использовании памяти. append особенно полезен в стиле функционального программирования, не использующем побочные эффекты.
Вместо дальнейшего объяснения cons-ячеек, деструктивных и неразрушающих функций и т. д. Я укажу вам хорошее введение: Практический Common Lisp, гл. 12, а для полной справки, Common Lisp Hyperspec, посмотрите главы 14 и 17.
это не то, как выглядит точечная нотация