Wie schreibe ich einen transparenten "Pass-Through" -Funktions-Wrapper?

10

Was ich mit einem "transparenten 'Pass-Through'-Funktions-Wrapper" meine, ist eine Funktion, nennen wir sie wrapper, die das Ergebnis aus der Übergabe aller Argumente an eine andere Funktion zurückgibt, nennen wir sie wrappee.

Wie geht das in Emacs Lisp?

NB: Die ideale wrapperFunktion ist unabhängig von der wrappeeSignatur der Funktion. dh es weiß nichts über die Anzahl, Positionen, Namen usw. der wrappeeArgumente; es gibt einfach alle seine Argumente an weiter wrappee, als wäre wrappeees das ursprünglich genannte gewesen. (Es ist jedoch nicht erforderlich, sich mit dem Aufrufstapel herumzuschlagen, um den Aufruf von wrapperdurch einen Aufruf von zu ersetzen wrappee.)

Ich habe eine teilweise Antwort auf meine Frage gepostet :

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

Dies funktioniert nur, wenn wrappeees nicht interaktiv ist. Anscheinend stellt die Art und Weise, wie interaktive Funktionen ihre Argumente erhalten, einen anderen "Kanal" dar als die (&rest args)Beschwörung. Was ich daher noch brauche, ist ein ebenso wrappee-agnostisches Gegenstück zur (&rest args)Signatur für den Fall, dass wrappeees sich um eine interaktive Funktion handelt.

(Diese Frage wurde durch ein in dieser früheren Frage beschriebenes Problem motiviert .)


Für den Fall, dass weitere Erläuterungen zu dem, wonach ich frage, erforderlich sind, finden Sie nachfolgend einige Beispiele, die die Python- und JavaScript-Entsprechungen dessen zeigen, wonach ich suche.

In Python werden im Folgenden einige Standardmethoden zum Implementieren eines solchen Wrappers gezeigt:

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

# or

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

(Hier *argssteht für "alle Positionsargumente" und **kwargsfür "alle Schlüsselwortargumente".)

Das JavaScript-Äquivalent wäre ungefähr so:

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

// or

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

Ich bin nicht der Meinung, dass diese Frage ein Duplikat von Wie man Mapcar auf eine Funktion mit mehreren Argumenten anwendet . Ich kann nicht erklären, warum, da die beiden Fragen für mich so offensichtlich unterschiedlich aussehen. Es ist, als würde man gefragt, "warum ein Apfel nicht als gleichwertig mit einer Orange angesehen werden sollte". Die bloße Frage ist so verrückt, dass man bezweifelt, jemals eine Antwort finden zu können, die die fragende Person zufriedenstellt.

kjo
quelle
Haben Sie darüber nachgedacht, Ratschläge / Ratschläge zu verwenden?
Wasamasa
@wasamasa: nein, und außerdem sehe ich nicht, wie Ratschläge / Ratschläge für diese Frage gelten würden. Auf jeden Fall finde ich das adviceZeug problematisch genug, dass ich mich lieber davon fernhalten möchte. Tatsächlich bestand die Motivation für diese Frage darin, eine Lösung für ein ansonsten unlösbares Problem zu finden, das ich mit einer empfohlenen Funktion habe ...
kjo
1
@wasamasa: Beratung stellt das gleiche Problem. Sie können festlegen, was mit den Argumenten geschehen soll. Um sie jedoch interaktiv zu gestalten, müssen Sie angeben, wie die Argumente bereitgestellt werden sollen. IOW, Sie müssen eine interactiveSpezifikation angeben.
Drew
1
Was ich damit gemeint habe, ist der Empfehlung der ursprünglichen interaktiven Funktion, alles zu tun, was Sie vorher und nachher tun möchten. Auf diese Weise sollten Sie sich keine Gedanken über die interaktive Spezifikation machen müssen.
Wasamasa
2
@wasamasa: Ja, aber das ist anders. Beratung ist immer für eine bestimmte Funktion, ob interaktiv oder nicht. Und wenn es sich um einen Befehl handelt, gibt es kein Problem - sein interaktives Verhalten wird für den empfohlenen Befehl vererbt (es sei denn, der Rat definiert das interaktive Verhalten neu). Diese Frage bezieht sich auf eine beliebige Funktion / einen beliebigen Befehl, nicht auf eine bestimmte.
Drew

Antworten:

11

Natürlich ist es inklusive der interactiveSpezifikation möglich. Wir haben es hier mit elisp zu tun ! (Lisp ist die Sprache, in der die wichtigsten Konstrukte Listen sind. Aufrufbare Formulare sind nur Listen. Sie können sie also nach Ihren Wünschen erstellen.)

