Странный сценарий цитирования на Лиспе - Graham's On Lisp, страница 37 - PullRequest
13 голосов
/ 23 октября 2010

Я пробираюсь через книгу Грэма "На Лиспе" и не могу понять следующий пример на странице 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)))

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

Ответы [ 3 ]

7 голосов
/ 23 октября 2010

Замечание о том, что ваша ментальная модель цитирования может быть ошибочной, является превосходным - хотя оно может или не может применяться в зависимости от того, что это за ментальная модель.

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

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

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

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

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

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

6 голосов
/ 23 октября 2010

В Common Lisp.

Помните:

'(1 2 3 4)

Выше буквенный список . Постоянные данные .

(list 1 2 3 4)

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

(defun a () '(1 2 3)

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

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

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

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

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

6 голосов
/ 23 октября 2010

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 в жизни - создавать новые списки).

...