En común Lisp, ¿cómo puedo modificar parte de una lista de parámetros dentro de una función sin cambiar la lista original?

StackOverflow https://stackoverflow.com/questions/1484335

Pregunta

Estoy tratando de pasar una lista a una función en Lisp, y cambiar el contenido de dicha lista dentro de la función sin afectar a la lista original. He leído que Lisp es el paso por valor, y es verdad, pero hay algo más en juego que no entiendo muy bien. Por ejemplo, este código funciona como se espera:

(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 sólo una parte de la lista. Asumo que esto tiene algo que ver con las listas que tienen el mismo contenido es el mismo en todas partes en la memoria o algo por el estilo? He aquí un ejemplo:

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

A continuación (de prueba) grabados (x b c). Entonces, ¿cómo puedo cambiar algunos elementos de una lista de parámetros en una función, como si esa lista era local a esa función?

¿Fue útil?

Solución

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

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

Después de SETF modifica n en el primer caso, los puntos ya no a esa lista, sino a una lista nueva. La lista a la que apunta original no se ve afectada. La nueva lista se devuelve por modify, pero ya que este valor no está asignado a nada, que se desvanece fuera de la existencia y en breve será el recolector de basura.

En el segundo caso, SETF no modifica n, pero el primer elemento de los puntos lista n a. Se trata de los mismos puntos de la lista a original, por lo que, después, se puede ver la lista modificada también a través de esta variable.

Para copiar una lista, utilice COPIA-LIST .

Otros consejos

listas Lisp se basan en cons cells. Las variables son como punteros a cons cells (u otros objetos Lisp). Cambiar una variable no va a cambiar otras variables. Cambio de cons cells será visible en todos los lugares en los que hay referencias a esos cons cells.

Un buen libro es Touretzky, Common Lisp: Una Introducción al Cálculo Simbólico .

También hay software que drena los árboles de las listas y los contras células.

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

(modify (list 1 2 3))

A continuación, usted tiene tres formas diferentes de utilizar la lista:

modificación destructiva de los contras células

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

estructura de intercambio

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

Sobre devuelve una lista que comparte estructura con el pasa en lista:. Los elementos de apoyo son los mismos en ambas listas

copiar

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

Por encima de la función BAZ es similar a la barra, pero no se comparten celdas de la lista, ya que la lista se copia.

No hace falta decir que la modificación destructiva menudo debe evitarse, a menos que exista una razón real para hacerlo (como la memoria a ahorrar cuando vale la pena).

Notas:

no destructiva modificar las listas de constantes literales

'no do: (let ((l' (a b c))) (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.

que es casi el 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 es en la pila), si cambia el contenido, que está cambiando el valor de lo objeto se señaló.

Es probable que tenga problemas porque a pesar de Lisp es el paso por valor se pasan las referencias a objetos, al igual que en Java o Python. Sus células contienen referencias contras que modificar, por lo que modifican tanto original y uno local.

OMI, usted debe tratar de escribir funciones en un estilo más funcional para evitar este tipo de problemas. A pesar de que Common Lisp es multi-paradigma de un estilo funcional es una forma más adecuada.

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

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top