Come scrivere un wrapper di funzione trasparente "pass-through"?


10

Ciò che intendo per "wrapper di funzioni" trasparenti "pass-through" è una funzione, chiamiamola wrapper, che restituisce il risultato dal passaggio di tutto il suo argomento ad un'altra funzione, chiamiamola wrappee.

Come si fa in Emacs Lisp?

NB: la wrapperfunzione ideale è agnostica rispetto alla wrappeefirma della funzione; cioè non sa nulla del numero, delle posizioni, dei nomi, ecc. degli wrappeeargomenti di; passa semplicemente tutti i suoi argomenti wrappee, proprio come se wrappeefosse stato quello originariamente chiamato. (Non è necessario, tuttavia, pasticciare con lo stack di chiamate per sostituire la chiamata wrappercon una chiamata a wrappee.)

Ho pubblicato una risposta parziale alla mia domanda:

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

Funziona solo quando nonwrappee è interattivo. Apparentemente, il modo in cui le funzioni interattive ottengono i loro argomenti rappresenta un "canale" diverso da ciò che è coperto dall'incantesimo . Ciò di cui ho ancora bisogno, quindi, è una controparte ugualmenteagnostica della firma per il caso in cui è una funzione interattiva .(&rest args)wrappee(&rest args)wrappee

(Questa domanda è stata motivata da un problema descritto in questa domanda precedente .)


Nel caso in cui siano necessari ulteriori chiarimenti su ciò che sto chiedendo, di seguito sono riportati un paio di esempi, che mostrano gli equivalenti Python e JavaScript di ciò che sto cercando.

In Python sono illustrati di seguito alcuni metodi standard per implementare un tale wrapper:

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

# or

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

(Qui *argssta per "tutti gli argomenti posizionali" e **kwargssta per "tutti gli argomenti delle parole chiave".)

L'equivalente JavaScript sarebbe qualcosa del genere:

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

// or

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

Per la cronaca, non sono d'accordo sul fatto che questa domanda sia un duplicato di Come applicare mapcar a una funzione con più argomenti . Non riesco a spiegare perché, poiché le due domande sembrano così ovviamente diverse per me. È come essere chiesto "spiega perché una mela non dovrebbe essere considerata equivalente a un'arancia". La semplice domanda è così folle, che si dubita che si possa mai trovare una risposta che soddisfi la persona che la pone.


Hai considerato l'utilizzo di consulenza / nadvice?
Wasamasa,

@wasamasa: no e, inoltre, non riesco a vedere come consigli / nadvice si applicherebbero a questa domanda. In ogni caso, trovo le advicecose abbastanza problematiche che preferirei starne alla larga. In effetti, la motivazione di questa domanda stava cercando di trovare una soluzione a un problema altrimenti intrattabile che ho con una funzione consigliata ...
kjo,

1
@wasamasa: il consiglio presenta lo stesso problema. Puoi dire cosa fare con qualsiasi argomento, ma per renderlo interattivo devi specificare come devono essere forniti gli argomenti. IOW, devi fornire una interactivespecifica.
Drew

1
Quello che intendevo dire è consigliare alla funzione interattiva originale di fare qualsiasi cosa tu voglia che accada prima e dopo, in questo modo non dovresti preoccuparti delle specifiche interattive.
Wasamasa,

2
@wasamasa: Sì, ma è diverso. Il consiglio è sempre per una funzione particolare , interattiva o no. E se si tratta di un comando, non vi sono problemi: il suo comportamento interattivo viene ereditato per il comando consigliato (a meno che il consiglio non ridefinisca il comportamento interattivo). Questa domanda riguarda una funzione / comando arbitraria , non una particolare.
Drew

Risposte:


11

Naturalmente è possibile includere le interactivespecifiche. Qui abbiamo a che fare con elisp ! (Lisp è la lingua in cui i costrutti più importanti sono gli elenchi. Le forme richiamabili sono solo elenchi. Quindi puoi costruirli dopo i tuoi gusti.)

