Странный Lisp сценарий котировки - Graham On Lisp, стр. 37

Я прокладываю себе путь через книгу Грэма "В Lisp" и не могу понять следующий пример на стр. 37:

If we define exclaim so that its return value
incorporates a quoted list,

(defun exclaim (expression)
(append expression ’(oh my)))

>  (exclaim ’(lions and tigers and bears))
(LIONS AND TIGERS AND BEARS OH MY)
> (nconc * ’(goodness))
(LIONS AND TIGERS AND BEARS OH MY GOODNESS)

could alter the list within the function:

> (exclaim ’(fixnums and bignums and floats))
(FIXNUMS AND BIGNUMS AND FLOATS OH MY GOODNESS)

To make exclaim proof against such problems, it should be written:
(defun exclaim (expression)
(append expression (list ’oh ’my)))

Кто-нибудь понимает, что происходит здесь? Это серьезно связано с моей ментальной моделью того, что цитирует.

Ответы

Ответ 1

nconc - деструктивная операция, которая изменяет свой первый аргумент, изменяя его хвост. В этом случае это означает, что список констант '(oh my) получает новый хвост.

Мы надеемся сделать это более ясным. Это примерно так:

; Hidden variable inside exclaim
oh_my = oh → my → nil

(exclaim '(lions and tigers and bears)) =
    lions → and → tigers → and → bears → oh_my

(nconc * '(goodness)) destructively appends goodness to the last result:
    lions → and → tigers → and → bears → oh → my → goodness → nil
so now, oh_my = oh → my → goodness → nil

Замена '(oh my) на (list 'oh 'my) исправляет это, потому что больше нет константы, разделяемой всеми и вся. Каждый вызов exclaim создает новый список (цель list в жизни - создавать новые списки).

Ответ 2

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

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

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

Рассмотрим форму (QUOTE x), где x - некоторое текстовое представление объекта. Это может быть символьный литерал, как в (QUOTE ABC), литерал списка, как в (QUOTE (A B C)), строковый литерал, как в (QUOTE "abc"), или любой другой литерал. На этапе чтения читатель будет читать форму в виде списка (назовите его form1), первым элементом которого является символ QUOTE, а вторым элементом является объект x, текстовое представление которого равно x. Обратите внимание, что я специально говорю, что объект x 'хранится в списке, который представляет выражение, то есть в некотором смысле он хранится как часть самого кода.

Теперь он повернулся. Вклад оценщика является form1, который является списком. Таким образом, он смотрит на первый элемент формы1, и, определив, что это символ QUOTE, , он возвращает в результате оценки второй элемент списка. Это ключевой момент. Оценщик возвращает второй элемент списка для оценки, который читатель читает на первом этапе выполнения (до компиляции!). Это все, что он делает. Там нет волшебства, это очень просто и значительно, никаких новых объектов не создаются, и никакие существующие не копируются.

Следовательно, всякий раз, когда вы изменяете "цитируемый список", вы изменяете сам код. Самомодифицирующийся код - очень запутанная вещь, и в этом случае поведение на самом деле undefined (поскольку ANSI Common Lisp позволяет реализациям помещать код в постоянную память).

Конечно, вышесказанное является всего лишь ментальной моделью. Реализации могут свободно реализовывать модель по-разному, и на самом деле я не знаю реализации Common Lisp, который, как и мое объяснение, вообще не компилирует. Тем не менее, это основная идея.

Ответ 3

В общем Lisp.

Помните:

'(1 2 3 4)

Выше представлен литер . Константные данные.

(list 1 2 3 4)

LIST - это функция, которая при вызове возвращает новый новый список с его аргументами в качестве элементов списка.

Избегайте изменения литеральных списков. Эффекты не стандартизированы. Представьте себе Lisp, который компилирует все постоянные данные в область памяти только для записи. Представьте себе Lisp, который принимает постоянные списки и делится ими по всем функциям.

(defun a () '(1 2 3)

(defun b () '(1 2 3))

Компилятор A Lisp может создать один список, совместно используемый обеими функциями.

Если вы измените список, возвращаемый функцией a

  • он не может быть изменен.
  • он может быть изменен
  • это может быть ошибка
  • он также может изменить список, возвращаемый функцией b

Реализации имеют свободу делать то, что им нравится. Это оставляет возможности для оптимизации.