Supposons que j'ai un fichier nommé elisp-defvar-test.el
contenant:
;;; 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
Je charge ce fichier, puis je vais dans le tampon de travail et je lance:
(setq lexical-binding t)
(f1 5)
(let ((my-dynamic-var 5))
(f2))
(f1 5)
renvoie 5 comme prévu, indiquant que le corps de f1
est traité my-dynamic-var
comme une variable de portée dynamique, comme prévu. Cependant, le dernier formulaire donne une erreur de variable nulle pour my-dynamic-var
, indiquant qu'il utilise la portée lexicale pour cette variable. Cela semble en contradiction avec la documentation de defvar
, qui dit:
Le
defvar
formulaire déclare également la variable comme "spéciale", de sorte qu'elle est toujours liée dynamiquement même silexical-binding
est t.
Si je modifie le defvar
formulaire dans le fichier de test pour fournir une valeur initiale, la variable est toujours traitée comme dynamique, comme le dit la documentation. Quelqu'un peut-il expliquer pourquoi la portée d'une variable est déterminée par le fait d'avoir defvar
reçu ou non une valeur initiale lors de la déclaration de cette variable?
Voici la trace d'erreur, au cas où cela serait important:
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)
la source
Réponses:
La raison pour laquelle les deux sont traités différemment est principalement «parce que c'est ce dont nous avions besoin». Plus spécifiquement, la forme à un seul argument de
defvar
est apparue il y a longtemps, mais plus tard que l'autre et était fondamentalement un "hack" pour faire taire les avertissements du compilateur: au moment de l'exécution, cela n'avait aucun effet, donc comme un "accident" cela signifiait que le comportement de silence de(defvar FOO)
ne s'appliquait qu'au fichier actuel (puisque le compilateur n'avait aucun moyen de savoir qu'un tel defvar avait été exécuté dans un autre fichier).Quand
lexical-binding
a été introduit dans Emacs-24, nous avons décidé de réutiliser cette(defvar FOO)
forme, mais cela implique que maintenant le fait avoir un effet.En partie pour préserver le comportement précédent "n'affecte que le fichier actuel", mais plus important encore pour permettre à une bibliothèque de l'utiliser
toto
comme var à portée dynamique sans empêcher d'autres bibliothèques d'utilisertoto
comme var à portée lexicale (généralement la convention de dénomination package-prefix évite celles conflits, mais il n'est malheureusement pas utilisé partout), le nouveau comportement de a(defvar FOO)
été défini pour s'appliquer uniquement au fichier actuel, et a même été affiné pour ne s'appliquer qu'à la portée actuelle (par exemple, s'il apparaît dans une fonction, il n'affecte que les utilisations de qui var dans cette fonction).Fondamentalement,
(defvar FOO VAL)
et ne(defvar FOO)
sont que deux choses "complètement différentes". Ils utilisent simplement le même mot-clé pour des raisons historiques.la source
(defvar FOO)
rend le nouveau mode beaucoup plus compatible avec l'ancien code. De plus, l'IIRC un problème avec la solution de CommonLisp est qu'elle est assez coûteuse pour un interprète pur comme Elisp (par exemple, chaque fois que vous évaluez un,let
vous devez regarder à l'intérieur de son corps au cas où il y en aurait undeclare
qui affecterait certains des vars).Sur la base de l'expérimentation, je pense que le problème est que,
(defvar VAR)
sans valeur init, cela n'a d'effet que sur la ou les bibliothèques dans lesquelles il apparaît.Lorsque j'ai ajouté
(defvar my-dynamic-var)
au*scratch*
tampon, l'erreur ne s'est plus produite.À l'origine, je pensais que c'était à cause de l' évaluation de ce formulaire, mais j'ai d'abord remarqué que la simple visite du dossier avec ce formulaire était suffisante; et en outre, le simple fait d'ajouter (ou de supprimer) cette forme dans le tampon, sans l'évaluer, a suffi pour changer ce qui s'est passé lors de l'évaluation à l'
(let ((my-dynamic-var 5)) (f2))
intérieur de ce même tampon aveceval-last-sexp
.(Je ne comprends pas vraiment ce qui se passe ici. Je trouve le comportement surprenant, mais je ne connais pas les détails de la façon dont cette fonctionnalité est mise en œuvre.)
J'ajouterai que cette forme de
defvar
(sans valeur init) empêche le compilateur d'octets de se plaindre des utilisations d'une variable dynamique définie en externe dans le fichier elisp en cours de compilation, mais en soi, cela ne fait pas que cette variable soitboundp
; il ne s'agit donc pas de définir strictement la variable. (Notez que si la variable étaitboundp
alors ce problème ne se produirait pas du tout.)Dans la pratique , je suppose que cela va fonctionner ok à condition que vous n'inclure dans une bibliothèque lexicales de liaison qui utilise votre variable (qui aurait probablement une définition réelle ailleurs).
(defvar my-dynamic-var)
my-dynamic-var
Éditer:
Merci au pointeur de @npostavs dans les commentaires:
Les deux
eval-last-sexp
et leseval-defun
utilisereval-sexp-add-defvars
pour:Plus précisément , il localise tous
defvar
,defconst
et lesdefcustom
instances. (Même en commentant, je le remarque.)Comme il recherche le tampon au moment de l'appel, il explique comment ces formulaires peuvent avoir un effet dans le tampon même sans être évalué, et confirme que le formulaire doit apparaître dans le même fichier elisp (et aussi plus tôt que le code évalué) .
la source
eval-sexp-add-defvars
vérifie les defvars dans le texte du tampon.Je ne peux pas reproduire cela du tout, l'évaluation de ce dernier extrait fonctionne très bien ici et renvoie 5 comme prévu. Êtes-vous sûr de ne pas évaluer
my-dynamic-var
seul? Cela générera une erreur car la variable est nulle, elle n'a pas été définie sur une valeur et elle n'en aura qu'une si vous la liez dynamiquement à une.la source
lexical-binding
non nul avant d'évaluer les formulaires? J'obtiens le comportement que vous décrivez aveclexical-binding
nil, mais quand je le mets à non-nil, j'obtiens l'erreur void-variable.lexical-binding
est défini et évalué les formulaires de manière séquentielle.my-dynamic-var
une valeur dynamique de haut niveau dans votre session actuelle? Je pense que cela pourrait le marquer de façon permanente.