Comment manipuler la liste des arguments dans nadvice.el?

12

Suite à une réponse à une autre question sur le nouveau système de conseil :

Dans l'ancien style advice.el, il était possible de manipuler des membres individuels de la liste d'arguments d'une fonction conseillée, sans faire aucune affirmation concernant les membres non manipulés. Par exemple, les conseils suivants:

(defadvice ansi-term (around prompt-for-name last)
  (let ((name (read-from-minibuffer "Tag: ")))
    (and (not (string= name ""))
         (ad-set-arg 1 (concat "Term: " name)))
    ad-do-it))

permet la fourniture (facultative) d'un argument de nom de tampon à un ansi-termappel, tandis ansi-termqu'il obtiendra toujours son premier argument en l'invitant selon sa propre forme interactive.

(Pour référence ultérieure, ansi-termla signature de est (PROGRAM &optional BUFFER-NAME), et sa forme interactive invite à PROGRAMME avec plusieurs valeurs par défaut possibles, mais ne fait rien en ce qui concerne BUFFER-NAME.)

Je ne sais pas si cela est possible ou non nadvice.el. Si c'est le cas, je ne sais pas comment cela peut être fait. J'ai trouvé quelques façons de remplacer la liste d'arguments d'une fonction conseillée.

Par exemple, à partir de * info * (elisp) Combinateurs de conseils :

`:filter-args'
 Call FUNCTION first and use the result (which should be a list) as
 the new arguments to pass to the old function.  More specifically,
 the composition of the two functions behaves like:
      (lambda (&rest r) (apply OLDFUN (funcall FUNCTION r)))

D'autres combinateurs offrent des capacités similaires, et le fil conducteur entre eux est que, bien que la liste d'arguments d'une fonction puisse être remplacée, tronquée, étendue, etc., il n'y a aucun moyen apparent pour que les conseils de fonction modifient l'argument à une position donnée de la liste sans affirmer quoi que ce soit sur le reste .

Dans le cas en discussion, il semble impossible pour l'auteur de conseils de ansi-termne transmettre qu'un nom de tampon, car il n'est pas possible de construire une liste qui a une valeur en position 1 mais rien, pas même nil, en position 0. Dans le cas général, il semble impossible pour l'auteur du conseil de modifier arbitrairement les arguments au-delà de la position 0.

Cela semble regrettable dans la mesure où, pour produire un effet similaire, il est nécessaire de copier-coller du code: en particulier, soit je peux copier ansi-termle formulaire interactif de et l'étendre à mon goût, soit je peux le copier ansi-termet l'étendre de la même manière. Dans les deux cas, je dois maintenant redéfinir une partie de la distribution Emacs Lisp dans mon fichier init, ce qui me semble indésirable en termes de durabilité et d'esthétique.

Ma question est donc la suivante: peut-on éliminer ce type de liste d'arguments nadvice.el? Si c'est le cas, comment?

Aaron Miller
la source
3
Pourquoi ne définissez-vous pas votre propre commande interactive par-dessus ansi-term? Je pense que c'est la solution préférable ici.
lunaryorn
1
Bien sûr, rien ne m'empêche de le faire, mais cela nécessiterait de remplacer la majeure partie de la mémoire musculaire d'une décennie, ce que j'aimerais éviter si je le peux.
Aaron Miller du

Réponses:

5

Cela semble regrettable dans la mesure où, pour produire un effet similaire, il est nécessaire de copier-coller du code: [...] Je peux copier ansi-termle formulaire interactif de

Au contraire, je pense que ce serait une bonne idée de copier-coller la forme interactive de la fonction conseillée, même si vous n'êtes pas obligé de le faire ici.

Je vous lis la question de haut en bas. Quand je suis arrivé au bloc de code, j'ai deviné que votre conseil changeait probablement le nom du tampon. Mais je ne savais pas jusqu'à ce que vous fournissiez plus tard la signature en tant que commentaire.

Dans le cas en discussion, il semble impossible pour l'auteur de conseils de ansi-termne transmettre qu'un nom de tampon, car il n'est pas possible de construire une liste qui a une valeur en position 1 mais rien, pas même nil, en position 0.

En effet, rien n'est moins rien que rien. :-) Mais ce n'est guère pertinent ici.

Comme vous pouvez le voir dans la documentation que vous avez citée, la valeur retournée par le conseil est utilisée comme argument pour la fonction conseillée. La valeur de retour doit être une liste de tous les arguments, pas seulement ceux qui ont changé.

En restant le plus près possible de l'ancien conseil, voici ce que vous auriez à faire en utilisant nadvice:

(defun ansi-term--tag-buffer (args)
  ;; As npostavs pointed out we also have to make sure the list is
  ;; two elements long.  Which makes this approach even more undesirable.
  (when (= (length args) 1)
    (setq args (nconc args (list nil))))
  (let ((name (read-from-minibuffer "Tag: ")))
    (and (not (string= name ""))
         (setf (nth 1 args) (concat "Term: " name))))
  args)

(advice-add 'ansi-term :filter-args 'ansi-term--tag-buffer)

Mais je vous recommande plutôt de définir les conseils comme ceci:

(defun ansi-term--tag-buffer (program &optional buffer-name)
  (list program
        (let ((tag (read-from-minibuffer "Tag: ")))
          (if (string= tag "")
              buffer-name
            (concat "Term: " tag)))))

Cette variante est en fait explicite.

tarse
la source
Pour la 1ère variante, vous devez étendre la argsliste en cas d'appel comme (ansi-term "foo"), sinon (setf (nth 1 args)...cela déclencherait une erreur.
npostavs du
Oui tu as raison. Une autre raison d'utiliser la deuxième variante - la première a un bug ;-) Supposons, à des fins de démonstration, que buffer-namec'est obligatoire.
tarsius
"Au contraire, je pense que ce serait une bonne idée de copier-coller la forme interactive de la fonction conseillée" - pourquoi? Copier-coller du code est une mauvaise idée dans presque tous les autres cas; pourquoi pas ici?
Aaron Miller
En fait, je ne pense pas que "copier-coller" soit le bon terme dans ce cas, je l'ai simplement utilisé parce que vous l'avez fait. Mais même s'il était approprié d'utiliser ce terme ici, alors "ne pas copier-coller" n'est qu'une règle heuristique et non absolue. D'autres heuristiques, qui, je pense , s'appliquent ici, sont «donner des noms significatifs aux variables et aux arguments» et «lorsque vous avez le choix entre compliquer quelque chose ou être verbeux, optez pour verbeux».
tarsius
1
Um, en fait, cela est toujours cassé, les :filter-argsconseils reçoivent un seul argument qui est une liste d'arguments pour la fonction conseillée, donc la 1ère variante devrait être supprimée &restet la 2ème variante devrait utiliser une sorte de construction destructrice pour obtenir des noms sympas.
npostavs
3

Voici comment je le ferais:

(defun my-ansi-term-prompt-for-name (orig-fun program
                                     &optional buffer-name &rest args)
  (apply orig-fun program
         (or buffer-name
             (let ((name (read-string "Tag: ")))
               (and (> (length name) 0)
                    (concat "Term: " name))))
         args))
(advice-add 'ansi-term :around #'my-ansi-term-prompt-for-name)

alors que c'est moi qui l' :filter-argsai présenté, je trouve personnellement que cela est rarement pratique.

Stefan
la source