Почему эта функция возвращает каждый раз другое значение?

Может кто-нибудь объяснить следующее поведение? В частности, почему функция возвращает каждый раз другой список? Почему при вызове функции 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