Applicazione: si desidera aggiungere automaticamente alcune funzionalità ad alcune funzioni. Alle funzioni estese dovrebbero essere assegnati nuovi nomi in modo che defadvicenon siano applicabili.

Innanzitutto una versione che si adatta esattamente al tuo scopo. Impostiamo la cella funzione ( fset) del simbolo wrappercon tutte le informazioni richieste da wrappeee aggiungiamo le nostre cose extra.

Funziona con entrambe le wrappeedefinizioni. La prima versione di wrappeeè interattiva, la seconda no.

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

Ma è più conveniente definire una macro che costruisce le funzioni estese. Quindi possiamo anche specificare i nomi delle funzioni in seguito. (Buono per una versione automatizzata.)

Dopo aver eseguito il codice qui sotto puoi chiamare in modo wrapper-interactiveinterattivo e wrapper-non-interactivenon interattivo.

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

Nota, fino ad ora non ho ancora trovato un modo per trasferire i moduli di dichiarazione, ma ciò dovrebbe anche essere possibile.


2
Qualcuno ha votato male a questa risposta. Non mi interessa davvero il punteggio, ma quello a cui tengo è la ragione per il voto negativo della risposta. Se voti in negativo, ti preghiamo di lasciare un commento! Questo mi darebbe la possibilità di migliorare la risposta.
Tobias,

Non lo so per certo, ma questo è destinato a far sì che chiunque legga il codice di un pacchetto che lo utilizza diventi WTF. Nella maggior parte dei casi, l'opzione più sensata è
gestirla

2
@wasamasa Sono parzialmente d'accordo. Tuttavia, ci sono casi in cui la strumentazione automatica è obbligatoria. Un esempio è edebug. Inoltre, ci sono funzioni in cui la interactivespecifica è considerevolmente più grande del corpo della funzione. In tali casi la riscrittura delle interactivespecifiche può essere piuttosto noiosa. La domanda e la risposta affrontano i principi richiesti.
Tobias,

1
Personalmente, trovo questa risposta abbastanza istruttiva, non solo per quanto riguarda l'ambito della domanda, ma anche in quanto mostra un'applicazione naturale di macro e come si passa dalla defun alla macro. Grazie!
gsl

11

Ho dovuto risolvere un problema molto simile in nadvice.el, quindi ecco una soluzione (che utilizza parte del codice di nadvice.el):

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

Rispetto alle altre soluzioni pubblicate finora, questa ha il vantaggio di funzionare correttamente se wrappeeviene ridefinita con una specifica interattiva diversa (ovvero non continuerà a utilizzare la vecchia specifica).

Naturalmente, se vuoi che il tuo wrapper sia veramente trasparente, puoi farlo più semplicemente:

(defalias 'wrapper #'wrappee)

Questa è l'unica risposta che consente di definire un wrapper che trova ciò che avvolge in fase di esecuzione. Ad esempio, voglio aggiungere un collegamento che esegua un'azione definita da un comando che viene cercato in fase di esecuzione. Usando advice-eval-interactive-speccome suggerito qui posso costruire le specifiche interattive che corrispondono a quel wrapper dinamico.
Igor Bukanov,

E 'possibile fare called-interactively-ptornare tin wrappee? C'è funcall-interactivelyma noapply-interactively
clemera il

1
@compunaut: certo, puoi farlo (apply #'funcall-interactively #'wrappee args)se vuoi. Ma dovresti farlo solo se la funzione viene chiamata in modo interattivo, quindi qualcosa di simile (apply (if (called-interactively-p 'any) #'funcall-interactively #'funcall) #'wrappee args).
Stefan,

Ah grazie! In qualche modo non riuscivo a pensare fuori dalla mia scatola.
clemera,

1

modifica: la risposta di Tobias è più bella di così, in quanto ottiene la forma interattiva precisa e il docstring della funzione wrapped.


Combinando le risposte di Aaron Harris e Kjo, potresti usare qualcosa del tipo:

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

Uso:

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

Call wrapper con uno dei seguenti:

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

M-x wrapper-func

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.