Почему эта функция возвращает каждый раз другое значение?
Может кто-нибудь объяснить следующее поведение? В частности, почему функция возвращает каждый раз другой список? Почему при вызове функции some-list
не инициализируется '(0 0 0)
?
(defun foo ()
(let ((some-list '(0 0 0)))
(incf (car some-list))
some-list))
Вывод:
> (foo)
(1 0 0)
> (foo)
(2 0 0)
> (foo)
(3 0 0)
> (foo)
(4 0 0)
Спасибо!
EDIT:
Кроме того, каков рекомендуемый способ реализации этой функции, если я хочу, чтобы функция выводила '(1 0 0)
каждый раз?
Ответы
Ответ 1
'(0 0 0)
- это литеральный объект, который считается константой (хотя и не защищенной от модификации). Таким образом, вы каждый раз изменяете один и тот же объект. Для создания разных объектов при каждом вызове функции используйте (list 0 0 0)
.
Итак, если вы не знаете, что вы делаете, вы всегда должны использовать литеральные списки (например, '(0 0 0)
) только как константы.
Ответ 2
На боковой ноте, определяющей эту функцию в sbcl REPL, вы получаете следующее предупреждение:
caught WARNING:
Destructive function SB-KERNEL:%RPLACA called on constant data.
See also:
The ANSI Standard, Special Operator QUOTE
The ANSI Standard, Section 3.2.2.3
Что дает хороший намек на проблему.
Ответ 3
'(0 0 0)
в коде - это литеральные данные. Изменение этих данных имеет поведение undefined. Общие реализации Lisp могут не обнаруживать его во время выполнения (если, например, данные не помещаются в некоторое пространство для чтения). Но это может иметь нежелательные последствия.
-
вы видите, что эти данные могут (и часто) совместно использоваться различными вызовами одной и той же функции
-
одна из наиболее тонких возможных ошибок такова: Common Lisp был определен с различными оптимизациями, которые могут быть сделаны компилятором. Например, компилятору разрешено повторно использовать данные:
Пример:
(let ((a '(1 2 3))
(b '(1 2 3)))
(list a b))
В приведенном выше фрагменте кода компилятор может обнаружить, что литеральные данные a
и b
равны EQUAL
. Затем он может иметь обе переменные, указывающие на одни и те же данные. Модификация может работать, но изменение видно из a
и b
.
Резюме: Модификация литеральных данных является источником нескольких тонких ошибок. Избегайте его, если это возможно. Затем вам нужно пропустить новые объекты данных. Заключение в целом означает выделение новых, новых структур данных во время выполнения.
Ответ 4
Хотелось написать сам, но я нашел хорошего онлайн:
CommonLisp имеет функции первого класса, т.е. функции - это объекты, которые могут быть созданы во время выполнения и переданы в качестве аргументов другим функциям. --AlainPicard Эти первоклассные функции также имеют свое собственное состояние, поэтому они являются функторами. Все функции Lisp являются функторами; здесь нет разделение между функциями, которые являются "просто кодом" и "функцией объектов". Состояние принимает форму захваченной лексической переменной привязок. Вам не нужно использовать LAMBDA для захвата привязок; DEFUN верхнего уровня тоже может это сделать: (let ((private-variable 42)) (defun foo() ...))
Код вместо... видит частную переменную в ее лексическом объем. Существует один экземпляр этой переменной, связанный с одним и только объект функции, который глобально привязан к символу FOO; переменная захватывается во время вычисления выражения DEFUN. Затем эта переменная действует как статическая переменная в C. Или, поочередно, вы можете думать о FOO как о "одиночном" объекте с "переменная экземпляра". --KazKylheku
Ссылка
http://c2.com/cgi/wiki?CommonLisp