Jak napisać przezroczyste opakowanie funkcji „pass-through”?

10

To, co rozumiem przez „przezroczyste” przekazujące „opakowanie funkcji”, to funkcja, nazwijmy ją wrapper, która zwraca wynik z przekazania całego argumentu do innej funkcji, nazwijmy ją wrappee.

Jak to się robi w Emacs Lisp?

Uwaga: Idealna wrapperfunkcja jest niezależna od wrappeepodpisu funkcji; tzn. nie wie nic o liczbie, pozycjach, nazwach itp. wrappeeargumentów; po prostu przekazuje wszystkie swoje argumenty wrappee, tak jakby wrappeebył pierwotnie nazywany. (Nie ma jednak potrzeby mieszania się ze stosem wywołań w celu zastąpienia połączenia wrapperwywołaniem do wrappee).

Zamieściłem częściową odpowiedź na moje pytanie:

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

Działa to tylko wtedy, gdy niewrappee jest interaktywne. Najwyraźniej sposób, w jaki funkcje interaktywne pobierają argumenty, reprezentuje inny „kanał” niż to, co obejmuje zaklęcie. Co jeszcze trzeba zatem jest equally- -agnostic odpowiednikiem podpisu dla przypadku, gdy stanowi interaktywny funkcji.(&rest args)wrappee(&rest args)wrappee

(To pytanie było motywowane problemem opisanym w tym wcześniejszym pytaniu ).


Na wypadek, gdyby potrzebne było dalsze wyjaśnienie tego, o co proszę, poniżej znajduje się kilka przykładów pokazujących ekwiwalenty Pythona i JavaScript tego, czego szukam.

W Pythonie pokazano kilka standardowych sposobów implementacji takiego opakowania:

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

# or

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

(Tutaj *argsoznacza „wszystkie argumenty pozycyjne” i **kwargs„wszystkie argumenty słów kluczowych”).

Odpowiednik JavaScript byłby mniej więcej taki:

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

// or

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

Dla przypomnienia , nie zgadzam się, że to pytanie jest duplikatem Jak zastosować mapcar do funkcji z wieloma argumentami . Nie potrafię wyjaśnić, dlaczego, ponieważ te dwa pytania wyglądają tak wyraźnie dla mnie inaczej. To tak, jakby zapytano „wyjaśnij, dlaczego jabłko nie powinno być uważane za ekwiwalent pomarańczy”. Samo pytanie jest tak szalone, że można wątpić, czy można znaleźć odpowiedź, która zadowoli osobę zadającą pytanie.

kjo
źródło
Czy rozważałeś skorzystanie z porady / nadvice?
wasamasa,
@wasamasa: nie, a ponadto nie widzę, jak porada / nadvice miałaby zastosowanie do tego pytania. W każdym razie uważam to za advicewystarczająco problematyczne, więc wolałbym trzymać się z daleka. W rzeczywistości motywem do tego pytania było znalezienie rozwiązania nierozwiązywalnego problemu, który mam z zalecaną funkcją ...
kjo
1
@wasamasa: Porady stanowią ten sam problem. Możesz powiedzieć mu, co zrobić z dowolnym argumentem, ale aby był interaktywny, musisz określić sposób dostarczenia argumentów. IOW, musisz podać interactivespecyfikację.
Drew
1
Miałem na myśli doradzanie oryginalnej funkcji interaktywnej, aby zrobiła wszystko, co chcesz, przed i po niej, w ten sposób nie powinieneś martwić się o interaktywną specyfikację.
wasamasa,
2
@wasamasa: Tak, ale jest inaczej. Porady dotyczą zawsze określonej funkcji, interaktywnej lub nie. A jeśli jest to polecenie, nie ma problemu - jego zachowanie interaktywne jest dziedziczone dla polecenia zalecanego (chyba że rada redefiniuje zachowanie interaktywne). To pytanie dotyczy dowolnej funkcji / polecenia, a nie konkretnej.
Drew

Odpowiedzi:

11

Oczywiście jest to możliwe łącznie ze interactivespecyfikacją. Mamy tu do czynienia z elisp ! (Lisp jest językiem, w którym najważniejszymi konstrukcjami są listy. Formy wywoływalne to tylko listy. Możesz je budować według własnych upodobań).

Zastosowanie: Chcesz dodać funkcję do niektórych funkcji w sposób automatyczny. Rozszerzone funkcje powinny otrzymać nowe nazwy, więc defadvicenie ma to zastosowania.

