Como forço a reavaliação de um defvar?


21

Suponha que eu tenha um buffer lisp do Emacs que contenha:

(defvar foo 1)

Se eu ligar eval-last-sexpou eval-buffer, fooestiver vinculado a 1. Se eu editar esse buffer para:

(defvar foo 2)

eval-last-sexpe eval-buffernão reexecute esta linha, então fooainda é 1.

Isso é particularmente desafiador quando existem várias dessas declarações e eu tenho que rastrear quais linhas não estão sendo reavaliadas.

Eu olhei para reiniciar o Emacs e depois (require 'foo), mas tenho que ter cuidado para evitar o carregamento de arquivos .elc mais antigos.

Como posso ter certeza absoluta e positiva de que as variáveis ​​e funções definidas no arquivo atual estão no mesmo estado que o carregamento do código novamente em uma nova instância do Emacs?


Você não pode ter " absolutamente, certeza absoluta de que o Emacs está em um estado igual ao carregamento do código novamente em uma nova instância do Emacs " sem fazer exatamente isso. Se você quiser ter certeza de escrever somente esta e outras variáveis ​​globais , poderá remover seus valores usando makunbounde reavaliar o código no buffer.
Drew

Claro, efeitos colaterais como (código bobo) (incf emacs-major-version)eu posso viver acontecendo repetidamente. Estou interessado em invadir o código com muitos defvarformulários.
Wilfred Hughes

Respostas:


29

Conforme explicado em outras respostas, a avaliação de um defvarformulário usando eval-last-sexpnão redefine o valor padrão.

Em vez disso, você pode usar eval-defun(vinculado a C-M-xin emacs-lisp-modepor padrão), que implementa o comportamento desejado como uma exceção especial:

Se o desfecho atual for realmente uma chamada para defvarou defcustom, a avaliação dessa forma redefinirá a variável usando sua expressão de valor inicial, mesmo que a variável já tenha algum outro valor. (Normalmente defvare defcustomnão altere o valor se já houver um.)


Se você precisar avaliar o conteúdo completo de um buffer, poderá escrever uma função que percorra os formulários de nível superior por vez e chame eval-defuncada um. Algo assim deve 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))))

5
Essa é a resposta. Não há necessidade de defvars falsos ou setqs extras. Basta usar em eval-defunvez de eval-last-sexp . Você pode até escrever uma função que chama eval-defuntodos os formulários do buffer e usá-la em vez de eval-buffer.
Malabarba 17/10/2014

1
@ Malabarba Este post está muito aquém de responder à pergunta. Use em eval-defunvez de eval-last-sexp, com certeza, mas a dificuldade é para eval-buffer.
Gilles 'SO- stop be evil'

@ Gilles sim, você está certo. Eu adicionei uma implementação tentativa da idéia do @ Malabara de chamar eval-defuntodos os formulários de nível superior no buffer.
Ffevotte 17/10/2014

1
Essa abordagem parece não funcionar se defvarnão estiver dentro de a defun. Exemplo: (progn (defvar foo "bar")).
Kaushal Modi

2
@kaushalmodi Os últimos exemplos que você cita (variáveis ​​que armazenam expressões regulares) se parecem muito com candidatos a defconst(que são sempre reavaliados). Houve recentemente um post muito esclarecedor sobre este tema em parênteses intermináveis
ffevotte

5

Como as outras respostas dizem, é assim que o defvar funciona, mas você pode contornar isso, afinal é o elisp.

Você pode redefinir temporariamente como o defvar funciona, se desejar, e durante esse período, recarregar os pacotes que deseja redefinir.

Escrevi uma macro em que, durante a avaliação do corpo, os valores defvars serão sempre reavaliados.

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

Exemplo 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: Isso deve ser usado apenas com a finalidade de reavaliar defvars, pois apenas ignora as doutrinas ao reavaliar. Você pode modificar a macro para dar suporte à reavaliação que também aplica doutrinas, mas deixarei isso para você.

No seu caso, você poderia fazer

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

Mas saiba o que aqueles que escrevem elisp esperam que o defvar funcione conforme especificado, pode ser que eles usem o defvar para definir e setq em alguma função init para especificar o valor, para que você possa acabar sem variáveis ​​que não pretende, mas isso é provavelmente raro.

Implementação Alternativa

Usando isso, você pode redefinir o defvar globalmente e controlar se ele definirá ou não o valor do símbolo para o argumento INIT-VALUE, mesmo que o símbolo seja definido alterando o valor do novo 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))

