around advice 怎样处理原来函数的 optional argument?

如果我想要 advise 的函数包含一个 optional argument,我的 advice 不需要对 argument 做任何处理,这 advice 应该怎样定义呢?

直接照抄 arg list。

1 个赞

别闹 :joy:

老的 defadvice 能处理这种情况吗?

跟被advice的一样就行了,如果是around的话多一个,第一个是原函数

1 个赞

那 advice 里面怎么样 call 原函数?

apply 还是 funcall ?

就是的啊…或者我们没理解你的意思?

随你了,都可以

好的,我写一个 example 试试


;; "+p 从系统剪切板paste时会调到此处
;; 如果在mac 终端下使用emacs ,则使用pbpaste从clipboard 获取内容
(defadvice gui-backend-get-selection (around get-clip-from-terminal-on-osx activate)
  ad-do-it  ;;call 原函数
  (when (and (equal system-type 'darwin)
             (not (display-graphic-p))
             (not (window-system))
             (equal (ad-get-arg 0) 'CLIPBOARD)) ; 获取参数
    (let ((default-directory "~/"))
      (setq ad-return-value (shell-command-to-string "pbpaste")))))

多谢各位,照抄列表是可行的,调用原始函数时要用 funcall

(defun myfun (&optional arg)
  (interactive)
  (if arg
      (message "arg is %s" arg)
    (message "No arg")))

(defun myadvice (orig-func &optional arg)
  (message "from advice")
  (funcall orig-func arg))

(advice-add #'myfun :around #'myadvice)

Advice 函数的参数表的写法没有要求,只要最后执行的时候参数数目不冲突就行,比如 &rest args 是万能的。

比如 :around 函数的调用规则是

(lambda (&rest r) (apply FUNCTION OLDFUN r))
                         ^^^^^^^^

比如这样一个函数

(defun foo (a &optional b &rest c)
  (list a b c))

如果要定义用在 :around 位置的 Advice 函数的话,下面的参数表写法都可行:

(old-fun a &optional b &rest c)
(old-fun a &rest r)
(old-fun &rest r)
(&rest r)

至于用哪一个就看你的需求和喜好了,比如假设你关心 old-fun 和第一个参数 a ,用

(old-fun a &rest _)

最简洁。

6 个赞

10# 楼 答案不对吧。

不处理 argument 就不需要抄,用 apply 调用原函数:

#+BEGIN_SRC emacs-lisp :results value
(defun myfun (arg1 arg2)
  (list arg1 arg2))

(defun myadvice (fn &rest rest)
  (apply fn rest))

(advice-add 'myfun :around 'myadvice)

(myfun "foo" "bar")
#+END_SRC

#+RESULTS:
| foo | bar |

处理一部分参数,就抄一部分:

#+BEGIN_SRC emacs-lisp :results value
(defun myfun (arg1 arg2)
  (list arg1 arg2))

(defun myadvice (fn arg1 &rest rest)
  (let ((arg1 "qux"))
    (apply fn arg1 rest)))

(advice-add 'myfun :around 'myadvice)

(myfun "foo" "bar")
#+END_SRC

#+RESULTS:
| qux | bar |

其实严格说来,不处理也可以照抄。只不过,既然不处理,就无需费力抄。而且抄了还要维护,以后原函数参数变更,advice 参数也要变更,这不自找麻烦吗。所以啊,&rest 应万变。

4 个赞

有三个参数,最后一个 &rest,只要处理第二个怎么办? :smirk:

那也只能抄到第二个参数了:

#+BEGIN_SRC emacs-lisp :results value
(defun myfun (arg1 arg2 &rest rest)
  (list arg1 arg2 rest))

(defun myadvice (fn _ arg2 &rest rest)
  (let ((arg2 2))
  (apply fn _ arg2 rest)))

(advice-add 'myfun :around 'myadvice)

(myfun "a" "b" "c" "d"))
#+END_SRC

#+RESULTS:
| a | 2 | (c d) |
2 个赞

用参数表分解或者在函数体中分解:

(defun foo (a b &rest c) (list a b c))
;; => foo

(foo 1 2 3 4)
;; => (1 2 (3 4))

(define-advice foo (:override (_ b &rest _) only-b)
  (list b))
;; => [email protected]

(foo 1 2 3 4)
;; => (2)

(define-advice foo (:override (&rest args) only-b)
  (let ((b (cl-second args)))
    (list b)))
;; => [email protected]

(foo 1 2 3 4)
;; => (2)

这两种写法都有效并非魔法,只是因为你定义的函数会被 apply 调用的缘故。


考虑这样一个问题或许有助于理解:问什么的参数表能同时满足以下四种情况?

(apply FUNC '())
(apply FUNC '(1))
(apply FUNC '(1 2))
(apply FUNC '(1 2 3))
以下四种都行
&optional a b c
&optional a b &rest r
&optional a &rest r
&rest r
1 个赞

为什么(advice-remove 'foo #'[email protected])之后(foo 1 2 3 4)结果还是(2),就好像advice-remove失败了一样?

建议用advice-adddefine-advice已经deprecate了

已经改掉了,应该是我复制粘贴中间又执行了代码。define-advice 同名函数会自动重新执行定义,不用 advice-remove 也行,所以索性去掉了。

define-advice 是 Emacs 25.1 才加入了,它的效果类似于 defun + advice-add

你说的是 defadvice 吧,虽然已经 deprecated 但这个函数还是使用最多的:

⋊> ag defadvice elpa-27.0.50/ -l | wc -l
      40
⋊> ag define-advice elpa-27.0.50/ -l | wc -l
       0
⋊> ag advice-add elpa-27.0.50/ -l | wc -l
      23