求助:如何理解对 Macro 进行的 advice?

论坛曾经有人谈论过给 macro 增加 advice:

我自己尝试了一下:

(defmacro test (num)
  `(+ 1 ,num))

(test 10)
;; => 11

(funcall #'test 1)
;; error: (invalid-function test)

(define-advice test
    (:around (orig arg) advice)
  (funcall orig (* 2 arg)))

(test 10)
;; => 21

发现我不太能理解他的行为,尤其是,直接 funcall 一个 macro 是不行的,但是在 define-advice 中又可以

求助坛友,有没有一些资料解释了 macro 进行 advice 的行为?

试试

(setq a 10)
(test a)
1 个赞

查了一下似乎的确没有什么文档,这种情况最好的方式还是直接去看源码:

(defun advice-add (symbol how function &optional props)
  (let* ((f (symbol-function symbol))
	 (nf (advice--normalize symbol f)))
    (unless (eq f nf) (fset symbol nf))
    (add-function how (cond
                       ;; ↓↓↓↓↓ 手动高亮
                       ((eq (car-safe nf) 'macro) (cdr nf))
                       ;; ↑↑↑↑↑ 手动高亮
                       ((or (not nf) (autoloadp nf))
                        (get symbol 'advice--pending))
                       (t (symbol-function symbol)))
                  function props)
    (put symbol 'function-documentation `(advice--make-docstring ',symbol))
    (add-function :around (get symbol 'defalias-fset-function)
                  #'advice--defalias-fset))
  nil)

Macro 处理核心也就上面高亮的一行代码:Emacs 的 macro 实际上是一个 cons (macro . 实际调用的函数),advice-add 在 advice 时实际 advice 的是 cons 里“实际调用的函数”。

但是给 macro 进行 advice 和写 macro 一样,需要对 macro 的求值过程以及 byte-compile 过程有完整的理解。比如上面的例子一般肯定会写成这样:

(define-advice test (:around (orig arg) advice)
  (funcall orig `(* 2 ,arg))) ; 而不是 (* 2 arg)
3 个赞
(defmacro test (num)
  `(+ 1 ,num))
test

(symbol-function 'test)
(macro lambda (num) (list '+ 1 num))

(define-advice test
    (:around (orig arg) advice)
  (funcall orig (* 2 arg)))
test@advice

(symbol-function 'test)
(macro . #[128 "\304\300\301^C#\207" [test@advice (lambda ... ...) :around nil apply] 5 advice])

(funcall (cdr (symbol-function 'test))
         1)
(+ 1 2)

2 个赞

感谢各位前辈的解答!

@include-yy 前辈非常简短,言简意赅,应该也是为了指出:即使是对宏进行 advice,也应该像宏一样处理 lisp 表达式,而非直接引用值。

@Kana 感谢前辈,讲解非常详细,简直手把手教 :folded_hands:

@LdBeth 前辈给的例子,帮助我进一步理解了:

同时,以上也解答了我在读到另一个帖子时的疑惑:

原来 advice 机制其实可以用于宏,感叹!(@twlz0ne 前辈当时帖子的结论,现在看可能有失偏颇,或许此结论的时效性已下降)

因此我总结下来:

1、宏是一种 cons: (macro . 实际调用的函数);

2、Emacs 在求值某条 S-exp 时,如果其 car 是 macro,那么就当作宏去执行,参数会原封不动地交与 cdr 的函数去处理,最终求值其最终返还的表达式;

3、如果对宏进行 advice,那么是在 advice 其 cdr 的函数,那么自然:该函数接受的是未求值的参数、其返回的结果应该是将要被求值的表达式。

2 个赞