31

My config is full of advice, and I keep hearing about the new shiny minimalist nadvice.el package.

I've searched the manuals, and I've read the source, but I'll openly admit: I still have no idea how to actually use it.

Can anyone here point me to a guide, or tell me how to get started porting my old-style advice over?

PythonNut
  • 10,243
  • 2
  • 29
  • 75
  • 7
    +1 for the question. If you've searched the manuals and not found what you needed, please consider filing a (doc) bug report: `M-x report-emacs-bug`. Some developers sometimes prefer developing over documenting. ;-) It is important that Emacs document itself. – Drew Jun 08 '15 at 04:30
  • 2
    The manual actually has a section on that, see [(info "(elisp) Porting old advices")](http://www.gnu.org/software/emacs/manual/html_node/elisp/Porting-old-advices.html#Porting-old-advices). It's not listed in the detailed index for whatever reason though. – wasamasa Jun 08 '15 at 05:17
  • 4
    Closely related: [Practical benefits of new advice system in Emacs 24.4](http://emacs.stackexchange.com/q/3079/504) – itsjeyd Jun 08 '15 at 10:14
  • 3
    Few examples using `nadvice` from my config: [:after](https://github.com/kaushalmodi/.emacs.d/blob/6079fb2b0cdcf70452d4d110d85f27826a4899a2/setup-files/setup-editing.el#L679-L690), [:filter-return](https://github.com/kaushalmodi/.emacs.d/blob/6079fb2b0cdcf70452d4d110d85f27826a4899a2/setup-files/setup-undo-tree.el#L17-L19), [:around](https://github.com/kaushalmodi/.emacs.d/blob/6079fb2b0cdcf70452d4d110d85f27826a4899a2/setup-files/setup-deft.el#L28-L32), [:before-until](https://github.com/kaushalmodi/.emacs.d/blob/6079fb2b0cdcf70452d4d110d85f27826a4899a2/setup-files/setup-verilog.el#L348-L367) – Kaushal Modi Jun 08 '15 at 13:59
  • 1
    @wasamasa I'm afraid that section is far from complete. I have a few advices (maybe just one, we'll see) that are more complex. Should I just make a question for each here? – PythonNut Jun 08 '15 at 19:24
  • 1
    Adding to @kaushalmodi's list, here is a [:filter-args](https://github.com/Archenoth/dotemacs/blob/b888a73bab8859bbd4e3201f294a2baa781a3346/Archenoth-config.org#the-actual-style-logic) from my config. It essentially lets me change arguments that a function gets called with before the function runs. The `(list buffer-or-name action frame)` list at the end of `my-display-buffer` are the arguments that the advised `display-buffer` gets called with in the end. – Archenoth Jul 17 '15 at 06:16
  • Have you seen [the section on porting to nadvice](https://www.gnu.org/software/emacs/manual/html_node/elisp/Porting-old-advices.html#Porting-old-advices)? – omajid Aug 13 '15 at 00:52
  • 1
    It's at best incomplete. At worst, it's just confusing. It doesn't explain what happens to `ad-return-value`, `ad-do-it`, and `ad-get-orig-definition`, among other nuances. – PythonNut Aug 13 '15 at 01:02

1 Answers1

33

All information you need is included in C-h f add-function which describes the underlying mechanism of advice-add.

The new advice system basically acts like replacing the current definition of a function by the function described in the table in C-h f add-function, depending on your choice of the WHERE argument, only cleaner for the sake of tracking what behaviour has been defined in what source file.

An example with the :around option

The most general case is the :around option, so I give an example for that. (It is probably better to use dedicated WHERE parameters when possible, but you can replace every other by an equivalent :around function).

Just as an example, lets say you want to debug some use of find-file and want to print its argument list every time it is called. You could write

(defun my-find-file-advice-print-arguments (old-function &rest arguments)
  "Print the argument list every time the advised function is called."
  (print arguments)
  (apply old-function arguments))

(advice-add #'find-file :around #'my-find-file-advice-print-arguments)

With this new implementation, everything the advice needs is passed as argument. ad-get-args becomes unnecessary, because the arguments are passed to the advice function as normal function arguments (for WHERE arguments for which it makes sense). ad-do-it becomes unnecessary as :around advice gets as arguments the function and the arguments, so (ad-do-it) is replaced by the form

(apply old-function arguments)

or when you have named the arguments

(funcall old-function first-arg second-arg)

which is cleaner as there are no magic forms involved. Modifying the arguments simply happens by passing modified values to OLD-FUNCTION.

Other WHERE values

The docstring of add-function contains a table of all advice places (or "combinators"), and what they are equivalent to, and explains the functionality in terms of a lambda behaving equivalent to the advised function:

`:before'       (lambda (&rest r) (apply FUNCTION r) (apply OLDFUN r))
`:after'        (lambda (&rest r) (prog1 (apply OLDFUN r) (apply FUNCTION r)))
`:around'       (lambda (&rest r) (apply FUNCTION OLDFUN r))
`:override'     (lambda (&rest r) (apply FUNCTION r))
`:before-while' (lambda (&rest r) (and (apply FUNCTION r) (apply OLDFUN r)))
`:before-until' (lambda (&rest r) (or  (apply FUNCTION r) (apply OLDFUN r)))
`:after-while'  (lambda (&rest r) (and (apply OLDFUN r) (apply FUNCTION r)))
`:after-until'  (lambda (&rest r) (or  (apply OLDFUN r) (apply FUNCTION r)))
`:filter-args'  (lambda (&rest r) (apply OLDFUN (funcall FUNCTION r)))
`:filter-return'(lambda (&rest r) (funcall FUNCTION (apply OLDFUN r)))

(cited from `C-h f add-function')

where FUNCTION is the advice function and OLDFUN the function where the advice is added. Don't try to understand all of them at once, just select a WHERE symbol that sounds fitting and try to understand that one.

Or just use :around. As far as I can tell the only advantage of using specialized WHEREs over :around for everything is that you get a bit more information from looking up C-h f ADVISED-FUNCTION prior to reading the docstring of the advice. Unless you plan to publish the code containing the advice it probably doesn't matter.

Named advice functions

I recommend using named functions as advice since it provides many advantages (some of them also apply to using named functions for hooks):

  • It shows up in C-h f find-file as

    :around advice: `my-find-file-advice-print-arguments'
    

    linking to the definition of the advice function, which as usual contains a link to the file where it was defined . If the advice had been defined as a lambda form directly in the advice-add form the docstring would be shown inline (a mess for long docstrings?) and nothing would indicate where it was defined.

  • You can remove the advice with

    (advice-remove #'find-file #'my-find-file-advice-print-arguments)
    
  • You can update the definition of the advice without rerunning advice-add or risking to keep the old version active (as running advice-add with a changed lambda will be recognized as new advice, not as an update to the old one).

Side remark The #'function notation is basically equivalent to 'function, except that it help the byte compiler identify symbols as function names and thus to identify missing functions (e.g. due to typos).

Aaron Miller
  • 552
  • 2
  • 12
kdb
  • 1,561
  • 12
  • 21
  • As per the [discussion](http://lists.gnu.org/archive/html/emacs-devel/2015-06/msg00308.html) I had with Stephen Monnier, hash-quotes should not be used here in all arguments.. it should be `(advice-add 'find-file :around #'my-find-file-advice-print-arguments)` and similarly `(advice-remove 'find-file #'my-find-file-advice-print-arguments)`. – Kaushal Modi Aug 18 '15 at 13:14
  • I guess `advice-add` is a border case. Personally I consider the `' ↔ #'` distinction as mostly a help to identify typos in function names, so here it would probably depend on whether one expects the function to be defined by the time the advice is added. – kdb Aug 18 '15 at 21:56
  • @kdb I eventually found this out for myself (after I ran into the docs for `add-function`). I wish the docs made that clearer. I might look to making a patch for it. – PythonNut Sep 04 '15 at 16:40
  • @kdb Do you mean "It shows up in `C-h f find-file`, not `C-x`? – Peeja Mar 28 '16 at 19:54
  • @Peeja Yes, corrected it. – kdb Mar 30 '16 at 07:41