Najpierw wersja, która pasuje dokładnie do twojego celu. Ustawiamy funkcję cell ( fset) symbolu wrapperze wszystkimi wymaganymi informacjami z wrappeei dodajemy nasze dodatkowe rzeczy.

Działa dla obu wrappeedefinicji. Pierwsza wersja wrappeejest interaktywna, druga nie.

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

Ale wygodniej jest zdefiniować makro, które konstruuje rozszerzone funkcje. W ten sposób możemy nawet później podać nazwy funkcji. (Dobre dla wersji automatycznej).

Po wykonaniu poniższego kodu możesz dzwonić wrapper-interactiveinteraktywnie i wrapper-non-interactivenieinteraktywnie.

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

Uwaga: do tej pory nie znalazłem sposobu na przesłanie formularzy deklaracji, ale powinno to być również możliwe.

Tobiasz
źródło
2
Hm, ktoś zlekceważył tę odpowiedź. Tak naprawdę nie dbam o wynik, ale to, co mnie obchodzi, jest powodem odrzucenia odpowiedzi na głos. Jeśli głosujesz w dół, zostaw również komentarz! To dałoby mi szansę na poprawienie odpowiedzi.
Tobias
Nie wiem na pewno, ale to na pewno sprawi, że każdy, kto odczytuje kod pakietu używającego go, przechodzi na WTF. W większości przypadków bardziej sensowną opcją jest poradzenie sobie z tym i napisanie funkcji wykonującej ręczne zawijanie (czy to z zastosowaniem lub przez przepisanie części specyfikacji interaktywnej
wasamasa,
2
@wasamasa Częściowo się zgadzam. Niemniej jednak zdarzają się przypadki, w których automatyczne oprzyrządowanie jest obowiązkowe. Przykładem jest edebug. Ponadto istnieją funkcje, w których interactive-specyfikacja jest znacznie większa niż treść funkcji. W takich przypadkach przepisanie interactivespecyfikacji może być dość nużące. Pytanie i odpowiedź dotyczą wymaganych zasad.
Tobias
1
Osobiście uważam tę odpowiedź za dość pouczającą, nie tylko w odniesieniu do zakresu pytania, ale także dlatego, że pokazuje ona naturalne zastosowanie makr oraz sposób przejścia od defunu do makra. Dzięki!
gsl,
11

Musiałem rozwiązać bardzo podobny problem nadvice.el, więc oto rozwiązanie (które wykorzystuje część kodu z nadvice.el):

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

W porównaniu z innymi opublikowanymi dotychczas rozwiązaniami, to ma tę zaletę, że działa poprawnie, jeśli wrappeezostanie ponownie zdefiniowane przy użyciu innej interaktywnej specyfikacji (tj. Nie będzie nadal używać starej specyfikacji).

Oczywiście, jeśli chcesz, aby opakowanie było naprawdę przezroczyste, możesz to zrobić po prostu:

(defalias 'wrapper #'wrappee)
Stefan
źródło
Jest to jedyna odpowiedź, która pozwala zdefiniować opakowanie, które znajdzie to, co otacza w czasie wykonywania. Na przykład chcę dodać skrót, który wykonuje akcję zdefiniowaną przez jakieś polecenie sprawdzane w czasie wykonywania. Za pomocąadvice-eval-interactive-spec zgodnie z sugestią tutaj, mogę zbudować interaktywną specyfikację, która odpowiada temu dynamicznemu opakowaniu.
Igor Bukanov,
Czy to możliwe, aby called-interactively-pzwrot twwrappee ? Jest, funcall-interactivelyale nie maapply-interactively
clemera
1
@compunaut: Oczywiście możesz to zrobić, (apply #'funcall-interactively #'wrappee args)jeśli chcesz. Ale powinieneś to zrobić tylko wtedy, gdy funkcja jest wywoływana interaktywnie, więc coś takiego (apply (if (called-interactively-p 'any) #'funcall-interactively #'funcall) #'wrappee args).
Stefan
Ha, dzięki! Jakoś nie mogłem myśleć poza moim pudełkiem.
clemera
1

edit: Odpowiedź Tobiasza jest ładniejsza, ponieważ uzyskuje precyzyjną interaktywną formę i dokumentację zawiniętej funkcji.


Łącząc odpowiedzi Aarona Harrisa i kjo, możesz użyć czegoś takiego:

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

Stosowanie:

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

Opakowanie połączenia z jednym z:

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

M-x wrapper-func

phils
źródło