给macro添加的advice在byte-compile时无效?

创建一个测试文件 test.el,内容如下:

(eval-and-compile (defun adv (arg)
		    (cons "z" arg))
		  (advice-add 'mcr :filter-args 'adv))

(defmacro mcr (&rest body)
  `(list "a" ,@body))


(defun fun (r)
  (mcr r))
  1. M-x eval-buffer,之后 M-: (fun b),结果是 ("a" "z" "b")

  2. M-x emacs-lisp-byte-compile-and-load, 之后 M-: (fun b),结果是 ("a" "b")

好像是给macro添加的advice在byte-compile时无效。

对advice、macro、compile之类的不太熟,请教下是原来就这么设计的还是我哪里搞错了?谢谢!

你要把macro 包含到 eval-and-compile

defmacro 在编译的时候有自己的 namespace,advice 修改不到它。在 eval-and-compile 里的 defmacro 才会在编译时通过求值副作用定义到全局 namespace。

以及,如果在同一个文件里,即使是在 eval-and-compile 里,在 toplevel 的 defmacro 也会造成在 local namespace 里没能被 advice 的定义优先于全局定义使用。

所以得使 defmacro 不在 top-level,阻止 byte compiler 用未被 advice 的 local namespace 里的定义。

(eval-and-compile
  (defun adv (arg)
    (cons "z" arg))
  (list (defmacro mcr (&rest body)
          `(list "a" ,@body)))
  (advice-add 'mcr :filter-args 'adv))


(defun fun (r)
  (mcr r))

3 个赞

谢谢大佬回复,还有很多需要学习。好像用 eval-and-compileeval-when-compile 编译之后的结果还不一样,已经有点懵圈了。

不过无论怎样,看来在魔改别人的包的时候,advising macro 这种方式终归是不合适的。

关于 eval-and-compileeval-when-compile 这个帖子讨论比较多:

macro 不适合拿来 advice。

  1. 你无法用一个宏 advice 另一个宏:

    (advice-add 'old-macro :override 'new-macro) :x:

  2. 用函数 advice 宏就是驴唇斗马嘴,你可能连接口都没法保证一致:

    原宏这样调用:(old-macro aaa)
    函数这样调用:(advice-fn 'aaa)

1 个赞

谢谢,很有收获。

尝试了一下,不编译的话,从使用的角度,advising macro 还是能用的,只要之后再手动加载一遍 .el 文件,虽然这么做比较丑陋 :sweat_smile:

高速错过出口,你也可以倒车回来,不觉得危险就没有什么好顾虑了。

1 个赞

:rofl: 哈哈,是有点这么个意思。

尝试研究了一下,看了看代码, eval-when-compileeval-and-compile 竟然还有“magic definitions on the byte-compile-macro-environment”。这么subtle的东西,一般也用不到,作为普通emacs用户,先绕路吧,等有时间再说了 :face_exhaling:

我认为用 setf/cl-letf 是更靠谱的做法:

;;; example.el

(eval-and-compile
  (defmacro mcr (&rest body)
    `(list "a" ,@body))

  (defalias 'mcr-orig
    `,(symbol-function 'mcr))

  (defmacro adv (&rest body)
    `(mcr-orig "z" ,@body))

  (setf (symbol-function 'mcr) 'adv))

;;; example-test.el

(require 'cl-lib)

;; 修改之后
(cl-assert (equal '("a" "z" b) (mcr 'b)))
;; 原始效果
(cl-assert (equal '("a" b)     (mcr-orig 'b)))
;; 临时还原
(cl-assert (equal '("a" b)     (cl-letf (((symbol-function 'mcr) 'mcr-orig))
                                 (mcr 'b))))

相关主题:


NOTE: 如果宏在 setf/cl-letf 之前就已于某个函数内使用,并且这个函数已编译,那么宏的行为也不会改变:

;; example.el
(eval-and-compile
  (defmacro mcr (&rest body)
    `(list "a" ,@body))

  (defalias 'mcr-orig
    `,(symbol-function 'mcr))

  (defmacro adv (&rest body)
    `(mcr-orig "z" ,@body))

  (require 'cl-lib)
  (defun call-mcr ()
    (let ((byte-compiled-p (byte-code-function-p (symbol-function 'call-mcr))))
      (message "==> byte-compiled-p: %s" byte-compiled-p)
      (cond
       ;; 已编译,行为不变
       (byte-compiled-p (cl-assert (equal '("a" b)     (mcr 'b))))
       ;; 未编译,行为改变
       (t               (cl-assert (equal '("a" "z" b) (mcr 'b)))))))

  (setf (symbol-function 'mcr) 'adv))
$ emacs -Q --batch -l /tmp/example.el -f call-mcr
==> byte-compiled-p: nil

$ emacs -Q --batch -l /tmp/example.elc -f call-mcr
==> byte-compiled-p: t