Странный пример цитируемого списка из 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. Здесь вы можете изменить исходный код во время выполнения, а не только данные, встроенные в исходный код.