¿Reinicialización adecuada de una lista? ¿Qué está pasando debajo del capó?

8

Me estoy enseñando un poco más de elisp y he encontrado el siguiente problema:

Si quiero restablecer una variable de lista, no se actualizará después de la primera evaluación. Aquí hay un código de ejemplo:

(defun initilize ()
  (setq example '(3)))

(defun modify ()
  (initilize)
  (message "%S" example)
  (setcar example 2))

; M-x eval-buffer RET
(modify) ; message --> (3)
(modify) ; message --> (2)
(modify) ; message --> (2)

Estoy interesado en dos cosas. La primera es aprender más sobre lo que está sucediendo "bajo el capó", entonces, ¿por qué funciona la primera vez y falla en llamadas posteriores?

La segunda pregunta, más práctica, es cómo reiniciar la lista correctamente o ¿hay otra forma común de hacer algo así?

Una solución alternativa que encontré es usar una lista citada y evaluar el contenido de esta manera:

(setq example `(,3)) 
clemera
fuente
2
Resumen: No hay que esperar '(some list)a estar eqa '(some list)- nunca .Hay general hay garantía de que en Lisp código que visiblemente cita una lista de declaraciones de nueva estructura de la lista cada vez. En algunas implementaciones de Lisp podría, o podría ser, algunas veces. En otros, nunca lo hace. Su código no debería depender de ningún comportamiento de la implementación. Si quieres nueva estructura de la lista, el uso listo conso equivalente.
Dibujó
(Supongo que esta pregunta es un duplicado, pero no sé dónde está el duplicado).
Dibujó el
1
Creo que el problema aquí es que examplenunca se ha declarado como una variable, por lo que setqdebe actuar como si declarara una nueva variable, pero más tarde, cuando llame initializenuevamente, se creará una nueva variable, mientras modifyrecuerda la anterior ... en cualquier caso, este no es un comportamiento esperado, sin embargo, el uso de setqalgo que no se ha introducido anteriormente como una variable también podría estar indefinido.
wvxvw
3
OK, descubrí lo que pasa. '(3)se trata como un valor literal, así que una vez (setcar '(3) 2), cada vez que haces (defvar foo '(3))o (let ((foo '(3)))etc. Es probable que obtener un valor de fooigual a '(2). Digo "probable" porque este comportamiento no está garantizado, es un tipo de optimización que el intérprete hace cuando se siente, algo conocido como eliminación de subexpresiones constantes (un caso particular de). Entonces, lo que escribió abo-abo no es exactamente la razón. Es más como modificar un literal de cadena en C (que generalmente genera una advertencia).
wvxvw
2
Duplicado de stackoverflow.com/q/16670989
phils

Respuestas:

5

Quizás esto aclare algo de la confusión:

  • Su función initilizeno inicializa la variable example. Lo establece en una celda de contra en particular: la misma celda de contras cada vez que se llama. La primera vez que initilizese llama, se setqasigna examplea una nueva celda de contras, que es el resultado de la evaluación '(3). Llamadas posteriores para initilizesimplemente reasignar examplea la misma celda de contras.

  • Dado que initilizesolo reasigna la misma celda de contras example, modifysolo establece el auto de esa misma celda de contras 2cada vez que se llama.

  • Para inicializar una lista, use listo cons(o un sexp entre comillas equivalente, como `(,(+ 2 1))o `(,3)). Por ejemplo, use (list 3).

La clave para comprender esto es saber que una celda contratada entre comillas se evalúa solo una vez, y luego se devuelve la misma celda contra. Esta no es necesariamente la forma en que se comportan todos los Lisps, pero es cómo se comporta Emacs Lisp.

De manera más general, el comportamiento de evaluar un objeto mutable entre comillas depende de la implementación, si no del lenguaje. En Common Lisp, por ejemplo, estoy bastante seguro de que no hay nada en la definición (especificación) del lenguaje que defina el comportamiento a este respecto; se deja a la implementación.

Resumen: No espere que '(alguna lista) sea igual a' (alguna lista), nunca. Por lo general, no hay garantía en Lisp de que el código que cita visiblemente una lista devuelva una nueva estructura de lista cada vez. En algunas implementaciones de Lisp podría, o podría ser, algunas veces. En otros, nunca lo hace. Su código no debería depender de ningún comportamiento de la implementación. Si quieres nueva estructura de la lista, el uso listo conso equivalente.

Dibujó
fuente
1
¿Es necesario el tono condescendiente en las dos primeras líneas de su respuesta? De lo contrario, una respuesta esclarecedora.
asjo
@asjo: La intención no era condescender de ninguna manera; lo siento. Ojalá esté más claro ahora.
Dibujó el
Gracias por aclarar un poco las cosas. Con el término "argumento" quise decir el argumento `(, 3) para la función setq, aunque entiendo que no está claro porque realmente estoy evaluando el 3 en la lista citada. Lo editaré.
clemera
@Drew ¡Genial! Es más amigable leer ahora.
asjo
5

Puede usar (setq example (list 3))para evitar este error.

Lo que pasa es initcesionarios un objeto que inicialmente contiene (3)a example. Establece el valor del objeto solo una vez. Posteriormente, modifica este valor.

Aquí está su ejemplo en C ++, si lo entiende mejor:

#include <stdio.h>
#include <string.h>
char* example;
char* storage = 0;
char* create_object_once (const char* str) {
  if (storage == 0) {
    storage = new char[strlen (str)];
    strcpy (storage, str);
  }
  return storage;
}
void init () {
  example = create_object_once ("test");
}
void modify () {
  init ();
  printf ("%s\n", example);
  example[0] = 'f';
}
int main (int argc, char *argv[]) {
  modify ();
  modify ();
  modify ();
  return 0;
}
abo-abo
fuente
1
Gracias, no conozco C ++ pero entiendo tu ejemplo. Sin embargo, una cosa todavía me molesta: en su ejemplo de código, introduce el almacenamiento variable que crea el objeto con el valor inicial solo si aún no se configuró. ¿Cómo se traduce eso a lo que está haciendo el intérprete Emacs Lisp? Quiero decir, si reevalúo la initilizefunción y llamo modifynuevamente, se mostrará (3)nuevamente solo porque reevalúe la función.
clemera
1
No puedo entrar en detalles (no los conozco exactamente), pero piense que (3)es un objeto temporal del que forma parte init. initEl cuerpo se ajusta examplea la dirección de ese objeto temporal, no toca su valor.
abo-abo el