如何慷慨地修改函数的定义

Kel*_* Hu 8 lisp emacs elisp

假设在库中定义了一个样本函数(这个问题的前提条件是该库中的所有定义都不能被修改,类似于"只读"):

(defun sample ()
  (foo)
  (bar)
  (baz))
Run Code Online (Sandbox Code Playgroud)

我想使用这个库,但功能sample无法满足我的要求,我想要的是:

(defun sample ()
  (foo)
  (when condition
    (bar))
  (baz))
Run Code Online (Sandbox Code Playgroud)

有人告诉我使用defadvice,但我注意到defadvice只能在调用之前或之后插入代码sample,例如:

(before-advice ...)
(sample)
(after-advice ...)
Run Code Online (Sandbox Code Playgroud)

它不能修改sample自己的定义.那么,我怎样才能慷慨地实现这一目标呢?我是否应该重写sample自己,称为my-samplesample2

phi*_*ils 6

sds的答案是有效的,除了你可能只想建议bar何时sample执行,所以你需要建议样本以激活和停用建议bar.我的with-temporary-advice宏有助于此:

(defmacro with-temporary-advice (function class name &rest body)
  "Enable the specified advice, evaluate BODY, then disable the advice."
  `(unwind-protect
       (progn
         (ad-enable-advice ,function ,class ,name)
         (ad-activate ,function)
         ,@body)
     (ad-disable-advice ,function ,class ,name)
     (ad-activate ,function)))

(defadvice bar (around my-conditional-bar disable)
  ;; This advice disabled by default, and enabled dynamically.
  (when condition
    ad-do-it))

(defadvice sample (around my-sample-advice activate)
  "Make execution of `bar' conditional when running `sample'."
  (with-temporary-advice 'bar 'around 'my-conditional-bar
   ad-do-it))
Run Code Online (Sandbox Code Playgroud)

请注意,如果barsample执行时也以其他方式调用,则建议也将适用于这些调用,因此如果可能,您应该考虑到这一点.

或者,您可能更愿意在需要时flet重新定义bar.当然,这与第一种解决方案有同样的警告.

(defadvice sample (around my-sample-advice activate)
  "Make execution of `bar' conditional when running `sample'."
  (if condition
      ad-do-it
    (flet ((bar () nil))
      ad-do-it)))
Run Code Online (Sandbox Code Playgroud)

容易阅读,但由于我不明白的原因flet,从Emacs 24.3开始,不再支持.它的文档字符串建议使用cl-flet,但作为cl-flet使用词法绑定,它实际上不起作用.最好的我可以说,它听起来flet并没有真正消失,但目前的建议似乎是使用建议.

还要注意,如果在内部bar,不需要的行为取决于某个变量,那么最好使用let该变量上的flet绑定而不是对函数的绑定.

编辑:

当然,这些方法确实使得查看正在发生的事情变得更加困难.根据具体情况,最好简单地重新定义sample函数以执行您想要的操作(或编写my-sample函数以在其位置调用,如您所建议的那样).