Anwendung: Sie möchten einigen Funktionen automatisch einige Funktionen hinzufügen. Die erweiterten Funktionen sollten neue Namen erhalten, damit sie defadvicenicht zutreffen.

Zuerst eine Version, die genau zu Ihrem Zweck passt. Wir setzen die Funktionszelle ( fset) des Symbols wrappermit allen erforderlichen Informationen aus wrappeeund fügen unsere zusätzlichen Inhalte hinzu.

Es funktioniert für beide wrappeeDefinitionen. Die erste Version von wrappeeist interaktiv, die zweite nicht.

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

Es ist jedoch bequemer, ein Makro zu definieren, das die erweiterten Funktionen erstellt. Damit können wir sogar die Funktionsnamen nachträglich angeben. (Gut für eine automatisierte Version.)

Nachdem Sie den folgenden Code ausgeführt haben, können Sie wrapper-interactiveinteraktiv und wrapper-non-interactivenicht interaktiv aufrufen .

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

Beachten Sie, dass ich bisher noch keine Möglichkeit gefunden habe, die Deklarationsformulare zu übertragen, aber das sollte auch möglich sein.

Tobias
quelle
2
Hm, jemand hat diese Antwort abgelehnt. Die Punktzahl interessiert mich nicht wirklich, aber was mich interessiert, ist der Grund, warum ich die Antwort abgelehnt habe. Wenn Sie abstimmen, hinterlassen Sie bitte auch einen Kommentar! Dies würde mir eine Chance geben, die Antwort zu verbessern.
Tobias
Ich weiß es nicht genau, aber dies wird jeden dazu bringen, den Code eines Pakets, das es verwendet, zu lesen. In den meisten Fällen ist es sinnvoller, sich damit zu befassen und eine Funktion zu schreiben, die das Umbrechen manuell
ausführt
2
@wasamasa Ich stimme teilweise zu. Es gibt jedoch Fälle, in denen eine automatische Instrumentierung obligatorisch ist. Ein Beispiel ist edebug. Darüber hinaus gibt es Funktionen, bei denen die interactiveSpezifikation erheblich größer ist als der Funktionskörper. In solchen Fällen kann das Umschreiben der interactiveSpezifikation ziemlich mühsam sein. Die Frage und die Antwort richten sich nach den erforderlichen Grundsätzen.
Tobias
1
Persönlich finde ich diese Antwort sehr lehrreich, nicht nur in Bezug auf den Umfang der Frage, sondern auch, da sie eine natürliche Anwendung von Makros zeigt und wie man von defun zu makro wechselt. Vielen Dank!
GSL
11

Ich musste ein sehr ähnliches Problem in lösen nadvice.el, daher hier eine Lösung (die einen Teil des Codes von nadvice.el verwendet):

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

Im Vergleich zu den anderen bisher veröffentlichten Lösungen hat diese den Vorteil, dass sie korrekt funktioniert, wenn sie wrappeemit einer anderen interaktiven Spezifikation neu definiert wird (dh die alte Spezifikation wird nicht weiter verwendet).

Wenn Sie möchten, dass Ihr Wrapper wirklich transparent ist, können Sie dies natürlich einfacher tun:

(defalias 'wrapper #'wrappee)
Stefan
quelle
Dies ist die einzige Antwort, mit der ein Wrapper definiert werden kann, der findet, was er zur Laufzeit umschließt. Zum Beispiel möchte ich eine Verknüpfung hinzufügen, die eine Aktion ausführt, die durch einen Befehl definiert ist, der zur Laufzeit nachgeschlagen wird. Mit advice-eval-interactive-specden hier vorgeschlagenen Informationen kann ich die interaktive Spezifikation erstellen, die diesem dynamischen Wrapper entspricht.
Igor Bukanov
Ist es möglich zu machen called-interactively-pRückkehr tin wrappee? Es gibt funcall-interactivelyaber keineapply-interactively
Clemera
1
@compunaut: Natürlich kannst du tun, (apply #'funcall-interactively #'wrappee args)wenn du willst. Aber Sie sollten es nur tun, wenn die Funktion interaktiv aufgerufen wird, also so etwas wie (apply (if (called-interactively-p 'any) #'funcall-interactively #'funcall) #'wrappee args).
Stefan
Ha, danke! Irgendwie konnte ich nicht über meinen Tellerrand hinaus denken.
Clemera
1

edit: Tobias 'Antwort ist schöner als diese, da sie die genaue interaktive Form und Dokumentation der umschlossenen Funktion erhält.


Wenn Sie die Antworten von Aaron Harris und kjo kombinieren, könnten Sie Folgendes verwenden:

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

Verwendungszweck:

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

Rufen Sie den Wrapper mit einer der folgenden Optionen auf:

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

M-x wrapper-func

Phils
quelle