¿Cómo fuerzo la reevaluación de un defvar?

21

Supongamos que tengo un búfer Emacs lisp que contiene:

(defvar foo 1)

Si llamo eval-last-sexpo eval-buffer, fooestá vinculado a 1. Si luego edito este búfer para:

(defvar foo 2)

eval-last-sexpy eval-bufferno vuelva a ejecutar esta línea, por foolo que sigue siendo 1.

Esto es particularmente desafiante cuando hay múltiples declaraciones de este tipo y tengo que rastrear qué líneas no se están reevaluando.

Miré simplemente reiniciar Emacs y luego (require 'foo), pero luego tengo que tener cuidado para evitar cargar cualquier archivo .elc anterior.

¿Cómo puedo estar absolutamente seguro de que las variables y funciones definidas en el archivo actual están en el mismo estado que el código de carga nuevamente en una nueva instancia de Emacs?

Wilfred Hughes
fuente
No puede estar " absolutamente seguro de que Emacs está en un estado que es lo mismo que cargar el código de nuevo en una nueva instancia de Emacs " sin hacer eso. Si desea asegurarse de que solo escriba esta y otras variables globales , puede eliminar sus valores utilizando makunboundy luego volver a evaluar el código en el búfer.
Dibujó el
Claro, los efectos secundarios como (código tonto) con los (incf emacs-major-version)que puedo vivir ocurren repetidamente. Estoy interesado en hackear el código con muchas defvarformas.
Wilfred Hughes

Respuestas:

29

Como se explica en otras respuestas, la evaluación de un defvarformulario usando eval-last-sexpno restablece el valor predeterminado.

En su lugar, puede utilizar eval-defun(con destino C-M-xen emacs-lisp-modeforma predeterminada), que implementa el comportamiento que desea como una excepción especial:

Si el defun actual es realmente una llamada a defvaro defcustom, al evaluarlo de esta manera se restablece la variable usando su expresión de valor inicial, incluso si la variable ya tiene algún otro valor. (Normalmente defvary defcustomno alteran el valor si ya hay uno).


Si necesita evaluar el contenido completo de un búfer, puede escribir una función que recorra los formularios de nivel superior y llame eval-defuna cada uno. Algo como esto debería funcionar:

(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
fuente
55
Esta es la respuesta No hay necesidad de defvars falsos o setqs adicionales. Solo use en eval-defunlugar de eval-last-sexp . Incluso puede escribir una función que llame eval-defuna cada formulario en el búfer y usarlo en lugar de eval-buffer.
Malabarba
1
@Malabarba Esta publicación no responde a la pregunta. Use en eval-defunlugar de eval-last-sexp, claro, pero la dificultad es para eval-buffer.
Gilles 'SO- deja de ser malvado'
@ Gilles sí, tienes razón. Agregué una implementación tentativa de la idea de @ Malabara de llamar eval-defuna cada formulario de nivel superior en el búfer.
ffevotte
1
Este enfoque no parece funcionar si defvarno está dentro de a defun. Ejemplo: (progn (defvar foo "bar")).
Kaushal Modi
2
@kaushalmodi Los últimos ejemplos que cita (variables que almacenan expresiones regulares) se parecen mucho a los candidatos defconst(que siempre se reevalúan). Recientemente ha habido una publicación muy esclarecedora sobre este tema entre paréntesis interminables
ffevotte
5

Como dicen las otras respuestas, esta es la forma en que defvar funciona, pero puedes evitarlo, después de todo, es elisp.

Puede redefinir temporalmente cómo funciona defvar si lo desea y, durante ese tiempo, volver a cargar los paquetes que desea restablecer.

Escribí una macro donde durante la evaluación del cuerpo, los valores de defvars siempre serán reevaluados.

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

Ejemplo de uso:

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: Esto solo debe usarse con el propósito de reevaluar los defvars, ya que simplemente ignora las cadenas de documentos al reevaluar. Puede modificar la macro para admitir la reevaluación que también aplica docstrings, pero se lo dejaré a usted.

En tu caso podrías hacer

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

Pero sepa lo que hacen los que escriben elisp esperando que defvar funcione como se especifica, podría ser que usan defvar para definir y establecer q en alguna función de inicio para especificar el valor, por lo que puede terminar con variables nulas que no pretende pero Esto es probablemente raro.

Implementación Alternativa

Con esto, puede redefinir defvar globalmente y controlar si establecerá o no el valor del símbolo en el argumento INIT-VALUE incluso si el símbolo se define cambiando el valor del nuevo defvar-always-reeval-valuessímbolo.

;; 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
fuente
1
No estoy seguro de redefinir el comportamiento de defvares una buena idea: hay varios usos posibles diferentes defvar, con semánticas ligeramente diferentes. Por ejemplo, un uso que su macro no tiene en cuenta es el (defvar SYMBOL)formulario, que se utiliza para decirle al compilador de bytes sobre la existencia de una variable sin establecer un valor.
ffevotte
Si absolutamente necesita redefinir defvarcon una macro, probablemente sea mejor prefijar el defvarformulario original con a makunbound, en lugar de reemplazarlo por setq.
ffevotte
Sí, es una idea terrible, y solo debe usarse para cosas como reevaluar los defvars de un paquete cargado en su búfer de memoria virtual, nunca debe enviar algo como esto.
Jordon Biondo
@Francesco también tiene razón sobre la versión makunbound, lo implementé pero me alejé de la idea, puse ese código en mi respuesta como alternativa.
Jordon Biondo
3

Se defvarestá evaluando y haciendo exactamente lo que ha especificado. Sin embargo, defvarsolo establece un valor inicial:

El argumento opcional INITVALUE se evalúa y se usa para establecer SYMBOL, solo si el valor de SYMBOL es nulo.

Entonces, para lograr lo que desea, necesitaría desvincular la variable antes de reevaluar, por ejemplo

(makunbound 'foo)

o use setqpara establecer el valor, por ej.

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

Si no necesita especificar una cadena de documentación aquí, puede omitirla por defvarcompleto.

Si realmente desea usar defvary desvincular esto automáticamente, deberá escribir una función para buscar defvarllamadas en el búfer actual (o región, o el último sexp, etc.); llama makunbounda cada uno; y luego hacer la evaluación real.

glucas
fuente
Estaba jugando con una eval-bufferenvoltura que desenredaría todo primero, pero la respuesta de @ Francesco eval-defunes realmente lo que quieres.
glucas
1

La siguiente macro se creó rastreando eval-defunsus funciones de soporte y modificándola para que ya no sea necesario evaluar una región de un búfer particular. Necesitaba ayuda en el hilo relacionado Convirtiendo una expresión en una cadena , y @Tobias acudió al rescate, enseñándome cómo convertir la función imperfecta en una macro. No creo que tengamos eval-sexp-add-defvarsque preceder elisp--eval-defun-1, pero si alguien piensa que es importante, hágamelo saber.

;;; 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 de leyes
fuente
0

El problema no es que la línea no se vuelva a evaluar. El problema es que defvardefine una variable y su valor predeterminado . Si ya existe una variable, cambiar su valor predeterminado no modifica el valor actual. Desafortunadamente, creo que necesitará ejecutar un setqpara cada variable cuyo valor desea actualizar.

Esto puede ser excesivo, pero puede actualizar su archivo de esta manera si desea poder actualizar fácilmente fooa su nuevo valor predeterminado.

(defvar foo 2)
(setq foo 2)

pero eso requiere que mantenga el valor predeterminado en dos lugares en su código. También puedes hacer esto:

(makunbound 'foo)
(defvar foo 2)

pero si existe la posibilidad de que foose declare en otro lugar, puede tener algunos efectos secundarios para tratar.

nispio
fuente
Eso es difícil cuando se intenta probar los cambios a un modo complejo. Prefiero no cambiar el código. eval-defuntrata defvarespecialmente, así que seguramente hay algo similar para tampones enteros?
Wilfred Hughes
@WilfredHughes No estoy seguro de lo que quieres decir con "algo para amortiguadores enteros". ¿Desea una única función que muestre makunboundlas variables declaradas en el búfer actual y luego la reevalúe? Podrías escribir el tuyo, pero no creo que haya una función lista para usar para esto. EDITAR: No importa, entiendo lo que estás diciendo. Un eval-defunque funciona en todo el búfer. Parece que @JordonBiondo tiene su solución para eso.
nispio
No. El problema es la falta de reevaluación: defvarno hace nada si la variable ya tiene un valor (como dice su documento:) The optional argument INITVALUE is evaluated, and used to set SYMBOL, only if SYMBOL's value is void.. El problema no es que defvarcambie el valor predeterminado y no el valor actual. (defvar a 4) (default-value 'a) (setq a 2) (default-value 'a); luego C-x C-edespués del defvarsexp; entonces (default-value 'a). C-x C-e, eval-regiony similares en un defvarsexp no cambian el valor predeterminado.
Dibujó el