Comment forcer la réévaluation d'un defvar?

22

Supposons que j'ai un tampon lisp Emacs qui contient:

(defvar foo 1)

Si j'appelle eval-last-sexpou eval-buffer, fooest lié à 1. Si j'édite alors ce tampon à:

(defvar foo 2)

eval-last-sexpet eval-bufferne réexécutez pas cette ligne, il en fooest de même pour 1.

Cela est particulièrement difficile lorsqu'il existe plusieurs déclarations de ce type et je dois déterminer quelles lignes ne sont pas réévaluées.

J'ai regardé le redémarrage d'Emacs, puis (require 'foo), mais je dois faire attention à ne pas charger de fichiers .elc plus anciens.

Comment puis-je être absolument, positivement sûr que les variables et les fonctions définies dans le fichier actuel sont dans le même état que le chargement de code à nouveau dans une nouvelle instance Emacs?

Wilfred Hughes
la source
Vous ne pouvez pas être " absolument, positivement sûr qu'Emacs est dans un état qui est le même que le chargement du code à nouveau dans une nouvelle instance d'Emacs " sans faire juste cela. Si vous voulez être sûr uniquement de cette variable et d'autres variables globales , vous pouvez supprimer leurs valeurs en utilisant makunboundpuis réévaluer le code dans le tampon.
Drew
Bien sûr, des effets secondaires comme (code idiot), (incf emacs-major-version)je peux vivre avec se produire à plusieurs reprises. Je suis intéressé à pirater du code avec beaucoup de defvarformulaires.
Wilfred Hughes

Réponses:

30

Comme expliqué dans d'autres réponses, l'évaluation d'un defvarformulaire à l'aide de eval-last-sexpne réinitialise pas la valeur par défaut.

À la place, vous pouvez utiliser eval-defun(lié à C-M-xin emacs-lisp-modepar défaut), qui implémente le comportement que vous souhaitez comme exception spéciale:

Si le defun actuel est en fait un appel à defvarou defcustom, son évaluation de cette façon réinitialise la variable en utilisant son expression de valeur initiale même si la variable a déjà une autre valeur. (Normalement defvaret defcustomne modifiez pas la valeur s'il y en a déjà une.)


Si vous devez évaluer le contenu complet d'un tampon, vous pouvez écrire une fonction qui parcourt tour à tour les formulaires de niveau supérieur et appelle eval-defunchacun d'eux. Quelque chose comme ça devrait fonctionner:

(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
la source
5
Telle est la réponse. Pas besoin de faux defvars ou de setqs supplémentaires. Utilisez simplement eval-defunau lieu de eval-last-sexp . Vous pouvez même écrire une fonction qui appelle eval-defuntous les formulaires du tampon et l'utiliser à la place de eval-buffer.
Malabarba
1
@Malabarba Ce message est loin de répondre à la question. Utilisez eval-defunau lieu de eval-last-sexp, bien sûr, mais la difficulté est pour eval-buffer.
Gilles 'SO- arrête d'être méchant'
@ Gilles oui, vous avez raison. J'ai ajouté une implémentation provisoire de l'idée de @ Malabara d'appeler eval-defunchaque formulaire de niveau supérieur dans le tampon.
ffevotte
1
Cette approche ne semble pas fonctionner si le defvarn'est pas à l'intérieur d'un defun. Exemple: (progn (defvar foo "bar")).
Kaushal Modi
2
@kaushalmodi Les derniers exemples que vous citez (variables stockant des expressions régulières) ressemblent beaucoup à des candidats defconst(qui sont toujours réévalués). Il y a récemment eu un article très instructif sur ce sujet entre parenthèses sans fin
ffevotte
5

Comme le disent les autres réponses, c'est exactement ainsi que fonctionne defvar, mais vous pouvez le contourner, c'est clair après tout.

Vous pouvez redéfinir temporairement le fonctionnement de defvar si vous le souhaitez et pendant ce temps, rechargez les packages que vous souhaitez réinitialiser.

J'ai écrit une macro où lors de l'évaluation du corps, les valeurs de defvars seront toujours réévaluées.

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

Exemple d'utilisation:

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

Remarque: ceci ne doit être utilisé que dans le but de réévaluer les defvars, car il ignore simplement les docstrings lors de la réévaluation. Vous pouvez modifier la macro pour prendre en charge la réévaluation qui applique également les docstrings, mais je vous laisse le soin de le faire.

Dans votre cas, vous pourriez faire

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

Mais sachez ce que font ceux qui écrivent elisp en s'attendant à ce que defvar fonctionne comme spécifié, il se peut qu'ils utilisent defvar pour définir et setq dans une fonction init pour spécifier la valeur, de sorte que vous pouvez finir par des variables nulles que vous n'avez pas l'intention mais c'est probablement rare.

Mise en œuvre alternative

En utilisant cela, vous pouvez simplement redéfinir defvar globalement et contrôler s'il définira ou non la valeur du symbole sur l'argument INIT-VALUE même si le symbole est défini en modifiant la valeur du nouveau defvar-always-reeval-valuessymbole.

;; 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
la source
1
Je ne suis pas sûr que redéfinir le comportement de defvarsoit une bonne idée: il existe plusieurs utilisations possibles de defvar, avec une sémantique légèrement différente. Par exemple, une forme dont votre macro ne tient pas compte est le (defvar SYMBOL)formulaire, qui est utilisé pour informer le compilateur d'octets de l'existence d'une variable sans définir de valeur.
ffevotte
Si vous devez absolument redéfinir defvaravec une macro, vous feriez probablement mieux de préfixer le defvarformulaire d' origine avec un makunbound, plutôt que de le remplacer par setq.
ffevotte
Oui, c'est une idée terrible et ne devrait être utilisée que pour des choses comme la réévaluation des defvars d'un paquet chargé dans votre tampon de travail, vous ne devriez jamais expédier quelque chose comme ça.
Jordon Biondo
@Francesco aussi vous avez raison sur la version makunbound, je l'avais implémentée mais en m'éloignant de l'idée, j'ai mis ce code sur ma réponse comme alternative.
Jordon Biondo
3

Le defvarest en cours d'évaluation et fait exactement ce que vous avez spécifié. Cependant, defvarne définit qu'une valeur initiale:

L'argument facultatif INITVALUE est évalué et utilisé pour définir SYMBOL, uniquement si la valeur de SYMBOL est nulle.

Donc, pour obtenir ce que vous voulez, vous devez soit délier la variable avant de réévaluer, par exemple

(makunbound 'foo)

ou utilisez setqpour régler la valeur, par ex.

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

Si vous n'avez pas besoin de spécifier ici une docstring, vous pouvez ignorer le defvartout.

Si vous voulez vraiment utiliser defvaret dissocier automatiquement cela, vous devrez écrire une fonction pour trouver les defvarappels dans le tampon actuel (ou région, ou dernier sexp, etc.); appeler makunboundpour chacun; puis faites l'évaluation réelle.

glucas
la source
J'étais en train de jouer avec un eval-bufferwrapper qui délierait tout d'abord, mais la réponse de @ Francesco eval-defunest vraiment ce que vous voulez.
glucas
1

La macro suivante a été créée en traçant eval-defunses fonctions de prise en charge et en la modifiant de sorte qu'il n'est plus nécessaire d'évaluer une région d'un tampon particulier. J'avais besoin d'aide dans le sujet connexe Conversion d'une expression en chaîne , et @Tobias est venu à la rescousse - m'enseignant comment convertir la fonction imparfaite en macro. Je ne pense pas que nous devions eval-sexp-add-defvarsprécéder elisp--eval-defun-1, mais si quelqu'un pense que c'est important, faites-le moi savoir.

;;; 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))
liste des lois
la source
0

Le problème n'est pas que la ligne ne soit pas réévaluée. Le problème est que defvardéfinit une variable et sa valeur par défaut . Si une variable existe déjà, la modification de sa valeur par défaut ne modifie pas la valeur actuelle. Malheureusement, je pense que vous devrez exécuter un setqpour chaque variable dont vous souhaitez mettre à jour la valeur.

Cela peut être exagéré, mais vous pouvez mettre à jour votre fichier comme celui-ci si vous souhaitez pouvoir facilement mettre fooà jour sa nouvelle valeur par défaut.

(defvar foo 2)
(setq foo 2)

mais cela nécessite que vous conserviez la valeur par défaut à deux endroits dans votre code. Vous pouvez également faire ceci:

(makunbound 'foo)
(defvar foo 2)

mais s'il y a une chance qui fooest déclarée ailleurs, vous pouvez avoir des effets secondaires à gérer.

nispio
la source
C'est difficile lorsque vous essayez de tester les modifications d'un mode complexe. Je préfère ne pas changer le code. eval-defuntraite defvarspécialement, alors il y a sûrement quelque chose de similaire pour les tampons entiers?
Wilfred Hughes
@WilfredHughes Je ne sais pas trop ce que vous entendez par "quelque chose pour des tampons entiers". Vous voulez une fonction unique qui makunbounddéclarera toutes les variables dans le tampon actuel, puis la réévaluera? Vous pouvez écrire le vôtre, mais je ne pense pas qu'il existe une fonction prête à l'emploi pour cela. EDIT: Peu importe, je comprends ce que vous dites. Un eval-defunqui fonctionne sur tout le tampon. Il semble que @JordonBiondo ait votre solution pour cela.
nispio
Non. Le problème est le manque de réévaluation: defvarne fait rien si la variable a déjà une valeur (comme le dit son doc:) The optional argument INITVALUE is evaluated, and used to set SYMBOL, only if SYMBOL's value is void.. Le problème n'est pas que defvarchange la valeur par défaut et non la valeur actuelle. (defvar a 4) (default-value 'a) (setq a 2) (default-value 'a); puis C-x C-eaprès le defvarsexp; alors (default-value 'a). C-x C-e, eval-regionEt similaire sur un defvarsexp ne pas changer la valeur par défaut.
Drew