Странный пример цитируемого списка из On Lisp

Этот отрывок из On Lisp действительно сбивает с толку - не ясно, как возвращать цитируемый список, такой как '(oh my) действительно может изменить поведение функции в будущем: не будет ли возвращенный список снова сгенерирован в функции с нуля, при следующем вызове?

Если мы определим восклицательный знак так, чтобы его возвращаемое значение включало в себя список в кавычках,

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

может изменить список в функции:

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

Чтобы оправдать такие проблемы, нужно написать:

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

Как именно этот последний звонок exclaim добавив слово goodness к результату? Функция не ссылается на какую-либо внешнюю переменную, поэтому как отдельный вызов nconc на самом деле изменить, как exclaim функция работает?

1 ответ

Решение

А) влияние модификации литеральных списков в стандарте Common Lisp не определено. То, что вы видите здесь в качестве примера, является одним из возможных вариантов поведения.

(1 2 3 4) это буквальный список. Но призыв к LIST как в (list 1 2 3 4) Возвращает свежий список во время выполнения.

б) список литеральных данных в коде функции. Каждый вызов вернет именно этот объект данных. Если вы хотите предоставить новый список для каждого вызова, то вам нужно использовать что-то вроде LIST или COPY-LIST.

c) Поскольку возвращаемый список всегда является одним и тем же литеральным объектом данных, его изменение МОЖЕТ иметь такой эффект, как описано. Можно также представить, что ошибка возникает, если код и его объекты размещаются в постоянной памяти. Изменяя список, вы попытаетесь записать его в постоянную память.

d) При работе с данными литерального списка в исходном коде следует помнить следующее: компилятор Lisp может оптимизировать хранилище. Если в исходном коде список встречается несколько раз, компилятору разрешается это обнаруживать и создавать только ОДИН список. Все различные места будут указывать на этот список. Таким образом, изменение списка приведет к тому, что эти изменения будут видны в нескольких местах.

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

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

Также обратите внимание, что Лисп может быть выполнен Интерпретатором. Интерпретатор обычно работает с исходной структурой Lisp - код не является машинным кодом, а интерпретирует код Lisp как данные Lisp. Здесь вы можете изменить исходный код во время выполнения, а не только данные, встроенные в исходный код.

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