LISP очень простой вопрос со списком

Я учусь шепелявить, и я новичок в этом, поэтому мне было интересно ...

если я сделаю это:

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

Итак, мой вопрос: какие еще случаи похожи на это в шепелявке, как вы, шепелявцы с черным поясом, знаете, как справляться с этим, что не является интуитивно понятным или непротиворечивым?

Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
3
0
628
6
Перейти к ответу Данный вопрос помечен как решенный

Ответы 6

Функция append должна сделать копию своего первого аргумента, чтобы избежать изменения существующих структур данных. В результате теперь у вас есть два сегмента списка, которые выглядят как (1 2 ...), но являются частью разных списков.

В общем, любой список может быть хвостом любого другого списка, но у вас не может быть единственного объекта списка, который служит главой нескольких списков.

Хм, в первую очередь мы узнаем, как это работает, так что то, что мы воображаем, непоследовательно имеет смысл.

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

После первого параметра def у вас есть список-1, который

(1 . 2 . nil)

в точечной нотации; список-2 - это

(2 . 3 . nil) 

это не то, как выглядит точечная нотация

Luís Oliveira 10.01.2009 20:39

Нет, скорее (1. (2. Nil))

Svante 13.01.2009 04:42

Вы должны думать о списках с точки зрения 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.

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