Comment utiliser nadvice?

29

Ma config est pleine de conseils, et j'entends toujours parler du nouveau nadvice.elpackage minimaliste brillant .

J'ai cherché dans les manuels et j'ai lu la source , mais je l'admets ouvertement: je n'ai toujours aucune idée de comment l'utiliser réellement.

Quelqu'un ici peut-il me diriger vers un guide ou me dire comment commencer à transférer mes conseils à l'ancienne?

PythonNut
la source
7
+1 pour la question. Si vous avez cherché les manuels et pas trouvé ce que vous avez besoin, s'il vous plaît envisager de déposer un rapport de bogue (doc): M-x report-emacs-bug. Certains développeurs préfèrent parfois développer plutôt que documenter. ;-) Il est important que Emacs se documente lui-même.
Drew
2
Le manuel contient en fait une section à ce sujet, voir (info "(elisp) Portage d'anciens conseils") . Il n'est pas répertorié dans l'index détaillé pour quelque raison que ce soit.
wasamasa
3
Quelques exemples en utilisant nadvicede ma config: : après , : filtre retour , : environ , : avant-jusqu'à
Kaushal Modi
1
@wasamasa J'ai bien peur que cette section soit loin d'être complète. J'ai quelques conseils (peut-être un seul, nous verrons) qui sont plus complexes. Dois-je simplement poser une question pour chacun ici?
PythonNut

Réponses:

22

Toutes les informations dont vous avez besoin sont incluses C-h f add-functionet décrivent le mécanisme sous-jacent de advice-add.

Le nouveau système de conseil agit essentiellement comme le remplacement de la définition actuelle d'une fonction par la fonction décrite dans le tableau dans C-h f add-function, selon votre choix d' WHERE argument, uniquement plus propre pour le suivi du comportement défini dans quel fichier source.

Un exemple avec l' :aroundoption

Le cas le plus général est l' :aroundoption, donc je donne un exemple pour cela. (Il est probablement préférable d'utiliser des WHEREparamètres dédiés lorsque cela est possible, mais vous pouvez les remplacer les uns les autres par une :aroundfonction équivalente ).

À titre d'exemple, disons que vous souhaitez déboguer une certaine utilisation de find-file et souhaitez afficher printsa liste d'arguments à chaque appel. Tu pourrais écrire

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

Avec cette nouvelle implémentation, tout ce dont les conseils ont besoin est passé en argument. ad-get-argsdevient inutile, car les arguments sont passés à la fonction de conseil en tant qu'arguments de la fonction normale (pour les WHEREarguments pour lesquels cela a du sens). ad-do-itdevient inutile car les :aroundconseils reçoivent comme arguments la fonction et les arguments, donc (ad-do-it)est remplacé par le formulaire

(apply old-function arguments)

ou quand vous avez nommé les arguments

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

ce qui est plus propre car il n'y a pas de formes magiques impliquées. La modification des arguments se produit simplement en passant des valeurs modifiées à OLD-FUNCTION.

Autres WHEREvaleurs

La docstring de add-functioncontient un tableau de tous les lieux de conseil (ou "combinateurs"), et à quoi ils sont équivalents, et explique la fonctionnalité en termes d' lambdaéquivalent de comportement à la fonction conseillée:

`: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')

où FUNCTION est la fonction de conseil et OLDFUN la fonction où le conseil est ajouté. N'essayez pas de les comprendre tous en même temps, sélectionnez simplement un WHEREsymbole qui vous convient et essayez de le comprendre.

Ou utilisez simplement :around. Pour autant que je sache, le seul avantage d'utiliser des WHEREs spécialisés :aroundpour tout est que vous obtenez un peu plus d'informations en recherchant C-h f ADVISED-FUNCTION avant de lire la docstring des conseils. Sauf si vous prévoyez de publier le code contenant les conseils, cela n'a probablement pas d'importance.

Fonctions de conseil nommées

Je recommande d'utiliser des fonctions nommées comme conseil car il offre de nombreux avantages (certains d'entre eux s'appliquent également à l'utilisation de fonctions nommées pour les hooks):

  • Il apparaît en C-h f find-filetant que

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

    lien vers la définition de la fonction de conseil, qui contient comme d'habitude un lien vers le fichier où elle a été définie. Si l'avis avait été défini comme un lambdaformulaire directement dans le advice-add formulaire, la docstring serait affichée en ligne (un gâchis pour les longues docstring?) Et rien n'indiquerait où il a été défini.

  • Vous pouvez supprimer le conseil avec

    (advice-remove #'find-file #'my-find-file-advice-print-arguments)
    
  • Vous pouvez mettre à jour la définition de l'avis sans relancer advice-addou risquer de garder l'ancienne version active (car l'exécution advice-addavec une modification lambdasera reconnue comme un nouvel avis, pas comme une mise à jour de l'ancienne).

Remarque latérale La #'functionnotation est fondamentalement équivalente à 'function, sauf qu'elle aide le compilateur d'octets à identifier les symboles en tant que noms de fonction et donc à identifier les fonctions manquantes (par exemple en raison de fautes de frappe).

kdb
la source
Conformément à la discussion que j'ai eue avec Stephen Monnier, les guillemets ne doivent pas être utilisés ici dans tous les arguments. Ils devraient l'être (advice-add 'find-file :around #'my-find-file-advice-print-arguments)et de la même manière (advice-remove 'find-file #'my-find-file-advice-print-arguments).
Kaushal Modi
Je suppose que advice-addc'est un cas frontalier. Personnellement, je considère que la ' ↔ #'distinction est principalement une aide à l'identification des fautes de frappe dans les noms de fonction, donc ici, cela dépendrait probablement de si l'on s'attend à ce que la fonction soit définie au moment où l'avis est ajouté.
kdb
@kdb J'ai finalement découvert cela par moi-même (après avoir rencontré les documents pour add-function). Je souhaite que les documents soient plus clairs. Je pourrais envisager de faire un patch pour cela.
PythonNut
@kdb Voulez-vous dire "Il apparaît C-h f find-file, non C-x?
Peeja
@Peeja Oui, corrigé.
kdb