Elisp 有办法获取未经过 Advice 的原始的函数定义吗?


#1

比如说,

(define-advice switch-to-buffer (:after (&rest _) recenter) (recenter))

如何调用未advice的原始的 switch-to-buffer 呢?


问题有歧义,我重新编辑一下:

现在已知是 switch-to-buffer 已经被 Advice 了,可能是 around,也可能是别的其他的,这些函数会在实际调用 switch-to-buffer 的时候被调用。

那么现在我突然想调用最原始的 switch-to-buffer ,就只有依次 advice-remove 所有的 advice,然后调用,然后再加回去。

这种方法在advice非常多的时候不是很好用,而且不是很干净。

我就想问问,在这种情况下有没有办法去调用原始的 switch-to-buffer


#2

需要调用原函数(OLDFUN),你得用 :around。用 Advice 之前得要看明白 (info “(elisp) Advice combinators”)


#3

你的描述存在歧义:

  1. 别人写了一个 advice,但是你想调原来的函数。
  2. 你正在写一个 advice,过程中需要调用原函数。

第 1 种情况,又可以细分成两种:

  • 1.1. 别人添加的 advice 有问题,解决之道是删除

    如何一次性删除所有 advice,看这个帖子:https://emacs.stackexchange.com/questions/24657

  • 1.2. 别人添加的 advice 没有问题,而且相关的 package 需要,不能删除。但是你自己在某个场景下需要调用原始函数:

    (defun foo ()
    (message "foo"))
    
    (defun override@foo ()
    (message "override@foo"))
    
    (fset 'orignal-foo (symbol-function 'foo))
    (advice-add 'foo :override 'override@foo)
    
    (foo)         ;; => override@foo
    (orignal-foo) ;; => foo
    

如果是第 2 个意思,应该用 :around:

(defun around@switch-to-buffer (orign-fn &rest args
                                         ;; 如果需要用到具体参数,直接抄原函数的定义:
                                         ;;     buffer-or-name &optional norecord force-same-window)
                                         )
  ;; do something before orign-fn
  (apply orign-fn args)
  ;; do something after orign-fn
  )

(advice-add 'switch-to-buffer :around 'around@switch-to-buffer)

使用 :around 表示你必须调用原来的函数,以完成工作。否则,就变成了 :override 的效果。

:before/:after 可以看作是 :around 的特例,这两种情况下自动调用原来的函数。


#4

@xuchunyang @twlz0ne

我的描述确实有歧义,两位理解的都不是我需要的。

(@twlz0ne 的第一种情况应该就是我需要的,不知道有没有办法解决~ )

现在已知是 switch-to-buffer 已经被 Advice 了,可能是 around,也可能是别的其他的,这些函数会在实际调用 switch-to-buffer 的时候被调用。

那么现在我突然想调用最原始的 switch-to-buffer,就只有依次 advice-remove 所有的 advice,然后调用,然后再加回去。

这种方法在advice非常多的时候不是很好用,而且不是很干净。

我就想问问,在这种情况下有没有办法去调用原始的 switch-to-buffer


#5

(defun zx ()
  (print 89))

(define-advice zx (:before (&rest _) rece) (print "77"))

(defun around@zx (orign-fn &rest args)
  (apply orign-fn args)
  (print "around")
  )
                                                                                       
(advice-add 'zx :around 'around@zx)

(zx)


#6

你再看我前面的回答,补充了各种分歧下的解决方案。


#7

可是这个好像和 (symbol-function 'foo) 和 advice-add 的位置有关系吧?

如果是先调用 advice-add 就没办法了。。。


#8

明白了你的问题,advice--cdr 貌似可以:

(defun foo () 123)
;; => foo

(define-advice foo (:around (_oldfun) bar)
  456)
;; => foo@bar

(foo)
;; => 456

(funcall (advice--cdr (symbol-function 'foo)))
;; => 123

注意我仅试过这一个例子。


但在我看来应该避免这样的问题,Advice 的使用需要节制,像 switch-to-buffer 这样的库函数不适合 Advice,优先重写一个新函数,如:switch-to-buffer-and-recenter


#9

那就想办法更先,自己的配置自己做主。


#10

参考 advice-function-mapc 的实现, 用 advice--cdr 应该可以处理多个 advice 的例子 .

(defun symbol-function-original (symbol)
  (let ((function-def (advice--symbol-function symbol)))
    (while (advice--p function-def)
      (setq function-def (advice--cdr function-def)))
    function-def))

(symbol-function-original 'foo)
;; (lambda nil
;;   (message "call foo"))


(defun foo ()
  (message "call foo"))

(defun foo-ad (orignal-fn &rest args)
  (message "call foo-ad")
  (apply orignal-fn args))

(advice-add 'foo :around 'foo-ad)

(foo)
;; call foo
;; call foo-ad

(funcall (symbol-function-original 'foo))
;; call foo


#11

advice.el有ad-get-orig-definition一键获取原始定义