Angenommen, ich habe eine Datei mit dem Namen elisp-defvar-test.el
:
;;; elisp-defvar-test.el --- -*- lexical-binding: t -*-
(defvar my-dynamic-var)
(defun f1 (x)
"Should return X."
(let ((my-dynamic-var x))
(f2)))
(defun f2 ()
"Returns the current value of `my-dynamic-var'."
my-dynamic-var)
(provide 'elisp-dynamic-test)
;;; elisp-defvar-test.el ends here
Ich lade diese Datei und gehe dann in den Arbeitspuffer und starte:
(setq lexical-binding t)
(f1 5)
(let ((my-dynamic-var 5))
(f2))
(f1 5)
Gibt wie erwartet 5 zurück, was darauf hinweist, dass der Body von wie erwartet als Variable mit dynamischem Gültigkeitsbereich f1
behandelt wird my-dynamic-var
. Das letzte Formular gibt jedoch einen Void-Variablenfehler für an my-dynamic-var
, der angibt, dass für diese Variable ein lexikalischer Gültigkeitsbereich verwendet wird. Dies scheint im Widerspruch zu der Dokumentation zu stehen defvar
, die besagt:
Das
defvar
Formular deklariert die Variable auch als "speziell", so dass sie immer dynamisch gebunden ist, auch wennlexical-binding
t ist.
Wenn ich das defvar
Formular in der Testdatei so ändere , dass ein Anfangswert angegeben wird, wird die Variable immer als dynamisch behandelt, wie in der Dokumentation angegeben. Kann jemand erklären, warum der Umfang einer Variablen davon abhängt, ob defvar
bei der Deklaration dieser Variablen ein Anfangswert angegeben wurde oder nicht ?
Hier ist die Fehlerrückverfolgung, falls es darauf ankommt:
Debugger entered--Lisp error: (void-variable my-dynamic-var)
f2()
(let ((my-dynamic-var 5)) (f2))
(progn (let ((my-dynamic-var 5)) (f2)))
eval((progn (let ((my-dynamic-var 5)) (f2))) t)
elisp--eval-last-sexp(t)
eval-last-sexp(t)
eval-print-last-sexp(nil)
funcall-interactively(eval-print-last-sexp nil)
call-interactively(eval-print-last-sexp nil nil)
command-execute(eval-print-last-sexp)
quelle
Antworten:
Warum die beiden unterschiedlich behandelt werden, ist meistens "weil wir das brauchten". Genauer gesagt, die Einzelargumentform von
defvar
erschien vor langer Zeit, aber später als die andere und war im Grunde ein "Hack", um Compiler-Warnungen zum Schweigen zu bringen: Zur Ausführungszeit hatte sie überhaupt keine Wirkung, was als "Unfall" bedeutete dass das Stummschaltungsverhalten(defvar FOO)
nur auf die aktuelle Datei angewendet wurde (da der Compiler nicht wissen konnte, dass eine solche Defvar in einer anderen Datei ausgeführt wurde).Wenn
lexical-binding
in Emacs-24 eingeführt wurde, haben wir beschlossen, wieder verwenden diese(defvar FOO)
Form, aber das bedeutet , dass es jetzt tut eine Wirkung haben.Teilweise, um das vorherige Verhalten "wirkt sich nur auf die aktuelle Datei aus"
toto
beizubehalten , aber noch wichtiger, um zu ermöglichen, dass eine Bibliothek als Variable mit dynamischem Gültigkeitsbereich verwendet wird, ohne zu verhindern, dass andere Bibliothekentoto
als Variable mit lexikalischem Gültigkeitsbereich verwendet werden (normalerweise werden diese durch die Namenskonvention für Paketpräfixe vermieden Konflikte, aber es wird leider nicht überall verwendet), das neue Verhalten von(defvar FOO)
wurde so definiert, dass es nur für die aktuelle Datei gilt, und wurde sogar verfeinert, sodass es nur für den aktuellen Bereich gilt (z. B. wenn es innerhalb einer Funktion angezeigt wird, wirkt es sich nur auf die Verwendung von aus diese var innerhalb dieser Funktion).Grundsätzlich
(defvar FOO VAL)
und(defvar FOO)
sind nur zwei "völlig verschiedene" Dinge. Sie verwenden aus historischen Gründen zufällig dasselbe Schlüsselwort.quelle
(defvar FOO)
macht den neuen Modus viel kompatibler mit altem Code. Ein IIRC-Problem mit der CommonLisp-Lösung besteht auch darin, dass es für einen reinen Interpreter wie den von Elisp ziemlich kostspielig ist (z. B. muss man jedes Mal, wenn man einen bewertetlet
, in seinen Körper schauen, falls es einen gibtdeclare
, der einige der Vars betrifft).Aufgrund von Experimenten glaube ich, dass das Problem darin besteht, dass ein
(defvar VAR)
Initialisierungswert nur Auswirkungen auf die Bibliothek (en) hat, in denen er angezeigt wird.Beim Hinzufügen
(defvar my-dynamic-var)
zum*scratch*
Puffer ist der Fehler nicht mehr aufgetreten.Ich dachte ursprünglich, dies sei auf die Auswertung dieses Formulars zurückzuführen, bemerkte dann aber zunächst, dass es ausreicht , nur die Datei mit diesem Formular zu besuchen . und außerdem reichte es aus , nur dieses Formular im Puffer hinzuzufügen (oder zu entfernen), ohne es auszuwerten, um zu ändern, was bei der Auswertung
(let ((my-dynamic-var 5)) (f2))
innerhalb desselben Puffers mit passiert isteval-last-sexp
.(Ich habe kein wirkliches Verständnis dafür, was hier passiert. Ich finde das Verhalten überraschend, aber ich bin nicht mit den Details der Implementierung dieser Funktionalität vertraut.)
Ich werde hinzufügen, dass diese Form von
defvar
(ohne Init-Wert) den Byte-Compiler daran hindert, sich über die Verwendung einer extern definierten dynamischen Variablen in der zu kompilierenden elisp-Datei zu beschweren, aber allein diese Variable nicht verursachtboundp
; Daher wird die Variable nicht streng definiert. (Beachten Sie, dass , wenn die Variable istboundp
dann dieses Problem nicht auftreten würde.)In der Praxis Ich nehme an, dies zur Verfügung gestellt arbeiten ok wird , dass Sie es sind
(defvar my-dynamic-var)
in jedem lexikalisch-Bindungsbibliothek , die Ihr verwendetmy-dynamic-var
Variable (die vermutlich eine wirkliche Definition an anderer Stelle hätte).Bearbeiten:
Danke an den Zeiger von @npostavs in den Kommentaren:
Beides
eval-last-sexp
undeval-defun
verwendeneval-sexp-add-defvars
, um:Insbesondere sucht er alle
defvar
,defconst
unddefcustom
Instanzen. (Auch wenn ich auskommentiert bin, merke ich es.)Da hierdurch der Puffer zur Aufrufzeit durchsucht wird, wird erläutert, wie sich diese Formulare auch ohne Auswertung auf den Puffer auswirken können, und es wird bestätigt, dass das Formular in derselben Elisp-Datei (und auch früher als der auszuwertende Code) erscheinen muss. .
quelle
eval-sexp-add-defvars
im Puffertext nach Defvars gesucht wird.Ich kann das überhaupt nicht reproduzieren. Die Auswertung des letzteren Snippets funktioniert hier einwandfrei und gibt erwartungsgemäß 5 zurück. Sind Sie sicher, dass Sie nicht
my-dynamic-var
alleine bewerten ? Dies führt zu einem Fehler, da die Variable ungültig ist, nicht auf einen Wert festgelegt wurde und nur dann einen Wert hat, wenn Sie sie dynamisch an einen gebunden haben.quelle
lexical-binding
vor der Auswertung der Formulare Nicht-Null gesetzt? Ich erhalte das Verhalten, das Sie mitlexical-binding
nil beschreiben , aber wenn ich es auf non-nil setze, erhalte ich den Fehler void-variable.lexical-binding
die Formulare nacheinander festgelegt und ausgewertet wurden.my-dynamic-var
in Ihrer aktuellen Sitzung versehentlich einen dynamischen Wert auf höchster Ebene angegeben? Ich denke, das könnte es dauerhaft besonders markieren.