Почему эта функция каждый раз возвращает другое значение? - PullRequest
19 голосов
/ 22 января 2012

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

Спасибо!

РЕДАКТИРОВАТЬ:

Кроме того, каков рекомендуемый способ реализации этой функции, если я хочу, чтобы функция каждый раз выводила '(1 0 0)?

Ответы [ 4 ]

24 голосов
/ 22 января 2012

'(0 0 0) - это буквальный объект, который считается константой (хотя и не защищен от модификации). Таким образом, вы эффективно модифицируете один и тот же объект каждый раз. Для создания разных объектов при каждом вызове функции используйте (list 0 0 0).

Поэтому, если вы не знаете, что делаете, вы всегда должны использовать буквенные списки (например, '(0 0 0)) только в качестве констант.

11 голосов
/ 23 января 2012

С другой стороны, определяя эту функцию в REPL sbcl, вы получаете следующее предупреждение:

  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

, которое дает хороший совет относительно рассматриваемой проблемы.

5 голосов
/ 23 января 2012

'(0 0 0) в коде - это буквальные данные.Изменение этих данных имеет неопределенное поведение.Обычные реализации Lisp могут не обнаружить его во время выполнения (если, например, данные не помещены в некоторое пространство памяти только для чтения).Но это может иметь нежелательные последствия.

  • вы видите, что эти данные могут быть (и часто используются) для различных вызовов одной и той же функции

  • Одна из наиболее тонких возможных ошибок заключается в следующем: Common Lisp был определен с различными оптимизациями, которые могут быть выполнены с помощью компилятора.Например, компилятору разрешено повторно использовать данные:

Пример:

(let ((a '(1 2 3))
      (b '(1 2 3)))
  (list a b))

В приведенном выше фрагменте кода компилятор может обнаружить, что литеральные данные a иb - это EQUAL.Тогда обе переменные могут указывать на одни и те же литеральные данные.Модификация может работать, но изменения видны с a и b.

Сводка: Модификация литеральных данных является источником нескольких тонких ошибок.Избегайте этого, если это возможно.Тогда вам нужно cons новых объектов данных. В общем случае означает выделение новых, новых структур данных во время выполнения.

0 голосов
/ 22 января 2012

Хотел написать один сам, но я нашел хороший онлайн:

CommonLisp имеет функции первого класса, то есть функции являются объектами, которые может быть создан во время выполнения и передан в качестве аргументов другим функциям. --AlainPicard Эти первоклассные функции также имеют свое собственное состояние, поэтому они являются функторами. Все функции Lisp являются функторами; здесь нет разделение между функциями, которые "просто код" и "функция объекты ". Государство принимает форму захваченной лексической переменной привязок. Вам не нужно использовать LAMBDA для захвата привязок; DEFUN верхнего уровня тоже может это делать: (let ((private-variable 42)) (defun foo () ...))

Код вместо ... видит приватную переменную в ее лексическом объем. Существует один экземпляр этой переменной, связанный с и только функциональный объект, который глобально привязан к символу FOO; Переменная записывается во время вычисления выражения DEFUN. Затем эта переменная действует как статическая переменная в C. Или, поочередно, вы можете думать о FOO как о «одиночном» объекте с "переменная экземпляра". --KazKylheku

Ref http://c2.com/cgi/wiki?CommonLisp

...