创建一个测试文件 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))
M-x eval-buffer
,之后 M-: (fun b)
,结果是 ("a" "z" "b")
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 个赞
LdBeth:
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))
谢谢大佬回复,还有很多需要学习。好像用 eval-and-compile
和 eval-when-compile
编译之后的结果还不一样,已经有点懵圈了。
不过无论怎样,看来在魔改别人的包的时候,advising macro 这种方式终归是不合适的。
关于 eval-and-compile
和 eval-when-compile
这个帖子讨论比较多:
整理重构自己的配置文件的时候,看到了不少曾经抄来的代码,其中有一些eval相关的内容,看了manual也不甚理解,不知道论坛里有没有和我一样有些困惑的朋友,一起讨论一下。
emacs将*.el编译为elc与eln是由什么变量决定的?又是在什么时机编译的?
为什么编译出的elc文件一般与el文件放置在同一目录下,但eln文件又会默认单独放置在eln-cache下呢?
为什么说elpa,melpa上提供的是经过了预编译的package?straight.el的readme中就这样说:
package.el downloads pre-built packages from central servers using a special (undocumented?) HTTP protocol, while straight.el clones Git (or other) repositories and builds packages locally.
但melpa上提供的也仅仅是el文件而已,并非elc文件
init.el、early-init.el等…
macro 不适合拿来 advice。
你无法用一个宏 advice 另一个宏:
(advice-add 'old-macro :override 'new-macro)
用函数 advice 宏就是驴唇斗马嘴,你可能连接口都没法保证一致:
原宏这样调用:(old-macro aaa)
函数这样调用:(advice-fn 'aaa)
1 个赞
谢谢,很有收获。
尝试了一下,不编译的话,从使用的角度,advising macro 还是能用的,只要之后再手动加载一遍 .el
文件,虽然这么做比较丑陋
高速错过出口,你也可以倒车回来,不觉得危险就没有什么好顾虑了。
1 个赞
哈哈,是有点这么个意思。
尝试研究了一下,看了看代码, eval-when-compile
和 eval-and-compile
竟然还有“magic definitions on the byte-compile-macro-environment”。这么subtle的东西,一般也用不到,作为普通emacs用户,先绕路吧,等有时间再说了
我认为用 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))))
相关主题:
基于我先前的一个包 psearch 实现。
适用于不便 advice 又不想改源码的场景,例如:一个巨长的函数,你只想改中间一两句。
改源代码当然是最稳妥的,但是改了至少得建个仓库来维护,不如 advice 和 patch 方便。
Melpa 上有一个看起来比较完善并且有不少人在用的包 el-patch ,但是它很复杂,引入了很多概念,看得我头疼,而且写起来很累赘,几乎是把原函数抄一遍了。
例如把以下左边函数改成右边的形式:
(defun test-patch () ;; => (defun test-patch ()
(list '(1 2 3) ;; (list '(1 2 3)
(if nil '(4 5 6)) ;; (if t '(4 5 6))
'(7 8 9))) ;; '(7 8 9)))
psearch 的写法:
(psearch-with-function-patch test-p…
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