1
Não tenho certeza de que redefinir o comportamento de defvaré uma boa idéia: existem vários usos possíveis de defvar, com semântica ligeiramente diferente. Por exemplo, um uso que sua macro não considera é o (defvar SYMBOL)formulário, que é usado para informar o compilador de bytes sobre a existência de uma variável sem definir um valor.
Ffevotte 17/10/2014

Se você absolutamente precisar redefinir defvarcom uma macro, provavelmente seria melhor prefixar o defvarformulário original com a makunbound, em vez de substituí-lo setq.
Ffevotte 17/10/2014

Sim, é uma péssima idéia e só deve ser usada para reavaliar os defvars de um pacote carregado no seu buffer de rascunho, você nunca deve enviar algo assim.
Jordon Biondo

@Francesco também você está certo sobre a versão confusa, eu implementei isso, mas me afastei da idéia, coloquei esse código na minha resposta como alternativa.
Jordon Biondo

3

O defvarestá sendo avaliado e fazendo exatamente o que você especificou. No entanto, defvardefine apenas um valor inicial:

O argumento opcional INITVALUE é avaliado e usado para definir SYMBOL, apenas se o valor de SYMBOL for nulo.

Portanto, para alcançar o que você deseja, você precisará desvincular a variável antes de reavaliar, por exemplo

(makunbound 'foo)

ou use setqpara definir o valor, por exemplo

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

Se você não precisar especificar uma sequência de documentos aqui, poderá pular defvarcompletamente.

Se você realmente deseja usar defvare desvincular automaticamente isso, precisará escrever uma função para encontrar defvarchamadas no buffer atual (ou região, ou último sexp, etc); ligue makunboundpara cada um; e então faça a avaliação real.


Eu estava brincando com um eval-bufferinvólucro que desvendaria tudo primeiro, mas a resposta de @ Francesco eval-defuné realmente o que você deseja.
glucas

1

A macro a seguir foi criada rastreando eval-defunsuas funções de suporte e modificando-a para que não seja mais necessário avaliar uma região de um buffer específico. Eu precisava de ajuda no thread relacionado Convertendo uma expressão em uma string , e o @Tobias veio em socorro - me ensinando como converter a função imperfeita em uma macro. Não acho que precisamos eval-sexp-add-defvarspreceder elisp--eval-defun-1, mas se alguém achar importante, informe-me.

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

O problema não é que a linha não esteja sendo reavaliada. O problema é que defvardefine uma variável e seu valor padrão . Se uma variável já existir, a alteração do valor padrão não modifica o valor atual. Infelizmente, acho que você precisará executar um setqpara cada variável cujo valor você deseja atualizar.

Isso pode ser um exagero, mas você pode atualizar seu arquivo assim se quiser atualizar facilmente foopara seu novo valor padrão.

(defvar foo 2)
(setq foo 2)

mas isso exige que você mantenha o valor padrão em dois lugares no seu código. Você também pode fazer isso:

(makunbound 'foo)
(defvar foo 2)

mas se houver uma chance foodeclarada em outro lugar, você poderá ter alguns efeitos colaterais.


Isso é difícil ao tentar testar as alterações em um modo complexo. Prefiro não mudar o código. eval-defuntrata defvarespecialmente, então certamente há algo semelhante para buffers inteiros?
Wilfred Hughes

@WilfredHughes Não sei ao certo o que você quer dizer com "algo para buffers inteiros". Você deseja uma única função que makunbounddeclare todas as variáveis ​​no buffer atual e depois a reavalie? Você poderia escrever o seu próprio, mas não acho que exista uma função pronta para isso. EDIT: Não importa, eu entendo o que você está dizendo. Um eval-defunque funciona em todo o buffer. Parece que @JordonBiondo tem sua solução para isso.
Nispio 17/10/2014

Não. O problema é a falta de reavaliação: defvarnão faz nada se a variável já tiver um valor (como diz o documento:) The optional argument INITVALUE is evaluated, and used to set SYMBOL, only if SYMBOL's value is void.. O problema não é que defvaraltera o valor padrão e não o valor atual. (defvar a 4) (default-value 'a) (setq a 2) (default-value 'a); em seguida, C-x C-eapós o defvarsexp; então (default-value 'a). C-x C-e, eval-regionE afins em um defvarsexp que não alterar o valor padrão.
Drew
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.