2012-01-22 22 views
15

¿Alguien puede explicar el siguiente comportamiento? Específicamente, ¿por qué la función devuelve una lista diferente cada vez? ¿Por qué no se inicializa some-list en '(0 0 0) cada vez que se llama a la función?¿Por qué esta función devuelve un valor diferente cada vez?

(defun foo() 
    (let ((some-list '(0 0 0))) 
    (incf (car some-list)) 
    some-list)) 

Salida:

> (foo) 
(1 0 0) 
> (foo) 
(2 0 0) 
> (foo) 
(3 0 0) 
> (foo) 
(4 0 0) 

Gracias!

EDIT:

Además, ¿cuál es la forma recomendada de implementar esta función, suponiendo que desea que la función de salida '(1 0 0) cada vez?

Respuesta

21

'(0 0 0) es un objeto literal, que se supone que es una constante (aunque no protegido de la modificación). Entonces estás modificando efectivamente el mismo objeto todo el tiempo. Para crear diferentes objetos en cada llamada de función, use (list 0 0 0).

De modo que, a menos que sepa lo que está haciendo, siempre debe usar listas literales (como '(0 0 0)) solo como constantes.

+0

Ah, tiene sentido ahora. Gracias por la explicación clara. –

+2

Probablemente sería bueno agregar que también el cuasiquoting no garantiza la devolución de listas nuevas. – 6502

+3

"a menos que sepa, lo que está haciendo" El comportamiento de modificar datos literales no está definido. De acuerdo con la especificación, en realidad no se puede saber lo que se está haciendo (con certeza), por lo que "siempre ** debes ** usar listas literales (como '(0 0 0)) solo como constantes'. –

-5

quería escribir uno yo mismo, pero me pareció una buena línea:

CommonLisp tiene funciones de primera clase, funciones es decir, son objetos que se pueden crear en tiempo de ejecución, y se pasan como argumentos a otras funciones. --AlainPicard Estas funciones de primera clase también tienen su propio estado, por lo que son funtores. Todas las funciones de Lisp son funtores; no hay separación entre las funciones que son "solo código" y "función objetos". El estado toma la forma de enlaces capturados de variable léxica . No necesita usar LAMBDA para capturar enlaces; un DEFUN de nivel superior puede hacerlo también: (let ((privado-variable 42)) (foo defun() ...))

El código en el lugar de ... ve-privada variables en su alcance léxico . Hay una instancia de esta variable asociada con el y el único objeto de función que está vinculado globalmente al símbolo FOO; la variable se captura en el momento en que se evalúa la expresión DEFUN. Esta variable actúa entonces como una variable estática en C. O bien, alternativamente, puede pensar en FOO como un objeto "singleton" con una "variable de instancia" . --KazKylheku

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

+1

¿Puede explicarnos cómo el texto que citó se relaciona con la pregunta? Puede que me esté perdiendo algo, pero no lo veo. – sepp2k

+0

El texto explica cómo las funciones son objetos de primera clase en Lisp, y realmente tienen un "estado". La variable declarada era parte del "estado" de la función. Como explica el texto, esto es muy similar a la declaración de variables locales estáticas en C. ¿Qué parte del texto no está relacionada con este problema? – xtrem

+2

La parte donde eso no es para nada lo que está sucediendo. Su cita habla sobre "enlaces de variables léxicas capturadas". Sin embargo, 'some-list' es una variable local de' foo', no es una variable capturada y, por lo tanto, no forma parte del estado de 'foo'. En cada invocación de 'foo',' some-list' tendrá un enlace único (que, como explicó Vsevolod, apunta a la misma lista de "constantes", lo que explica el comportamiento del OP). Esto es completamente diferente a una función que modifica las variables capturadas. – sepp2k

9

En una nota, la definición de esta función en el REPL sbcl se obtiene la siguiente advertencia:

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 

que da una buena pista hacia el problema en cuestión.

4

'(0 0 0) en el código son datos literales. La modificación de esta información tiene un comportamiento indefinido. Las implementaciones de Common Lisp pueden no detectarlo en tiempo de ejecución (a menos que los datos se coloquen, por ejemplo, en algún espacio de memoria de solo lectura). Pero puede tener efectos no deseados.

  • ves que estos datos pueden ser (ya menudo es) compartida a través de varias invocaciones de la misma función

  • uno de los errores más sutiles posibles es la siguiente: Common Lisp se ha definido con varias optimizaciones lo cual puede ser hecho por un compilador en mente. Por ejemplo se permite que un compilador para reutilizar los datos:

Ejemplo:

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

En el código de seguridad de fragmento de código que el compilador puede detectar que los datos literales de a y b es EQUAL. Puede tener ambas variables apuntando a los mismos datos literales. Modificarlo puede funcionar, pero el cambio es visible desde a y b.

Resumen: La modificación de datos literales es una fuente de varios errores sutiles. Evítalo si es posible. Luego necesita cons nuevos objetos de datos. Contener en general significa la asignación de nuevas estructuras de datos en tiempo de ejecución.

Cuestiones relacionadas