6

Estoy tratando de pasar una lista a una función en Lisp, y cambiar el contenido de esa lista dentro de la función sin afectar la lista original. He leído que Lisp es de pasada, y es cierto, pero hay algo más que no entiendo del todo. Por ejemplo, este código funciona como se esperaba:En common-lisp, ¿cómo modifico parte de un parámetro de lista dentro de una función sin cambiar la lista original?

(defun test() 
    (setf original '(a b c)) 
    (modify original) 
    (print original)) 
(defun modify (n) 
    (setf n '(x y z)) 
    n)

Si llama (prueba), se imprime devuelve (a b c) a pesar de que (modificar) (x y z).

Sin embargo, no funciona de esa manera si intenta cambiar solo una parte de la lista. Supongo que esto tiene algo que ver con las listas que tienen el mismo contenido siendo el mismo en la memoria en todas partes o algo así. Aquí hay un ejemplo:

(defun test() 
    (setf original '(a b c)) 
    (modify original) 
    (print original)) 
(defun modify (n) 
    (setf (first n) 'x) 
    n)

Luego imprime (prueba) (x b c). Entonces, ¿cómo puedo cambiar algunos elementos de un parámetro de lista en una función, como si esa lista fuera local para esa función?

+3

Tenga en cuenta que las consecuencias de modificar las constantes literales no están definidas. No hagas eso. Nunca. '(a b c) es una constante literal en tu código. No deberías modificarlo. Puede modificar listas que se crean con la función LIST like (lista 'a' b 'c). –

+4

también tenga en cuenta que (SETF original '(a b c)) no tiene sentido. SETF no introduce variables.la variable 'original' no está definida en ninguna parte. Puede establecer variables que se han introducido a través de LET, DEFUN, DEFVAR, DEFPARAMETER, ... –

Respuesta

7

SETF modifica un lugar. n puede ser un lugar. El primer elemento de la lista n puntos a también puede ser una . lugar

en ambos casos, la lista en poder de original se pasa a modify como su parámetro n. Esto significa que tanto original en la función test y n en la función modify ahora posee la misma lista, que significa que tanto original como n ahora apuntan a su primer elemento.

Después de que SETF modifique n en el primer caso, ya no apunta a esa lista, sino a una nueva lista. La lista apuntada por original no se ve afectada. La nueva lista es devuelta por modify, pero como este valor no está asignado a nada, se desvanece y pronto se recolectará basura.

En el segundo caso, SETF no modifica n, pero el primer elemento de la lista apunta a n. Esta es la misma lista a la que apunta original, por lo tanto, luego, puede ver la lista modificada también a través de esta variable.

Para copiar una lista, use COPY-LIST.

2

Probablemente tenga problemas porque, aunque Lisp es pass-by-value, las referencias a objetos se pasan, como en Java o Python. Sus células cons contienen referencias que usted modifica, por lo que modifica tanto las originales como las locales.

IMO, debe intentar escribir funciones en un estilo más funcional para evitar tales problemas. Aunque Common Lisp es multi-paradigma, un estilo funcional es una forma más apropiada.

(defun modificar (n) (x cons'(CDR n))

+0

Este es mi primer proyecto en lisp, y realmente no entiendo la programación funcional todavía. ¿Supongo que solo necesito repensar la forma en que he estructurado mi programa? Estoy tratando de examinar posibles modificaciones a un estado de juego sin modificar el estado. Entonces, en lugar de hacer funciones para makechange y undochange, esperaba poder modificar una "copia" del estado en su lugar. – Ross

+0

En general, es aconsejable evitar cambios de estado innecesarios. A menudo no los necesitas y te hacen programar más duro para depurar y provocar errores como este. – freiksenet

11

Las listas Lisp se basan en las células cons. Las variables son como punteros a las células cons (u otros objetos Lisp). Cambiar una variable no cambiará otras variables. El cambio de las celdas cons será visible en todos los lugares donde haya referencias a esas celdas cons.

Un buen libro es Touretzky, Common Lisp: A Gentle Introduction to Symbolic Computation.

También hay software que dibuja árboles de listas y celdas de cons.

Si pasa una lista de una función como esta:

(modify (list 1 2 3)) 

Entonces usted tiene tres formas diferentes de utilizar la lista:

modificación destructiva de las cons cells

(defun modify (list) 
    (setf (first list) 'foo)) ; This sets the CAR of the first cons cell to 'foo . 

estructura compartiendo

(defun modify (list) 
    (cons 'bar (rest list))) 

Above devuelve una lista que comparte estructura con la lista pasada: los elementos restantes son los mismos en ambas listas.

copia

(defun modify (list) 
    (cons 'baz (copy-list (rest list)))) 

Por encima de la función BAZ es similar al bar, pero no hay celdas de la lista son compartidos, ya que la lista se copia.

No hace falta decir que las modificaciones destructivas a menudo deben evitarse, a menos que haya una razón real para hacerlo (como guardar la memoria cuando vale la pena).

Notas:

no destructiva modificar las listas de constantes literales

'no hacer: (let ((l' (abc))) (setf (primera l) 'bar))

Motivo: la lista puede estar protegido contra escritura, o puede ser compartida con otras listas (dispuestas por el compilador), etc.

también:

Introducir las variables

como esto

(let ((original (list 'a 'b 'c))) 
    (setf (first original) 'bar)) 

o como esto

(defun foo (original-list) 
    (setf (first original-list) 'bar)) 

Nunca setf una variable no definida.

+0

Gracias. Esto fue muy útil para mí. – Ross

4

que es casi lo mismo que este ejemplo en C:

void modify1(char *p) { 
    p = "hi"; 
} 

void modify2(char *p) { 
    p[0] = 'h'; 
} 

en ambos casos se pasa un puntero, si cambia el puntero, que está cambiando la copia de parámetros del valor del puntero (que está en la pila), si cambias el contenido, estás cambiando el valor de cualquier objeto que esté apuntando.

Cuestiones relacionadas