Почему в этом случае локальная переменная elisp сохраняет свое значение?

Может ли кто-нибудь объяснить мне, чтопроисходит в этом очень простом фрагменте кода?

(defun test-a ()
  (let ((x '(nil)))
    (setcar x (cons 1 (car x)))
    x))

По вызову(test-a) впервые получаю ожидаемый результат:((1)), Но, к моему удивлению, называя это еще раз, я получаю,((1 1))((1 1 1)) и так далее. Почему это происходит? Я ошибаюсь, чтобы ожидать(test-a) всегда возвращаться((1))? Также обратите внимание, что после переоценки определенияtest-a, возвращаемый результат сбрасывается.

Также учтите, что эта функция работает так, как я ожидаю:

(defun test-b ()
  (let ((x '(nil)))
    (setq x (cons (cons 1 (car x)) 
                  (cdr x)))))

(test-b) всегда возвращается((1)), Почему неtest-a а такжеtest-b эквивалент?

Ответы на вопрос(3)

Решение Вопроса
Плохо

test-a являетсясамоизменяющийся код, Эточрезвычайно опасно, В то время какпеременная x исчезает в концеlet форма, егоНачальное значение сохраняется в объекте функции, и это значение, которое вы изменяете. Помните, что в Лиспефункция является объектом первого класса, который может быть передан (как число или список), а иногдамодифицированный, Это именно то, что вы делаете здесь: начальное значение дляx является частью объекта функции, и вы изменяете его.

Давайте на самом деле посмотрим, что происходит:

(symbol-function 'test-a)
=> (lambda nil (let ((x (quote (nil)))) (setcar x (cons 1 (car x))) x))
(test-a)
=> ((1))
(symbol-function 'test-a)
=> (lambda nil (let ((x (quote ((1))))) (setcar x (cons 1 (car x))) x))
(test-a)
=> ((1 1))
(symbol-function 'test-a)
=> (lambda nil (let ((x (quote ((1 1))))) (setcar x (cons 1 (car x))) x))
(test-a)
=> ((1 1 1))
(symbol-function 'test-a)
=> (lambda nil (let ((x (quote ((1 1 1))))) (setcar x (cons 1 (car x))) x))
Хорошо

test-b возвращает новую клетку против и, таким образом, в безопасности. Начальная стоимостьx никогда не изменяется Разница между(setcar x ...) а также(setq x ...) является то, что первый изменяетобъект уже хранится в переменнойx в то время как последнийхранитновый объект вx, Разница похожа наx.setField(42) противx = new MyObject(42) в .C++

Суть

В общем, лучше всего лечитьцитируемый данные как'(1) как константы - делайне изменить их:

quote возвращает аргумент, не оценивая его.(quote x) урожайностьxПредупреждение:quote не создает возвращаемое значение, а просто возвращает значение, которое было предварительно создано читателем Lisp (см. информационный узелПечатное представление). Это означает, что(a . b) не идентичен(cons 'a 'b)Бывший не минусы. Цитирование должно быть зарезервировано для констант, которые никогда не будут изменены побочными эффектами, если вы не любите самоизменяющийся код. Смотрите общую ловушку в информационном узлеперегруппировка для примера неожиданных результатов, когда цитируемый объект изменен.

Если вам нужноизменить списоксоздать его сlist или жеcons или жеcopy-list вместо .quote

УвидетьБольше Примеры.

PS. Это было продублировано наEmacs.

PPS. Смотрите такжеПочему эта функция каждый раз возвращает другое значение? для идентичной проблемы Common Lisp.

 Tyler21 мая 2013 г., 20:12
Это имеет смысл сейчас, когда вы это объясните. Я бы никогда не сделал эту связь.
 Tyler21 мая 2013 г., 19:50
Но почему модификация сохраняется? не должен»т переменная х, который я думаю, просто указатель на'(nil) здесь, быть удаленным в конце формы let?
 Vatine24 мая 2013 г., 11:12
@ abo-abo Хотя проблема проявляется из-заsetcar мутируя свою ценность, виновник модифицирует литералы. Если вы изменитеtest-a иметь(let ((x (list nil))) ...) вы бы не увидели проблему, потому что ваш(nil) будет заново создаваться при каждом вызове, вместо того, чтобы быть одноразовым литералом, который изменяется.
 Tyler22 мая 2013 г., 13:10
@ abo-abo в test-a, setcar используется для прямого изменения списка, на который указывает x. X указывает на константу в определении функции, поэтому сама функция изменяется. В test-b значения в списке, на которые изначально указывает x, используются для создания нового списка, который будет присвоен x. Начальное значение никогда не изменяется напрямую. Таким образом, вы правы, сет-кар является ключевым отличием.
 sds21 мая 2013 г., 20:03
@Tyler: переменная удалена, но начальное значение - нет. см редактировать
 phils22 мая 2013 г., 04:07
Это'с подсветкой звонить(symbol-function 'test-a) оба после определенияtest-aи снова после вызова.
 abo-abo22 мая 2013 г., 09:57
Спасибо, @sds. Я принял ваш ответ, но я до сих порЯ понял это. "его начальное значение сохраняется в объекте функции " - это та часть, которую я нене понимаю: почему это отличается втест-б? Это было заявлено точно так же. Моя интуиция говорит мне, что этоСеткар, который виноват здесь, а не комбинацияпусть и 'цитаты.

Я обнаружил, что виновник действительноцитаты. Вот's его строка документа:

Вернуть аргумент, не оценивая его.

...

Предупреждение: `цитата ' не создает возвращаемое значение, а просто возвращает значение, которое было предварительно создано читателем Lisp

...

Цитирование должно быть зарезервировано для констант, которые никогда не будут изменены побочными эффектами, если вы не любите самоизменяющийся код.

Я тоже переписал для удобства

(setq test-a 
      (lambda () ((lambda (x) (setcar x (cons 1 (car x))) x) (quote (nil)))))

а затем использовал

(funcall test-a)

чтобы увидеть кактест-а менялся.

Похоже,(ноль) в вашем (пусть) оценивается только один раз. Когда вы (setcar), каждый вызов изменяет один и тот же список на месте. Вы можете заставить (тест-а) работать, если вы замените(ноль) с (список (список)), хотя я предполагаю, чтоЭто более элегантный способ сделать это.

(test-b) каждый раз создает абсолютно новый список из cons-ячеек, поэтому он работает по-разному.

 regularfry21 мая 2013 г., 16:46
По-моему, ясказал бы этоs потому что emacs создает список при синтаксическом анализе литерала в let, а затем обращается к одному и тому же списку при каждом вызове функции Это'это нравитсяс помощью указателя на дерево разбора для ссылки на x.
 sds21 мая 2013 г., 20:04
@ abo-abo: Emacs делаетне сохранить информацию о х, но о его начальном значении. смотри мой ответ.
 abo-abo21 мая 2013 г., 15:37
Спасибо, ваш ответ представляет собой обходной путь, но я все еще неЯ не понимаю, почему это происходит. х внутри "test-a объявлен локальным, и это действительно таквне функции, но почему функция сохраняет информацию о x между вызовами?

Ваш ответ на вопрос