Por que uma variável local elisp mantém seu valor nesse caso?

Alguém poderia me explicar o que está acontecendo neste trecho de código muito simples?

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

Após uma chamada (test-a) pela primeira vez, recebo o resultado esperado: ((1)) . Mas para minha surpresa, chamando-o mais uma vez, eu recebo ((1 1)) , ((1 1 1)) e assim por diante. Por que isso está acontecendo? Estou errado em esperar que (test-a) retorne sempre ((1)) ? Observe também que depois de reavaliar a definição do test-a , o resultado de retorno é redefinido.

Considere também que essa function funciona como eu esperava:

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

(test-b) sempre retorna ((1)) . Por que não são equivalentes ao test-a e test-b ?

O mal

test-a é test-a código auto-modificador . Isso é extremamente perigoso . Enquanto a variável x desaparece no final da forma let , seu valor inicial persiste no object da function, e esse é o valor que você está modificando. Lembre-se que em Lisp uma function é um object de primeira class , que pode ser passado por aí (como um número ou uma lista) e, às vezes, modificado . Isso é exatamente o que você está fazendo aqui: o valor inicial de x é uma parte do object de function e você está modificando-o.

Vamos realmente ver o que está acontecendo:

 (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)) 

O bom

test-b retorna uma nova cons consola e, portanto, é seguro. O valor inicial de x nunca é modificado. A diferença entre (setcar x ...) e (setq x ...) é que o primeiro modifica o object já armazenado na variável x enquanto o último armazena um novo object em x . A diferença é semelhante a x.setField(42) vs. x = new MyObject(42) em C++ .

The Bottom Line

Em geral, é melhor tratar dados citados como '(1) como constantes – não os modifique:

quote retorna o argumento, sem avaliá-lo. (quote x) rende x . Aviso : a quote não constrói seu valor de retorno, mas apenas retorna o valor que foi pré-construído pelo leitor Lisp (consulte Representação impressa do nó de informações). Isso significa que (a . b) não é idêntico a (cons 'a 'b) : o primeiro não contras. As citações devem ser reservadas para constantes que nunca serão modificadas por efeitos colaterais, a menos que você goste de código de auto-modificação. Veja a armadilha comum no nó de informações Rearranjo para obter um exemplo de resultados inesperados quando um object citado é modificado.

Se você precisar modificar uma lista , crie-a com list ou cons ou copy-list vez de quote .

Veja mais exemplos .

PS. Isso foi duplicado no Emacs .

PPS. Veja também Por que esta function retorna um valor diferente toda vez? para um problema de Common Lisp idêntico.

Eu encontrei o culpado é de fato ‘citação. Aqui está o seu doc-string:

Retorne o argumento, sem avaliá-lo.

Atenção: `quote ‘não constrói seu valor de retorno, mas apenas retorna o valor que foi pré-construído pelo leitor Lisp

As citações devem ser reservadas para constantes que nunca serão modificadas por efeitos colaterais, a menos que você goste de código de auto-modificação.

Eu também reescrevi por conveniência

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

e depois usado

 (funcall test-a) 

para ver como ‘o teste-a estava mudando.

Parece que o ‘(nulo) no seu (let) é avaliado apenas uma vez. Quando você (setcar), cada chamada está modificando a mesma lista no local. Você pode fazer (teste-a) funcionar se você replace o ‘(nil) por (lista (lista)), embora eu presuma que exista uma maneira mais elegante de fazê-lo.

(teste-b) constrói uma lista totalmente nova a partir das células de cons cada vez, e é por isso que funciona de maneira diferente.