29

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?

  • 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 8 '15 at 4:30
  • 2
    The manual actually has a section on that, see (info "(elisp) Porting old advices"). It's not listed in the detailed index for whatever reason though. – wasamasa Jun 8 '15 at 5:17
  • 4
    Closely related: Practical benefits of new advice system in Emacs 24.4 – itsjeyd Jun 8 '15 at 10:14
  • 3
    Few examples using nadvice from my config: :after, :filter-return, :around, :before-until – Kaushal Modi Jun 8 '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 8 '15 at 19:24
22

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).

  • As per the discussion 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 4 '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 7:41

Not the answer you're looking for? Browse other questions tagged or ask your own question.