Wie erzwinge ich eine Neubewertung einer Defvar?

21

Angenommen, ich habe einen Emacs-Lisp-Puffer, der Folgendes enthält:

(defvar foo 1)

Wenn ich eval-last-sexpoder anrufe eval-buffer, fooist gebunden an 1. Wenn ich dann diesen Puffer bearbeite an:

(defvar foo 2)

eval-last-sexpund eval-bufferführe diese Zeile nicht erneut aus, es fooist also immer noch 1.

Dies ist besonders schwierig, wenn es mehrere solcher Aussagen gibt und ich nachverfolgen muss, welche Zeilen nicht neu bewertet werden.

Ich habe nur nach einem Neustart von Emacs gesucht und dann (require 'foo), aber dann muss ich vorsichtig sein, um das Laden älterer .elc-Dateien zu vermeiden.

Wie kann ich absolut sicher sein, dass sich die in der aktuellen Datei definierten Variablen und Funktionen im selben Zustand befinden wie das erneute Laden von Code in einer neuen Emacs-Instanz?

Wilfred Hughes
quelle
Sie können nicht " absolut sicher sein, dass sich Emacs in einem Zustand befindet, der dem erneuten Laden des Codes in einer neuen Emacs-Instanz gleicht ", ohne dies zu tun. Wenn Sie sicherstellen möchten, dass nur diese und andere globale Variablen verwendet werden , können Sie ihre Werte mit entfernen makunboundund den Code im Puffer neu auswerten.
Drew
Klar, mit Nebenwirkungen wie (albernem Code) (incf emacs-major-version)kann ich immer wieder leben. Ich bin daran interessiert, Code mit vielen defvarFormularen zu hacken .
Wilfred Hughes

Antworten:

29

Wie in anderen Antworten erläutert, wird durch die Auswertung eines defvarFormulars mit eval-last-sexpnicht der Standardwert zurückgesetzt.

Stattdessen können Sie eval-defun( standardmäßig an C-M-xin gebunden emacs-lisp-mode) verwenden, um das gewünschte Verhalten als besondere Ausnahme zu implementieren:

Wenn die aktuelle Definition tatsächlich ein Aufruf von defvaroder defcustomist und auf diese Weise ausgewertet wird, wird die Variable unter Verwendung ihres Anfangswertausdrucks zurückgesetzt, auch wenn die Variable bereits einen anderen Wert hat. (Normalerweise defvarund defcustomändern Sie den Wert nicht, wenn bereits einer vorhanden ist.)


Wenn Sie den vollständigen Inhalt eines Puffers auswerten müssen, können Sie eine Funktion schreiben, die die Formulare der obersten Ebene nacheinander durchläuft und eval-defunjedes aufruft . So etwas sollte funktionieren:

(defun my/eval-buffer ()
  "Execute the current buffer as Lisp code.
Top-level forms are evaluated with `eval-defun' so that `defvar'
and `defcustom' forms reset their default values."
  (interactive)
  (save-excursion
    (goto-char (point-min))
    (while (not (eobp))
      (forward-sexp)
      (eval-defun nil))))
ffevotte
quelle
5
Das ist die Antwort. Keine Notwendigkeit für gefälschte Defvars oder zusätzliche Setqs. Verwenden Sie eval-defunstatt eval-last-sexp . Sie können sogar eine Funktion schreiben, die eval-defunjedes Formular im Puffer aufruft und stattdessen verwendet eval-buffer.
Malabarba
1
@Malabarba Dieser Beitrag ist weit hinter der Beantwortung der Frage zurück. Verwenden Sie eval-defunstatt eval-last-sexp, sicher, aber die Schwierigkeit ist für eval-buffer.
Gilles 'SO - hör auf böse zu sein'
@ Gilles ja, du hast recht. Ich habe eine vorläufige Implementierung von @ Malabaras Idee hinzugefügt, eval-defunjedes Formular der obersten Ebene im Puffer aufzurufen .
ffevotte
1
Dieser Ansatz scheint nicht zu funktionieren, wenn sich das defvarnicht in einem befindet defun. Beispiel: (progn (defvar foo "bar")).
Kaushal Modi,
2
@kaushalmodi Die zuletzt genannten Beispiele (Variablen, in denen reguläre Ausdrücke gespeichert sind) ähneln Kandidaten defconst(die immer neu bewertet werden). Kürzlich gab es einen sehr aufschlussreichen Beitrag zu diesem Thema in endlosen Klammern
ffevotte
5

Wie die anderen Antworten sagen, funktioniert defvar so, aber man kann es umgehen, das ist doch ein Elisp.

Sie können die Funktionsweise von defvar vorübergehend neu definieren und in dieser Zeit die Pakete neu laden, die Sie zurücksetzen möchten.

Ich habe ein Makro geschrieben, in dem während der Auswertung des Körpers defvars-Werte immer neu bewertet werden.

(defmacro my-fake-defvar (name value &rest _)
  "defvar impersonator that forces reeval."
  `(progn (setq ,name ,value)
          ',name))

(defmacro with-forced-defvar-eval (&rest body)
  "While evaluating, any defvars encountered are reevaluated"
  (declare (indent defun))
  (let ((dv-sym (make-symbol "old-defvar")))
    `(let ((,dv-sym (symbol-function 'defvar)))
       (unwind-protect
           (progn
             (fset 'defvar (symbol-function 'my-fake-defvar))
             ,@body)
         (fset 'defvar ,dv-sym)))))

Beispiel Verwendung:

file_a.el

(defvar my-var 10)

file_b.el

(with-forced-defvar-eval
  (load-file "file_a.el")
  (assert (= my-var 10))
  (setq my-var 11)
  (assert (= my-var 11)
  (load-file "file_a.el")
  (assert (= my-var 10))

Hinweis: Dies sollte nur zum Zwecke der Neubewertung von Defvars verwendet werden, da Docstrings bei der Neubewertung nur ignoriert werden. Sie können das Makro so ändern, dass es auch die erneute Auswertung von Dokumentzeichenfolgen unterstützt, aber das überlasse ich Ihnen.

In Ihrem Fall könnten Sie tun

(with-forced-defvar-eval (require 'some-package))

Aber wissen Sie, was diejenigen, die elisp schreiben, tun, wenn sie erwarten, dass defvar wie angegeben funktioniert. Es könnte sein, dass sie defvar verwenden, um in einer Init-Funktion den Wert zu definieren und setq zu setzen, so dass Sie möglicherweise keine Variablen erhalten, die Sie nicht beabsichtigen, aber das ist wahrscheinlich selten.

Alternative Implementierung

Mit dieser Option können Sie defvar einfach global neu definieren und steuern, ob der Wert des Symbols auf das INIT-VALUE-Argument gesetzt wird, auch wenn das Symbol durch Ändern des Werts des neuen defvar-always-reeval-valuesSymbols definiert wird.

;; save the original defvar definition
(fset 'original-defvar (symbol-function 'defvar))

(defvar defvar-always-reeval-values nil
  "When non-nil, defvar will reevaluate the init-val arg even if the symbol is defined.")

(defmacro my-new-defvar (name &optional init-value docstring)
  "Like defvar, but when `defvar-always-reeval-values' is non-nil, it will set the symbol's value to INIT-VALUE even if the symbol is defined."
  `(progn
     (when defvar-always-reeval-values (makunbound ',name))
     (original-defvar ,name ,init-value ,docstring)))

;; globally redefine defvar to the new form
(fset 'defvar (symbol-function 'my-new-defvar))
Jordon Biondo
quelle
1
Ich bin mir nicht sicher, ob eine Neudefinition des Verhaltens defvareine gute Idee ist: Es gibt verschiedene Verwendungsmöglichkeiten defvarmit leicht unterschiedlicher Semantik. Eine Verwendung, die Ihr Makro nicht berücksichtigt, ist beispielsweise das (defvar SYMBOL)Formular, mit dem der Byte-Compiler über das Vorhandensein einer Variablen informiert wird, ohne einen Wert festzulegen.
ffevotte
Wenn Sie unbedingt eine Neudefinition defvarmit einem Makro vornehmen müssen, ist es wahrscheinlich besser, dem ursprünglichen defvarFormular ein Präfix voranzustellen makunbound, als es durch ein zu ersetzen setq.
ffevotte
Ja, es ist eine schreckliche Idee und sollte nur für Dinge wie die Neubewertung der Defvars eines geladenen Pakets in Ihrem Arbeitspuffer verwendet werden. Sie sollten niemals so etwas versenden.
Jordon Biondo
@Francesco auch du hast recht mit der makunbound version, ich hatte das implementiert aber bin von der idee abgekommen, ich habe diesen code auf meine antwort als alternative gesetzt.
Jordon Biondo
3

Der defvarwird ausgewertet und tut genau das, was Sie angegeben haben. Setzt jedoch defvarnur einen Anfangswert:

Das optionale Argument INITVALUE wird nur ausgewertet und zum Festlegen von SYMBOL verwendet, wenn der Wert von SYMBOL ungültig ist.

Um das zu erreichen, was Sie möchten, müssen Sie entweder die Bindung der Variablen aufheben, bevor Sie sie erneut auswerten, z

(makunbound 'foo)

oder setqmit den Wert einstellen, z

(defvar foo nil "My foo variable.")
(setq foo 1)

Wenn Sie hier keinen Docstring angeben müssen, können Sie den gesamten Text überspringen defvar.

Wenn Sie dies wirklich verwenden defvarund automatisch aufheben möchten, müssen Sie eine Funktion schreiben, um defvarAufrufe im aktuellen Puffer (oder in der Region oder im letzten Sexp usw.) zu finden. rufe makunboundnach jedem; und dann die eigentliche Auswertung machen.

Glucas
quelle
Ich habe mit einem eval-bufferWrapper gespielt, der zuerst alle Bindungen aufhebt, aber @ Francescos Antwort darauf eval-defunist genau das, was Sie wollen.
Glucas
1

Das folgende Makro wurde erstellt, indem eval-defunauf seine unterstützenden Funktionen zurückgegriffen und diese so geändert wurden, dass es nicht mehr erforderlich ist, einen Bereich eines bestimmten Puffers auszuwerten. Ich brauchte Hilfe in dem verwandten Thread. Einen Ausdruck in einen String umwandeln, und @Tobias kam zur Hilfe, um mir beizubringen, wie man die unvollständige Funktion in ein Makro umwandelt. Ich glaube nicht, dass wir eval-sexp-add-defvarsvorangehen müssen elisp--eval-defun-1, aber wenn jemand der Meinung ist, dass dies wichtig ist, lassen Sie es mich bitte wissen.

;;; EXAMPLE:
;;;   (defvar-reevaluate
;;;     (defvar undo-auto--this-command-amalgamating "hello-world"
;;;     "My new doc-string."))

(defmacro defvar-reevaluate (input)
"Force reevaluation of defvar."
  (let* ((string (prin1-to-string input))
        (form (read string))
        (form (elisp--eval-defun-1 (macroexpand form))))
    form))
Gesetzesliste
quelle
0

Das Problem ist nicht, dass die Leitung nicht neu bewertet wird. Das Problem besteht darin, dass defvareine Variable und ihr Standardwert definiert werden . Wenn eine Variable bereits vorhanden ist, wird durch Ändern ihres Standardwerts der aktuelle Wert nicht geändert. Leider denke ich, dass Sie eine setqfür jede Variable ausführen müssen, deren Wert Sie aktualisieren möchten.

Dies mag ein Overkill sein, aber Sie können Ihre Datei auf diese Weise aktualisieren, wenn Sie einfach fooauf den neuen Standardwert aktualisieren möchten .

(defvar foo 2)
(setq foo 2)

Dazu müssen Sie jedoch den Standardwert an zwei Stellen im Code beibehalten. Sie könnten dies auch tun:

(makunbound 'foo)
(defvar foo 2)

Aber wenn es eine Möglichkeit foogibt, die an anderer Stelle deklariert wird, müssen Sie möglicherweise mit einigen Nebenwirkungen fertig werden.

Nispio
quelle
Das ist schwierig, wenn Sie versuchen, Änderungen an einem komplexen Modus zu testen. Ich möchte den Code lieber nicht ändern. speziell eval-defunbehandelt defvar, also gibt es sicher etwas ähnliches für ganze puffer?
Wilfred Hughes
@ Wilfred Hughes Ich bin nicht sicher, was Sie mit "etwas für ganze Puffer" meinen. Sie möchten eine einzelne Funktion, mit der makunboundalle im aktuellen Puffer deklarierten Variablen neu ausgewertet werden? Sie könnten Ihre eigene schreiben, aber ich glaube nicht, dass es eine Out-of-the-Box-Funktion dafür gibt. EDIT: Egal, ich verstehe, was Sie sagen. Und eval-defundas funktioniert über den gesamten Puffer. Es sieht so aus, als ob @JordonBiondo Ihre Lösung dafür hat.
Nispio
Nein. Das Problem ist das Fehlen einer erneuten Bewertung: defvarTut nichts, wenn die Variable bereits einen Wert hat (wie im Dokument angegeben:) The optional argument INITVALUE is evaluated, and used to set SYMBOL, only if SYMBOL's value is void.. Das Problem ist nicht, dass defvarsich der Standardwert und nicht der aktuelle Wert ändert. (defvar a 4) (default-value 'a) (setq a 2) (default-value 'a); dann C-x C-enach dem defvarsexp; dann (default-value 'a). C-x C-e, eval-regionUnd dergleichen auf einem defvarsexp Sie nicht den Standardwert ändern.
Drew