Comment écrire un wrapper de fonction "pass-through" transparent?

10

Ce que je veux dire par un "wrapper de fonction transparent" pass-through "" est une fonction, appelons-la wrapper, qui renvoie le résultat du passage de tous ses arguments à une autre fonction, appelons-la wrappee.

Comment cela se fait-il dans Emacs Lisp?

NB: La wrapperfonction idéale est agnostique quant à la wrappeesignature de la fonction; c'est-à-dire qu'il ne sait rien du nombre, des positions, des noms, etc. des wrappeearguments de; il transmet simplement tous ses arguments à wrappee, comme s'il wrappeeavait été celui initialement appelé. (Il n'est cependant pas nécessaire de jouer avec la pile d'appels pour remplacer l'appel à wrapperpar un appel à wrappee.)

J'ai posté une réponse partielle à ma question:

(defun wrapper (&rest args) (apply 'wrappee args))

Cela ne fonctionne que lorsqu'il wrappeen'est pas interactif. Apparemment, la façon dont les fonctions interactives obtiennent leurs arguments représente un "canal" différent de ce qui est couvert par l' (&rest args)incantation. Ce dont j'ai encore besoin, par conséquent, est une wrappeecontrepartie également -agnostique de la (&rest args)signature pour le cas où wrappeeest une fonction interactive .

(Cette question était motivée par un problème décrit dans cette question précédente .)


Dans le cas où une clarification supplémentaire de ce que je demande est nécessaire, voici quelques exemples, montrant les équivalents Python et JavaScript de ce que je recherche.

En Python, quelques méthodes standard pour implémenter un tel wrapper sont présentées ci-dessous:

def wrapper(*args, **kwargs):
    return wrappee(*args, **kwargs)

# or

wrapper = lambda *args, **kwargs: wrappee(*args, **kwargs)

(Ici *argssignifie "tous les arguments positionnels", et **kwargssignifie "tous les arguments de mots clés".)

L'équivalent JavaScript serait quelque chose comme ceci:

function wrapper () { return wrappee.apply(this, arguments); }

// or

wrapper = function () { return wrappee.apply(this, arguments); }

Pour mémoire, je ne suis pas d'accord que cette question est un doublon de Comment appliquer mapcar à une fonction avec plusieurs arguments . Je n'arrive pas à expliquer pourquoi, puisque les deux questions me semblent si différentes. C'est comme si on lui demandait "d'expliquer pourquoi une pomme ne devrait pas être considérée comme équivalente à une orange". La simple question est tellement folle, qu'on doute que l'on puisse jamais trouver une réponse qui satisfasse la personne qui la pose.

kjo
la source
Avez-vous envisagé d'utiliser des conseils / nadvices?
wasamasa
@wasamasa: non, et d'ailleurs, je ne vois pas comment les conseils / nadvices s'appliqueraient à cette question. En tout cas, je trouve les advicechoses suffisamment problématiques pour que je préfère rester à l'écart. En fait, la motivation pour cette question était d'essayer de trouver une solution à un problème autrement insoluble que j'ai avec une fonction conseillée ...
kjo
1
@wasamasa: Les conseils posent le même problème. Vous pouvez lui dire quoi faire avec l'un des arguments, mais pour le rendre interactif, vous devez spécifier comment les arguments doivent être fournis. IOW, vous devez fournir une interactivespécification.
attiré le
1
Ce que je voulais dire, c'est conseiller à la fonction interactive originale de faire tout ce que vous voulez avant et après, de cette façon, vous ne devriez pas vous soucier de la spécification interactive.
wasamasa
2
@wasamasa: Oui, mais c'est différent. Les conseils sont toujours pour une fonction particulière , qu'elle soit interactive ou non. Et s'il s'agit d'une commande, il n'y a pas de problème - son comportement interactif est hérité de la commande conseillée (à moins que l'avis ne redéfinisse le comportement interactif). Cette question concerne une fonction / commande arbitraire , pas une fonction particulière.
Tiré le

Réponses:

11

Bien sûr, il est possible d'inclure la interactivespécification. Nous avons affaire ici à elisp ! (Lisp est le langage où les constructions les plus importantes sont des listes. Les formulaires appelables ne sont que des listes. Vous pouvez donc les construire à votre guise.)

Application: vous souhaitez ajouter automatiquement certaines fonctionnalités à certaines fonctions. Les fonctions étendues doivent recevoir de nouveaux noms afin que cela defadvicene s'applique pas.

D'abord une version qui correspond tout à fait à votre objectif. Nous définissons la cellule de fonction ( fset) du symbole wrapperavec toutes les informations requises wrappeeet ajoutons nos éléments supplémentaires.

Cela fonctionne pour les deux wrappeedéfinitions. La première version de wrappeeest interactive, la seconde ne l'est pas.

(defun wrappee (num str)
  "Nontrivial wrappee."
  (interactive "nNumber:\nsString:")
  (message "The number is %d.\nThe string is \"%s\"." num str))

(defun wrappee (num str)
  "Noninteractive wrappee."
  (message "The number is %d.\nThe string is \"%s\"." num str))

(fset 'wrapper (list 'lambda
             '(&rest args)
             (concat (documentation 'wrappee t) "\n Wrapper does something more.")
             (interactive-form 'wrappee)
             '(prog1 (apply 'wrappee args)
            (message "Wrapper does something more."))))

Mais il est plus pratique de définir une macro qui construit les fonctions étendues. Avec cela, nous pouvons même spécifier les noms de fonction par la suite. (Bon pour une version automatisée.)

Après avoir exécuté le code ci-dessous, vous pouvez appeler de manière wrapper-interactiveinteractive et wrapper-non-interactivenon interactive.

(defmacro make-wrapper (wrappee wrapper)
  "Create a WRAPPER (a symbol) for WRAPPEE (also a symbol)."
  (let ((arglist (make-symbol "arglist")))
  `(defun ,wrapper (&rest ,arglist)
     ,(concat (documentation wrappee) "\n But I do something more.")
     ,(interactive-form wrappee)
     (prog1 (apply (quote ,wrappee) ,arglist)
       (message "Wrapper %S does something more." (quote ,wrapper))))))

(defun wrappee-interactive (num str)
  "That is the doc string of wrappee-interactive."
  (interactive "nNumber:\nsString:")
  (message "The number is %d.\nThe string is \"%s\"." num str))

(defun wrappee-non-interactive (format &rest arglist)
  "That is the doc string of wrappee-non-interactive."
  (apply 'message format arglist))

(make-wrapper wrappee-interactive wrapper-interactive)
(make-wrapper wrappee-non-interactive wrapper-non-interactive)
;; test of the non-interactive part:
(wrapper-non-interactive "Number: %d, String: %s" 1 "test")

Remarque, jusqu'à présent, je n'ai pas encore trouvé de moyen de transférer les formulaires de déclaration, mais cela devrait également être possible.

Tobias
la source
2
Hm, quelqu'un a voté contre cette réponse. Je ne me soucie pas vraiment du score, mais ce qui m'importe, c'est la raison pour laquelle j'ai voté contre la réponse. Si vous votez contre, veuillez également laisser un commentaire! Cela me donnerait une chance d'améliorer la réponse.
Tobias
Je ne sais pas avec certitude, mais cela ne manquera pas de faire en sorte que quiconque lisant le code d'un package l'utilisant devienne WTF. Dans la plupart des cas, l'option la plus judicieuse consiste à y faire face et à écrire une fonction faisant le wrapping manuellement (que ce soit avec
Apply
2
@wasamasa Je suis partiellement d'accord. Néanmoins, il existe des cas où l'instrumentation automatique est obligatoire. Un exemple est edebug. En outre, il existe des fonctions où la interactivespécification-est considérablement plus grande que le corps de la fonction. Dans de tels cas, la réécriture de la interactivespécification peut être assez fastidieuse. La question et la réponse portent sur les principes requis.
Tobias
1
Personnellement, je trouve cette réponse assez instructive, non seulement en ce qui concerne la portée de la question, mais aussi car elle montre une application naturelle des macros, et comment on passe du defun à la macro. Merci!
gsl
11

J'ai dû résoudre un problème très similaire dans nadvice.el, alors voici une solution (qui utilise une partie du code de nadvice.el):

(defun wrapper (&rest args)
  (interactive (advice-eval-interactive-spec
                (cadr (interactive-form #'wrappee))))
  (apply #'wrappee args))

Par rapport aux autres solutions publiées jusqu'à présent, celle-ci a l'avantage de fonctionner correctement si elle wrappeeest redéfinie avec une spécification interactive différente (c'est-à-dire qu'elle ne continuera pas à utiliser l'ancienne spécification).

Bien sûr, si vous voulez que votre emballage soit vraiment transparent, vous pouvez le faire plus simplement:

(defalias 'wrapper #'wrappee)
Stefan
la source
C'est la seule réponse qui permet de définir un wrapper qui trouve ce qu'il encapsule lors de l'exécution. Par exemple, je veux ajouter un raccourci qui exécute une action définie par une commande qui est recherchée lors de l'exécution. En utilisant advice-eval-interactive-speccomme suggéré ici, je peux construire la spécification interactive qui correspond à ce wrapper dynamique.
Igor Bukanov
Est - il possible de faire called-interactively-prevenir ten wrappee? Il n'y en a funcall-interactivelypasapply-interactively
clemera
1
@compunaut: Bien sûr, vous pouvez le faire (apply #'funcall-interactively #'wrappee args)si vous le souhaitez. Mais vous ne devriez le faire que si la fonction est appelée de manière interactive, donc quelque chose comme (apply (if (called-interactively-p 'any) #'funcall-interactively #'funcall) #'wrappee args).
Stefan
Ha, merci! D'une certaine manière, je ne pouvais pas penser en dehors de ma boîte.
clemera
1

edit: la réponse de Tobias est plus agréable que cela, car elle obtient la forme interactive précise et la docstring de la fonction encapsulée.


En combinant les réponses d'Aaron Harris et de kjo, vous pourriez utiliser quelque chose comme:

(defmacro my-make-wrapper (fn &optional name)
  "Return a wrapper function for FN defined as symbol NAME."
  `(defalias ',(or (eval name)
                   (intern (concat "my-" (symbol-name (eval fn)) "-wrapper")))
     (lambda (&rest args)
       ,(format "Generic wrapper for %s."
                (if (symbolp (eval fn))
                    (concat "`" (symbol-name (eval fn)) "'")
                  fn))
       (interactive)
       (if (called-interactively-p 'any)
           (call-interactively ,fn)
         (apply ,fn args)))))

Usage:

(my-make-wrapper 'find-file 'wrapper-func)

Habillage d'appel avec l'un des éléments suivants:

(wrapper-func "~/.emacs.d/init.el")

M-x wrapper-func

phils
la source