Załóżmy, że mam plik o nazwie 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
Ładuję ten plik, a następnie przechodzę do bufora scratch i uruchamiam:
(setq lexical-binding t)
(f1 5)
(let ((my-dynamic-var 5))
(f2))
(f1 5)
zwraca 5 zgodnie z oczekiwaniami, co wskazuje, że treść f1
traktowana jest my-dynamic-var
jak zmienna o dynamicznym zasięgu, zgodnie z oczekiwaniami. Jednak ostatnia forma podaje błąd zmiennej void dla my-dynamic-var
, wskazując, że używa ona leksykalnego zakresu dla tej zmiennej. Wydaje się to sprzeczne z dokumentacją defvar
, która mówi:
defvar
Forma deklaruje również zmienną jako „Special”, tak, że zawsze jest związany dynamicznie nawet jeślilexical-binding
jest t.
Jeśli zmienię defvar
formularz w pliku testowym, aby podać wartość początkową, zmienna jest zawsze traktowana jako dynamiczna, jak głosi dokumentacja. Czy ktoś może wyjaśnić, dlaczego zakres zmiennej zależy od tego, czy defvar
podczas deklarowania tej zmiennej podano wartość początkową?
Oto ślad śledzenia błędu, na wypadek, gdyby miał on znaczenie:
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)
źródło
Odpowiedzi:
To, dlaczego oboje są traktowani inaczej, jest głównie „ponieważ tego potrzebowaliśmy”. Mówiąc dokładniej, forma jednoparlamentarna
defvar
pojawiła się dawno temu, ale później niż druga i była w zasadzie „hackem” do wyciszenia ostrzeżeń kompilatora: w czasie wykonywania nie miała żadnego efektu, więc jako „wypadek” oznaczała że zachowanie wyciszania(defvar FOO)
dotyczyło tylko bieżącego pliku (ponieważ kompilator nie mógł wiedzieć, że taki defvar został wykonany w innym pliku).Kiedy
lexical-binding
został wprowadzony w Emacs-24, zdecydowaliśmy się ponownie wykorzystać tę(defvar FOO)
formę, ale to oznacza, że teraz nie mają wpływu.Częściowo w celu zachowania poprzedniego zachowania „wpływa tylko na bieżący plik”, ale co ważniejsze, aby biblioteka mogła używać
toto
jako var dynamicznie skalowanego, nie uniemożliwiając innym bibliotekom używaniatoto
jako var zmiennego leksykalnie (zazwyczaj konwencja nazewnictwa prefiksów pakietu unika takich konflikty, ale niestety nie wszędzie się to stosuje), nowe zachowanie(defvar FOO)
zdefiniowano tak, aby dotyczyło tylko bieżącego pliku, a nawet udoskonalono, więc dotyczy tylko bieżącego zakresu (np. jeśli pojawia się w funkcji, wpływa tylko na użycie które różnią się w ramach tej funkcji).Zasadniczo
(defvar FOO VAL)
i(defvar FOO)
są to tylko dwie „zupełnie różne” rzeczy. Zdarza się, że używają tego samego słowa kluczowego ze względów historycznych.źródło
(defvar FOO)
sprawia, że nowy tryb jest znacznie bardziej kompatybilny ze starym kodem. Ponadto, IIRC stanowi problem z rozwiązaniem CommonLisp, ponieważ jest dość kosztowny dla takiego interpretera jak Elisp (np. Za każdym razem, gdylet
oceniasz, musisz zajrzeć do jego ciała, na wypadek, gdyby miało todeclare
wpływ na niektóre zmienne).Na podstawie eksperymentów uważam, że problem polega na tym, że
(defvar VAR)
brak wartości początkowej ma wpływ tylko na biblioteki, w których się pojawia.Po dodaniu
(defvar my-dynamic-var)
do*scratch*
bufora błąd już nie występował.Początkowo myślałem, że było to spowodowane oceną tego formularza, ale potem zauważyłem po pierwsze, że wystarczyło przejrzenie pliku z obecnym formularzem; a ponadto samo dodanie (lub usunięcie) tej formy w buforze, bez oceny, wystarczyło zmienić to, co się stało podczas oceny
(let ((my-dynamic-var 5)) (f2))
w tym samym buforze za pomocąeval-last-sexp
.(Nie do końca rozumiem, co się tutaj dzieje. Uważam to zachowanie za zaskakujące, ale nie jestem zaznajomiony ze szczegółami implementacji tej funkcji.)
Dodam, że ta forma
defvar
(bez wartości początkowej) zapobiega narzekaniu przez kompilator bajtów na użycie zewnętrznej zmiennej dynamicznej w kompilowanym pliku elisp, ale sama w sobie nie powoduje, że ta zmienna jestboundp
; więc nie jest to ścisłe definiowanie zmiennej. (Zauważ, że gdyby zmienna była,boundp
to problem w ogóle nie wystąpiłby).W praktyce przypuszczam to będzie działać na OK pod warunkiem, że nie zawierają
(defvar my-dynamic-var)
w każdej bibliotece leksykalnym wiążącego, który wykorzystuje swojąmy-dynamic-var
zmienną (która przypuszczalnie będzie miało prawdziwą definicję gdzie indziej).Edytować:
Dzięki wskaźnikowi z @npostavs w komentarzach:
Zarówno
eval-last-sexp
ieval-defun
używaćeval-sexp-add-defvars
w celu:Konkretnie to lokalizuje wszystkie
defvar
,defconst
idefcustom
instancje. (Nawet gdy jestem komentowany, zauważam.)Ponieważ przeszukuje bufor w czasie połączenia, wyjaśnia, w jaki sposób te formularze mogą mieć wpływ na bufor nawet bez oceny, i potwierdza, że formularz musi pojawić się w tym samym pliku elisp (a także wcześniej niż kod poddawany ocenie) .
źródło
eval-sexp-add-defvars
sprawdza defvary w tekście bufora.Nie mogę tego w ogóle odtworzyć, ocena tego ostatniego fragmentu działa tutaj dobrze i zwraca 5 zgodnie z oczekiwaniami. Czy na pewno nie oceniasz
my-dynamic-var
samodzielnie? Spowoduje to błąd, ponieważ zmienna jest nieważna, nie została ustawiona na wartość i będzie miała tylko jedną, jeśli dynamicznie powiążesz ją z jedną.źródło
lexical-binding
inną niż zero przed oceną formularzy? Otrzymuję zachowanie, którelexical-binding
opisujesz jako zero, ale kiedy ustawię to na zero, pojawia się błąd zmiennej void.lexical-binding
jest ustawiony, i przeanalizowałem formularze sekwencyjnie.my-dynamic-var
wartość dynamiczną najwyższego poziomu w bieżącej sesji? Myślę, że to może oznaczać, że jest to trwale wyjątkowe.