Come posso forzare la rivalutazione di un defvar?


21

Supponiamo di avere un buffer lisp Emacs che contiene:

(defvar foo 1)

Se chiamo eval-last-sexpo eval-buffer, fooè associato a 1. Se poi modifico questo buffer in:

(defvar foo 2)

eval-last-sexpe eval-buffernon rieseguire questa riga, quindi fooè ancora 1.

Ciò è particolarmente impegnativo quando ci sono più affermazioni del genere e devo rintracciare quali righe non vengono rivalutate.

Ho esaminato solo il riavvio di Emacs e poi (require 'foo), ma poi devo fare attenzione per evitare di caricare file .elc meno recenti.

Come posso essere assolutamente sicuro che le variabili e le funzioni definite nel file corrente siano nello stesso stato del caricamento del codice in una nuova istanza di Emacs?


Non si può essere " assolutamente, positivamente sicuri che Emacs si trovi nello stesso modo del nuovo caricamento del codice in una nuova istanza di Emacs " senza farlo. Se vuoi essere sicuro solo di scrivere questa e altre variabili globali , puoi rimuovere i loro valori usando makunbounde quindi rivalutare il codice nel buffer.
Ha

Certo, effetti collaterali come (codice stupido) con (incf emacs-major-version)cui posso vivere ripetutamente. Sono interessato a hackerare il codice con molte defvarforme.
Wilfred Hughes,

Risposte:


29

Come spiegato in altre risposte, la valutazione di un defvarmodulo mediante eval-last-sexpnon reimposta il valore predefinito.

Invece, è possibile utilizzare eval-defun(legato a C-M-xin emacs-lisp-modeper impostazione predefinita), che implementa il comportamento che si desidera come un'eccezione speciale:

Se il defun corrente è in realtà una chiamata a defvaro defcustom, valutandolo in questo modo reimposta la variabile usando la sua espressione di valore iniziale anche se la variabile ha già qualche altro valore. (Normalmente defvare defcustomnon modificare il valore se ne esiste già uno.)


Se è necessario valutare l'intero contenuto di un buffer, è possibile scrivere una funzione che accompagni a turno i moduli di livello superiore e li invii eval-defun. Qualcosa del genere dovrebbe funzionare:

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

5
Questa è la risposta Non sono necessari defvar falsi o setq aggiuntivi. Basta usare eval-defuninvece di eval-last-sexp . È anche possibile scrivere una funzione che chiama eval-defuntutti i moduli nel buffer e utilizzarla al posto di eval-buffer.
Malabarba,

1
@Malabarba Questo post non è in grado di rispondere alla domanda. Usa eval-defuninvece di eval-last-sexp, certo, ma la difficoltà è per eval-buffer.
Gilles 'SO- smetti di essere malvagio' il

@Gilles sì, hai ragione. Ho aggiunto un'implementazione provvisoria dell'idea di @ Malabara di chiamare eval-defuntutti i moduli di livello superiore nel buffer.
ffevotte,

1
Questo approccio non sembra funzionare se defvarnon è all'interno di a defun. Esempio: (progn (defvar foo "bar")).
Kaushal Modi,

2
@kaushalmodi Gli ultimi esempi citati (variabili che memorizzano espressioni regolari) assomigliano molto ai candidati defconst(che viene sempre rivalutato). Recentemente c'è stato un post molto illuminante su questo argomento tra parentesi infinite
ffevotte

5

Come dicono le altre risposte, questo è il modo in cui defvar funziona, ma puoi aggirarlo, dopo tutto è elisp.

È possibile ridefinire temporaneamente il funzionamento di defvar se lo si desidera e durante tale periodo, ricaricare i pacchetti che si desidera ripristinare.

Ho scritto una macro in cui durante la valutazione del corpo, i valori di defvars verranno sempre rivalutati.

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

Esempio di utilizzo:

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

Nota: questo dovrebbe essere usato solo allo scopo di rivalutare i defvar, in quanto ignora solo i docstring durante la rivalutazione. Puoi modificare la macro per supportare la rivalutazione che applica anche i docstring, ma lo lascerò a te.

Nel tuo caso potresti farlo

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

Ma sappi cosa fanno quelli che scrivono elisp aspettandosi che defvar funzioni come specificato, potrebbe essere che usano defvar per definire e impostareq in alcune funzioni di init per specificare il valore, quindi potresti finire per annullare le variabili che non intendi ma questo è probabilmente raro.

Implementazione alternativa

Usando questo puoi semplicemente ridefinire defvar a livello globale e controllare se imposterà il valore del simbolo sull'argomento INIT-VALUE anche se il simbolo è definito cambiando il valore del nuovo defvar-always-reeval-valuessimbolo.

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

1
Non sono sicuro che ridefinire il comportamento di defvarsia una buona idea: ci sono molti diversi usi possibili defvar, con semantica leggermente diversa. Ad esempio, un uso per cui la macro non tiene conto è il (defvar SYMBOL)modulo, che viene utilizzato per comunicare al compilatore di byte l'esistenza di una variabile senza impostare un valore.
ffevotte,

Se hai assolutamente bisogno di ridefinire defvarcon una macro, probabilmente sarebbe meglio prefissare il defvarmodulo originale con a makunbound, piuttosto che sostituirlo con setq.
ffevotte,

Sì, è un'idea terribile, e dovrebbe essere usato solo per cose come la rivalutazione dei defvars di un pacchetto caricato nel buffer di memoria virtuale, non si dovrebbe mai spedire qualcosa di simile.
Jordon Biondo,

@Francesco anche tu hai ragione sulla versione makunbound, l'avevo implementato ma allontanandomi dall'idea, ho messo quel codice sulla mia risposta come alternativa.
Jordon Biondo,

3

Il defvarè in corso di valutazione e di fare esattamente quello che hai specificato. Tuttavia, defvarimposta solo un valore iniziale:

L'argomento facoltativo INITVALUE viene valutato e utilizzato per impostare SYMBOL, solo se il valore di SYMBOL è nullo.

Quindi, per ottenere ciò che desideri, dovrai separare la variabile prima di rivalutare, ad es

(makunbound 'foo)

oppure utilizzare setqper impostare il valore, ad es

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

Se non è necessario specificare una docstring qui, è possibile saltare del defvartutto.

Se vuoi davvero usare defvare sbloccare automaticamente questo, dovrai scrivere una funzione per trovare le defvarchiamate nel buffer corrente (o regione, o ultimo sexp, ecc.); chiamare makunboundper ciascuno; e poi fai il vero eval.


Stavo giocando con un eval-bufferinvolucro che prima avrebbe sciolto tutto, ma la risposta di @ Francesco eval-defunè davvero ciò che vuoi.
glucas,

1

La seguente macro è stata creata tracciando le eval-defunsue funzioni di supporto e modificandola in modo che non sia più necessario valutare una regione di un determinato buffer. Avevo bisogno di aiuto nel thread correlato Conversione di un'espressione in una stringa e @Tobias è venuto in soccorso, insegnandomi come convertire la funzione imperfetta in una macro. Non penso che dobbiamo eval-sexp-add-defvarsprecedere elisp--eval-defun-1, ma se qualcuno pensa che sia importante, per favore fatemelo sapere.

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

0

Il problema non è che la linea non viene rivalutata. Il problema è che defvardefinisce una variabile e il suo valore predefinito . Se esiste già una variabile, la modifica del valore predefinito non modifica il valore corrente. Sfortunatamente, penso che dovrai eseguire un setqper ogni variabile di cui desideri aggiornare il valore.

Questo potrebbe essere eccessivo, ma potresti aggiornare il tuo file in questo modo se vuoi essere in grado di aggiornare facilmente fooal suo nuovo valore predefinito.

(defvar foo 2)
(setq foo 2)

ma ciò richiede che tu mantenga il valore predefinito in due punti nel tuo codice. Puoi anche fare questo:

(makunbound 'foo)
(defvar foo 2)

ma se c'è una possibilità foodichiarata altrove, potresti avere degli effetti collaterali da affrontare.


È difficile quando si tenta di testare le modifiche a una modalità complessa. Preferirei non cambiare il codice. eval-defuntratta in modo defvarspeciale, quindi sicuramente c'è qualcosa di simile per interi buffer?
Wilfred Hughes,

@WilfredHughes Non sono sicuro di cosa intendi con "qualcosa per interi buffer". Volete una singola funzione che makunboundqualsiasi delle variabili dichiarate nel buffer corrente, e quindi rivalutarla? Potresti scrivere il tuo, ma non credo che ci sia una funzione pronta all'uso per questo. EDIT: Non importa, ho capito cosa stai dicendo. Un eval-defunche funziona su tutto il buffer. Sembra che @JordonBiondo abbia la tua soluzione per questo.
nispio,

No. Il problema è la mancanza di rivalutazione: defvarnon fa nulla se la variabile ha già un valore (come dice il suo doc:) The optional argument INITVALUE is evaluated, and used to set SYMBOL, only if SYMBOL's value is void.. Il problema non è che defvarcambia il valore predefinito e non il valore corrente. (defvar a 4) (default-value 'a) (setq a 2) (default-value 'a); poi C-x C-edopo il defvarsexp; allora (default-value 'a). C-x C-e, eval-regione simili su un defvarsexp non modificano il valore predefinito.
Disegnò il
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.