如何强制重新评估defvar?


21

假设我有一个Emacs lisp缓冲区,其中包含:

(defvar foo 1)

如果我调用eval-last-sexpeval-bufferfoo则绑定到1。如果我将其编辑为:

(defvar foo 2)

eval-last-sexp并且eval-buffer不要重新执行这一行,所以foo还是1。

当存在多个这样的语句并且我必须跟踪哪些行没有被重新评估时,这尤其具有挑战性。

我只看过重新启动Emacs的情况,然后(require 'foo),但是我必须小心避免加载任何旧的.elc文件。

我如何才能绝对肯定地确保当前文件中定义的变量和函数处于与在新的Emacs实例中重新加载代码相同的状态?


您不能“ 绝对,肯定地确保Emacs处于与在新Emacs实例中重新加载代码相同的状态 ”,而不必这样做。如果要确保仅使用 this和其他全局变量,则可以使用删除它们的值makunbound,然后重新评估缓冲区中的代码。
2014年

当然,像(愚蠢的代码)这样​​的副作用(incf emacs-major-version)我可以忍受反复发生。我对黑客入侵有很多defvar形式的代码很感兴趣。
Wilfred Hughes

Answers:


29

如其他答案所述,使用评估defvar表单eval-last-sexp不会重置默认值。

相反,你可以使用eval-defun(绑定到C-M-xemacs-lisp-mode默认情况下),它实现了你想作为一个特殊的例外的行为:

如果当前defun实际上是对defvar或的调用,则以defcustom这种方式评估它会使用其初始值表达式重置变量,即使该变量已经具有其他值。(通常defvardefcustom如果已经存在,则不要更改该值。)


如果您需要评估缓冲区的全部内容,则可以编写一个函数,该函数依次遍历顶级表单并对其进行调用eval-defun。这样的事情应该起作用:

(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
就是答案。不需要伪造的defvars或额外的setqs。只需使用eval-defun代替即可eval-last-sexp 。您甚至可以编写一个函数,该函数调用eval-defun缓冲区中的每个表单,然后使用它代替eval-buffer
马拉巴巴2014年

1
@Malabarba这篇文章远远没有回答这个问题。可以使用eval-defun代替eval-last-sexp,但是困难在于eval-buffer
吉尔(Gilles)'所以

@吉尔斯,是的,你是对的。我添加了@Malabara的想法的临时实现,该想法调用eval-defun缓冲区中的每个顶级表单。
ffevotte 2014年

1
如果defvar不在内,则此方法似乎无效defun。范例:(progn (defvar foo "bar"))
Kaushal Modi 2015年

2
@kaushalmodi您引用的后面的示例(存储正则表达式的变量)看起来很像候选对象defconst(始终会对其进行重新评估)。最近在无休止的括号中
ffevotte 2015年

5

就像其他答案说的那样,这只是defvar的工作方式,但是您可以绕开它,毕竟这是elisp。

您可以临时重新定义defvar的工作方式,并在此期间重新加载要重置的软件包。

我写了一个宏,其中在评估身体时,将始终重新评估defvars值。

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

用法示例:

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

注意:这仅应用于重新评估defvar,因为重新评估时它仅忽略文档字符串。您可以修改宏以支持重新评估也适用于文档字符串,但是我将由您自己决定。

在你的情况下你可以做

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

但是知道那些编写elisp的人这样做是希望defvar能够按指定方式工作,这可能是因为他们使用defvar来定义,并在某个init函数中使用setq来指定值,所以您可能最终会放弃不想要的变量,但是这可能很少见。

替代实施

使用此方法,您可以全局重新定义defvar,并控制是否通过更改新defvar-always-reeval-values符号的值来定义符号的值是否设置为INIT-VALUE arg 。

;; 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
我不确定重新定义的行为是否defvar是一个好主意:有几种不同的可能用法defvar,其语义略有不同。例如,您的宏不使用的(defvar SYMBOL)一种形式是表单,它用于告诉字节编译器变量的存在而无需设置值。
ffevotte 2014年

如果您绝对需要defvar用宏重新定义,则最好在原始defvar格式前加上a 前缀makunbound,而不是用代替setq
ffevotte 2014年

是的,这是一个糟糕的主意,仅应用于重新评估暂存缓冲区中已加载程序包的defvar之类的事情,切勿运送此类物品。
Jordon Biondo

@Francesco你也对makunbound版本是正确的,我已经实现了它,但是偏离了这个想法,我将代码放在我的答案上作为替代方案。
Jordon Biondo

3

defvar被评估,做你指定什么。但是,defvar仅设置一个初始值:

仅当SYMBOL的值为void时,才对可选参数INITVALUE进行评估,并用于设置SYMBOL。

因此,要实现您想要的目标,您将需要在重新评估之前取消绑定变量,例如

(makunbound 'foo)

或用于setq设置值,例如

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

如果您不需要在此处指定文档字符串,则可以defvar完全跳过。

如果您确实要使用defvar并自动取消绑定,则需要编写一个函数以defvar在当前缓冲区(或区域,或最后的sexp等)中查找调用;呼吁makunbound每个;然后进行实际评估。


我本来eval-buffer打算先解开所有包装的包装纸,但是@Francesco的答案eval-defun确实是您想要的。
glucas 2014年

1

通过跟踪宏eval-defun的支持功能并对其进行修改,可以创建以下宏,从而不再需要评估特定缓冲区的区域。我需要相关线程中的帮助,将表达式转换为字符串,然后@Tobias进行了急救-教我如何将不完美的函数转换为宏。我认为我们不必eval-sexp-add-defvars先行elisp--eval-defun-1,但是如果有人认为这很重要,请告诉我。

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

问题不在于该生产线没有得到重新评估。问题是defvar定义了一个变量及其默认值。如果变量已经存在,则更改其默认值不会修改当前值。不幸的是,我认为您将需要setq为每个要更新其值的变量运行a 。

这可能有点过分,但是如果您希望能够轻松更新foo到其新的默认值,则可以像这样更新文件。

(defvar foo 2)
(setq foo 2)

但这要求您在代码中的两个位置维护默认值。您也可以这样做:

(makunbound 'foo)
(defvar foo 2)

但是如果有在foo其他地方声明的机会,您可能会遇到一些副作用。


在尝试测试对复杂模式的更改时,这很困难。我宁愿不更改代码。特殊eval-defun对待defvar,所以整个缓冲区肯定有类似的东西吗?
Wilfred Hughes

@WilfredHughes我不确定“整个缓冲区的含义”是什么意思。您想要一个函数,该函数将makunbound在当前缓冲区中声明任何变量,然后对其进行重新评估吗?您可以编写自己的代码,但我认为没有为此功能的即用型功能。编辑:没关系,我明白你的意思。一个eval-defun是对整个缓冲区的工作原理。看来@JordonBiondo为此提供了解决方案。
nispio 2014年

否。问题在于缺少重新评估:defvar如果变量已经具有值(如其文档所述:),则不执行任何操作The optional argument INITVALUE is evaluated, and used to set SYMBOL, only if SYMBOL's value is void.。问题在于defvar更改默认值而不是当前值。(defvar a 4) (default-value 'a) (setq a 2) (default-value 'a); 然后C-x C-edefvar性爱之后;然后(default-value 'a)C-x C-eeval-region等上一defvarSEXP也不会更改默认值。
2014年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.