Richtige Neuinitialisierung einer Liste? Was passiert unter der Haube?

8

Ich bringe mir etwas mehr Elisp bei und bin auf folgendes Problem gestoßen:

Wenn ich eine Listenvariable zurücksetzen möchte, wird sie nach der ersten Auswertung nicht aktualisiert. Hier ist ein Beispielcode:

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

Ich interessiere mich für zwei Dinge. Das erste ist, mehr darüber zu erfahren, was "unter der Haube" passiert. Warum funktioniert es also beim ersten Mal und schlägt bei nachfolgenden Anrufen fehl?

Die zweite und praktischere Frage ist, wie die Liste richtig neu initialisiert werden kann, oder gibt es eine andere übliche Methode, um so etwas zu tun?

Eine Problemumgehung, die ich selbst gefunden habe, besteht darin, eine zitierte Liste zu verwenden und den Inhalt folgendermaßen zu bewerten:

(setq example `(,3)) 
Clemera
quelle
2
Zusammenfassung: Erwarten Sie nicht '(some list)zu sein , equm '(some list)- je .Es ist in der Regel keine Garantie in Lisp , dass Code, der sichtbar zitiert eine Liste zurückgibt neue Listenstruktur jedes Mal. In einigen Lisp-Implementierungen kann dies der Fall sein oder manchmal. Bei anderen ist dies niemals der Fall. Ihr Code sollte ohnehin nicht von einem solchen Verhalten der Implementierung abhängen. Wenn Sie eine neue Listenstruktur wünschen, verwenden Sie listoder consoder eine gleichwertige.
Drew
(Ich vermute, dass diese Frage ein Duplikat ist, aber ich weiß nicht, wo das Duplikat ist.)
Drew
1
Ich denke, dass das Problem hier ist, dass examplees nie als Variable deklariert wurde, also so tun setqmuss, als ob es eine neue Variable deklariert, aber später, wenn Sie initializeerneut aufrufen, wird eine neue Variable erstellt, während modifydie alte gespeichert wird ... In jedem Fall ist dies kein erwartetes Verhalten. Die Verwendung von setqetwas, das zuvor nicht als Variable eingeführt wurde, kann jedoch genauso gut undefiniert sein.
wvxvw
3
OK, ich habe herausgefunden, was passiert. '(3)wird als wörtlicher Wert behandelt. (setcar '(3) 2)Wenn Sie also einmal , wann immer Sie dies tun (defvar foo '(3))oder (let ((foo '(3)))so weiter, erhalten Sie wahrscheinlich einen Wert von foogleich '(2). Ich sage "wahrscheinlich", weil dieses Verhalten nicht garantiert ist. Es ist eine Art Optimierung, die der Interpreter immer dann durchführt, wenn er Lust hat, etwas, das als Eliminierung von Konstanten-Subausdrücken bekannt ist (ein besonderer Fall von). Was abo-abo schrieb, ist also nicht genau der Grund. Es ist eher so, als würde man ein String-Literal in C ändern (was normalerweise eine Warnung generiert).
wvxvw
2
Duplikat von stackoverflow.com/q/16670989
phils

Antworten:

5

Vielleicht klärt dies die Verwirrung auf:

  • Ihre Funktion initilizeinitialisiert keine Variablen example. Es setzt es auf eine bestimmte Nachteile-Zelle - bei jedem Aufruf dieselbe Nachteile-Zelle. Beim ersten initilizeAufruf wird die setqZuweisung exampleeiner neuen Nachteile-Zelle zugewiesen, die das Ergebnis der Auswertung ist '(3). Nachfolgende Aufrufe müssen initilizenur examplederselben Cons-Zelle zugewiesen werden .

  • Da initilizenur dieselbe Nachteile-Zelle neu zugewiesen wird, examplewird modifydas Auto derselben Nachteile-Zelle bei 2jedem Aufruf einfach auf gesetzt.

  • Verwenden Sie zum Initialisieren einer Liste listoder cons(oder ein gleichwertiges Backquote-Sexp wie `(,(+ 2 1))oder `(,3)). Verwenden Sie zum Beispiel (list 3).

Der Schlüssel zum Verständnis besteht darin, zu wissen, dass eine zitierte Cons-Zelle nur einmal ausgewertet wird und danach dieselbe Cons-Zelle zurückgegeben wird. Dies ist nicht unbedingt die Art und Weise, wie sich alle Lisps verhalten, aber so verhält sich Emacs Lisp.

Allgemeiner ist das Verhalten beim Auswerten eines zitierten veränderlichen Objekts implementierungsabhängig, wenn nicht sprachabhängig. In Common Lisp zum Beispiel bin ich mir ziemlich sicher, dass die Definition (Spezifikation) der Sprache nichts enthält, was das diesbezügliche Verhalten definiert - es bleibt der Implementierung überlassen.

Zusammenfassung: Erwarten Sie nicht, dass '(eine Liste) gleich' (eine Liste) ist. Es gibt im Allgemeinen keine Garantie in Lisp, dass Code, der eine Liste sichtbar zitiert, jedes Mal eine neue Listenstruktur zurückgibt. In einigen Lisp-Implementierungen kann dies der Fall sein oder manchmal. Bei anderen ist dies niemals der Fall. Ihr Code sollte ohnehin nicht von einem solchen Verhalten der Implementierung abhängen. Wenn Sie eine neue Listenstruktur wünschen, verwenden Sie listoder consoder eine gleichwertige.

Drew
quelle
1
Ist der herablassende Ton in den beiden ersten Zeilen Ihrer Antwort notwendig? Ansonsten eine aufschlussreiche Antwort.
Asjo
@asjo: Die Absicht war nicht, sich in irgendeiner Weise herablassen zu lassen; Es tut uns leid. Hoffentlich ist es jetzt klarer.
Drew
Dank ein bisschen aufklären. Mit dem Begriff "Argument" meinte ich das Argument "(, 3) für die setq-Funktion, obwohl ich verstehe, dass es etwas unklar ist, weil ich die 3 in der zitierten Liste wirklich bewerte. Ich werde das bearbeiten.
Clemera
@ Draw Great! Es ist jetzt freundlicher zu lesen.
Asjo
5

Sie können (setq example (list 3))diesen Fehler vermeiden.

Was passiert ist , initordnet ein Objekt , das zunächst enthält (3)zu example. Der Wert des Objekts wird nur einmal festgelegt. Anschließend ändern Sie diesen Wert.

Hier ist Ihr Beispiel in C ++, wenn Sie das besser verstehen:

#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
quelle
1
Danke, ich kenne C ++ nicht, aber ich verstehe Ihr Beispiel. Eines stört mich jedoch immer noch: In Ihrem Codebeispiel führen Sie den Variablenspeicher ein, der das Objekt nur dann mit dem Anfangswert erstellt, wenn es noch nicht festgelegt wurde. Wie überträgt sich das auf das, was der Emacs Lisp Interpreter tut? Ich meine, wenn ich die initilizeFunktion neu bewerte und modifyerneut aufrufe, wird (3)sie nur wieder angezeigt, weil ich die Funktion neu bewertet habe.
Clemera
1
Ich kann nicht auf Einzelheiten eingehen (kenne sie nicht genau), sondern stelle sie mir (3)als temporäres Objekt vor, das Teil davon ist init. initDer Körper von 'setzt exampleauf die Adresse dieses temporären Objekts, er berührt seinen Wert nicht.
abo-abo