Jak wymusić ponowną ocenę defvar?

21

Załóżmy, że mam bufor Lisp Emacsa, który zawiera:

(defvar foo 1)

Jeśli zadzwonię eval-last-sexplub eval-buffer, foozostanie przypisane do 1. Jeśli następnie zmodyfikuję ten bufor do:

(defvar foo 2)

eval-last-sexpi eval-buffernie wykonuj ponownie tej linii, więc foonadal jest 1.

Jest to szczególnie trudne, gdy istnieje wiele takich stwierdzeń i muszę wyśledzić, które wiersze nie podlegają ponownej ocenie.

(require 'foo)Patrzyłem na ponowne uruchomienie Emacsa , ale potem musiałem uważać, aby nie załadować żadnych starszych plików .elc.

Jak mogę mieć absolutną, pozytywną pewność, że zmienne i funkcje zdefiniowane w bieżącym pliku są w tym samym stanie, co ładowanie kodu na nowo w nowej instancji Emacsa?

Wilfred Hughes
źródło
Nie możesz być „ absolutnie, pozytywnie pewien, że Emacs jest w stanie, który jest taki sam, jak ponowne ładowanie kodu w nowej instancji Emacsa ”, nie robiąc tego. Jeśli chcesz mieć pewność, że zapisujesz tylko te i inne zmienne globalne , możesz usunąć ich wartości, używając, makunbounda następnie ponownie ocenić kod w buforze.
Drew,
Jasne, efekty uboczne takie jak (głupi kod) (incf emacs-major-version)Mogę żyć z powtarzaniem się. Interesuje mnie hackowanie kodu za pomocą wielu defvarformularzy.
Wilfred Hughes

Odpowiedzi:

29

Jak wyjaśniono w innych odpowiedziach, ocena defvarformularza za pomocą eval-last-sexpnie resetuje wartości domyślnej.

Zamiast tego można użyć eval-defun(związany C-M-xw emacs-lisp-modedomyślnie), który implementuje zachowanie chcesz jako wyjątek specjalny:

Jeśli bieżący defun jest w rzeczywistości wywołaniem defvarlub defcustom, jego ocena w ten sposób resetuje zmienną przy użyciu wyrażenia wartości początkowej, nawet jeśli zmienna ma już inną wartość. (Normalnie defvari defcustomnie zmieniają wartości, jeśli istnieją już jest jeden).


Jeśli musisz ocenić pełną zawartość bufora, możesz napisać funkcję, która przechodzi kolejno do formularzy najwyższego poziomu i wywołuje eval-defunkażdą z nich. Coś takiego powinno działać:

(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
źródło
5
Oto odpowiedź. Nie potrzeba fałszywych defvarów ani dodatkowych setqs. Po prostu użyj eval-defunzamiast eval-last-sexp . Możesz nawet napisać funkcję, która wywołuje eval-defunkażdą formę w buforze i używać jej zamiast eval-buffer.
Malabarba
1
@Malabarba Ten post jest daleki od odpowiedzi na pytanie. Użyj eval-defunzamiast eval-last-sexp, oczywiście, ale trudność jest dla eval-buffer.
Gilles „SO- przestań być zły”
@Gilles tak, masz rację. Dodałem wstępną implementację pomysłu @ Malabara, aby wywoływać eval-defunkażdą formę najwyższego poziomu w buforze.
ffevotte
1
To podejście nie działa, jeśli defvarnie ma go w defun. Przykład: (progn (defvar foo "bar")).
Kaushal Modi
2
@kaushalmodi Te ostatnie przykłady, które przytaczasz (zmienne przechowujące wyrażenia regularne) wyglądają bardzo podobnie do kandydatów defconst(które są zawsze ponownie oceniane). Niedawno pojawił się bardzo pouczający post na ten temat w niekończących się nawiasach
ffevotte
5

Tak jak inne odpowiedzi mówią, tak działa defvar, ale można to obejść, w końcu jest elisp.

Możesz tymczasowo zmienić sposób działania defvar, jeśli chcesz, i w tym czasie ponownie załaduj pakiety, które chcesz zresetować.

Napisałem makro, w którym podczas oceny ciała wartości defvars zawsze będą przewartościowane.

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

Przykładowe użycie:

plik_a.el

(defvar my-var 10)

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

Uwaga: należy tego używać wyłącznie w celu ponownej oceny defvarów, ponieważ po prostu ignoruje ona dokumenty podczas ponownej oceny. Możesz zmodyfikować makro w celu obsługi ponownej oceny, która dotyczy również dokumentów, ale pozostawię to tobie.

W twoim przypadku możesz to zrobić

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

Ale wiedz, co robią ci, którzy piszą elisp, oczekując, że defvar będzie działał zgodnie ze specyfikacją, może być tak, że używają defvar do definiowania i setq w jakiejś funkcji init, aby określić wartość, więc możesz skończyć na zerowych zmiennych, których nie zamierzasz, ale jest to prawdopodobnie rzadkie.

Alternatywne wdrożenie

Za pomocą tego możesz po prostu przedefiniować globalnie defvar i kontrolować, czy ustawi wartość symbolu na wartość INIT-VALUE arg, nawet jeśli symbol jest zdefiniowany przez zmianę wartości nowego defvar-always-reeval-valuessymbolu.

;; 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
źródło
1
Nie jestem pewien, czy przedefiniowanie zachowania defvarjest dobrym pomysłem: istnieje kilka różnych możliwych zastosowań defvar, z nieco inną semantyką. Na przykład jednym z zastosowań, których nie uwzględnia twoje makro, jest (defvar SYMBOL)formularz, który służy do informowania kompilatora bajtów o istnieniu zmiennej bez ustawiania wartości.
ffevotte
Jeśli absolutnie potrzebujesz przedefiniować defvarza pomocą makra, prawdopodobnie lepiej byłoby przedrostkować oryginalny defvarformularz makunboundzamiast, zamiast go zastąpić setq.
ffevotte
Tak, to okropny pomysł i powinien być używany tylko do takich celów, jak ponowne sprawdzenie defvars załadowanej paczki w buforze scratch, nigdy nie powinieneś wysyłać czegoś takiego.
Jordon Biondo
@Francesco również masz rację co do wersji makunbound, wdrożyłem to, ale odszedłem od pomysłu, umieściłem ten kod na mojej odpowiedzi jako alternatywę.
Jordon Biondo
3

defvarJest oceniany i robi dokładnie to, co już określona. Jednak defvarustawia tylko wartość początkową:

Opcjonalny argument INITVALUE jest analizowany i używany do ustawienia SYMBOL, tylko jeśli wartość SYMBOL jest nieważna.

Aby więc osiągnąć to, co chcesz, musisz albo odłączyć zmienną przed ponowną oceną, np

(makunbound 'foo)

lub użyj, setqaby ustawić wartość, np

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

Jeśli nie musisz tutaj określać dokumentu, możesz go defvarcałkowicie pominąć .

Jeśli naprawdę chcesz tego użyć defvari automatycznie cofnąć powiązanie, musisz napisać funkcję, aby znaleźć defvarwywołania w bieżącym buforze (lub regionie, lub ostatnim sexp itp.); wezwać makunboundkażdego; a następnie wykonaj rzeczywistą ewaluację.

glucas
źródło
eval-bufferBawiłem się opakowaniem, które najpierw rozwiąże wszystko, ale odpowiedź @ Francesco eval-defunjest naprawdę tym, czego chcesz.
glucas
1

Poniższe makro zostało utworzone poprzez śledzenie eval-defunjego funkcji pomocniczych i modyfikowanie go, aby nie było już konieczne ocenianie regionu określonego bufora. Potrzebowałem pomocy w powiązanym wątku Konwertowanie wyrażenia na ciąg znaków , a @Tobias przybyło na ratunek - ucząc mnie, jak przekształcić niedoskonałą funkcję w makro. Nie sądzę, że musimy eval-sexp-add-defvarspoprzedzać elisp--eval-defun-1, ale jeśli ktoś uważa, że ​​to ważne, daj mi znać.

;;; 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))
lista prawnicza
źródło
0

Problemem nie jest to, że linia nie podlega ponownej ocenie. Problem polega na tym, że defvardefiniuje zmienną i jej wartość domyślną . Jeśli zmienna już istnieje, zmiana jej wartości domyślnej nie modyfikuje bieżącej wartości. Niestety myślę, że będziesz musiał uruchomić setqdla każdej zmiennej, której wartość chcesz zaktualizować.

Może to być przesada, ale możesz zaktualizować plik w ten sposób, jeśli chcesz mieć możliwość łatwej aktualizacji foodo nowej wartości domyślnej.

(defvar foo 2)
(setq foo 2)

ale wymaga to zachowania domyślnej wartości w dwóch miejscach w kodzie. Możesz także to zrobić:

(makunbound 'foo)
(defvar foo 2)

ale jeśli istnieje szansa foozadeklarowana gdzie indziej, możesz mieć pewne skutki uboczne do pokonania.

nispio
źródło
Jest to trudne przy próbie przetestowania zmian w trybie złożonym. Wolałbym nie zmieniać kodu. eval-defuntraktuje defvarspecjalnie, więc na pewno jest coś podobnego dla całych buforów?
Wilfred Hughes
@WilfredHughes Nie jestem pewien, co rozumiesz przez „coś dla całych buforów”. Chcesz pojedynczej funkcji, która będzie makunbounddowolnymi zmiennymi zadeklarowanymi w bieżącym buforze, a następnie ponownie ją oceni? Możesz napisać własny, ale nie sądzę, że jest do tego gotowa funkcja. EDYCJA: Nieważne, rozumiem co mówisz. eval-defunKtóry działa na cały bufor. Wygląda na to, że @JordonBiondo ma na to odpowiednie rozwiązanie.
nispio
Nie. Problemem jest brak ponownej oceny: defvarnic nie robi, jeśli zmienna ma już wartość (jak mówi jej dokument:) The optional argument INITVALUE is evaluated, and used to set SYMBOL, only if SYMBOL's value is void.. Problemem nie jest to, że defvarzmienia wartość domyślną, a nie bieżącą. (defvar a 4) (default-value 'a) (setq a 2) (default-value 'a); potem C-x C-epo defvarsexp; potem (default-value 'a). C-x C-e, eval-regioni tym podobne w defvarsexp nie zmieniają wartości domyślnej.
Drew,