Как написать прозрачную «сквозную» функцию-обертку?

10

То, что я подразумеваю под «прозрачной« проходной »функцией-оберткой», - это функция, давайте назовем ее wrapper, которая возвращает результат передачи всех своих аргументов какой-то другой функции, давайте назовем ее wrappee.

Как это делается в Emacs Lisp?

NB: Идеальная wrapperфункция агностик о в wrappeeподписи функции в; то есть он ничего не знает о количестве, позициях, именах и т. д. wrappeeаргументов России; он просто передает все свои аргументы wrappee, как если бы wrappeeон был первоначально вызван. (Однако нет необходимости связываться со стеком вызовов, чтобы заменить вызов на wrapperвызов wrappee.)

Я разместил частичный ответ на мой вопрос:

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

Это работает только тогда, когда неwrappee является интерактивным. Очевидно, способ, которым интерактивные функции получают свои аргументы, представляет собой «канал», отличный от того, что описывается в заклинании. Поэтому мне по-прежнему нужен равноагностический аналог сигнатуры для случая, когда есть интерактивная функция.(&rest args)wrappee(&rest args)wrappee

(Этот вопрос был мотивирован проблемой, описанной в этом предыдущем вопросе .)


В случае, если необходимо дальнейшее разъяснение того, что я запрашиваю, ниже приведены несколько примеров, показывающих эквиваленты Python и JavaScript того, что мне нужно.

В Python пара стандартных способов реализации такой оболочки показана ниже:

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

# or

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

(Здесь *argsозначает «все позиционные аргументы» и **kwargs«все ключевые аргументы».)

Эквивалент JavaScript будет примерно таким:

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

// or

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

Для справки , я не согласен, что этот вопрос является дубликатом Как применить mapcar к функции с несколькими аргументами . Я затрудняюсь объяснить, почему, поскольку эти два вопроса кажутся мне совершенно разными. Это все равно, что спросить: «Объясните, почему яблоко не следует считать эквивалентом апельсина». Простой вопрос настолько сумасшедший, что можно сомневаться в том, что кто-то может придумать ответ, который удовлетворил бы того, кто его спрашивал.

KJo
источник
Рассматривали ли вы использовать совет / nadvice?
Васамаса
@wasamasa: нет, и, более того, я не понимаю, как совет / nadvice применим к этому вопросу. В любом случае, я нахожу adviceматериал достаточно проблематичным, поэтому предпочел бы избежать этого. На самом деле, мотивом для этого вопроса была попытка найти решение неразрешимой проблемы, которую я имею, с помощью рекомендованной функции ...
kjo
1
@wasamasa: Совет представляет ту же проблему. Вы можете указать, что делать с любым из аргументов, но чтобы сделать его интерактивным, вам нужно указать, как должны быть предоставлены аргументы. IOW, вам нужно предоставить interactiveспецификации.
Дрю
1
То, что я имел в виду, - это советовать оригинальной интерактивной функции делать все, что вы хотите, до и после нее, так что вам не нужно беспокоиться об интерактивной спецификации.
Васамаса
2
@wasamasa: Да, но это другое. Совет всегда для конкретной функции, будь то интерактивная или нет. И если это команда, то проблем нет - ее интерактивное поведение наследуется для рекомендованной команды (если только совет не переопределяет интерактивное поведение). Этот вопрос касается произвольной функции / команды, а не конкретной.
Дрю

Ответы:

11

Конечно, это возможно, включая interactiveспецификацию. Мы имеем дело здесь с elisp ! (Лисп - это язык, где наиболее важными конструкциями являются списки. Вызываемые формы - это просто списки. Поэтому вы можете создавать их по своему усмотрению.)

Приложение: Вы хотите добавить некоторые функции к некоторым функциям автоматически. Расширенным функциям должны быть присвоены новые имена, чтобы они defadviceне применялись.

Во-первых, версия, которая в точности соответствует вашим целям. Мы устанавливаем функцию cell ( fset) символа wrapperсо всей необходимой информацией wrappeeи добавляем наш дополнительный материал.

Это работает для обоих wrappeeопределений. Первая версия wrappeeявляется интерактивной, вторая - нет.

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

Но удобнее определить макрос, который создает расширенные функции. При этом мы можем даже указать имена функций впоследствии. (Хорошо для автоматической версии.)

После выполнения кода ниже вы можете звонить в wrapper-interactiveинтерактивном и wrapper-non-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")

Обратите внимание, до сих пор я не нашел способ передачи форм объявлений, но это также должно быть возможно.

Тобиас
источник
2
Хм, кто-то проголосовал против этого ответа. Меня не очень интересует оценка, но то, что меня волнует, является причиной для повторного голосования за ответ. Если вы проголосовали против, пожалуйста, оставьте комментарий! Это дало бы мне возможность улучшить ответ.
Тобиас
Я не знаю наверняка, но это обязательно заставит любого, кто читает код пакета, использующего его, перейти в WTF. В большинстве случаев более разумный вариант - разобраться с этим и написать функцию, выполняющую перенос вручную (будь то с помощью apply или переписав части интерактивной спецификации.
wasamasa
2
@ wasamasa Я частично согласен. Тем не менее, есть случаи, когда автоматическое приборостроение является обязательным. Пример есть edebug. Кроме того, есть функции, где interactiveспецификация значительно больше, чем тело функции. В таких случаях переписывание interactiveспецификации может быть довольно утомительным. Вопрос и ответ касаются требуемых принципов.
Тобиас
1
Лично я нахожу этот ответ весьма поучительным не только с точки зрения объема вопроса, но и с точки зрения естественного применения макросов и того, как можно перейти от определения функций к макросам. Спасибо!
gsl
11

Мне пришлось решить очень похожую проблему nadvice.el, поэтому вот решение (которое использует некоторый код из nadvice.el):

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

По сравнению с другими решениями, опубликованными до сих пор, у этого есть преимущество правильной работы, если wrappeeего переопределить с другой интерактивной спецификацией (то есть он не будет продолжать использовать старую спецификацию).

Конечно, если вы хотите, чтобы ваша оболочка была действительно прозрачной, вы можете сделать это проще:

(defalias 'wrapper #'wrappee)
Стефан
источник
Это единственный ответ, который позволяет определить оболочку, которая находит, что она упаковывает во время выполнения. Например, я хочу добавить ярлык, который выполняет действие, определенное некоторой командой, которая ищется во время выполнения. Используя advice-eval-interactive-specпредложенное здесь, я могу создать интерактивную спецификацию, соответствующую этой динамической оболочке.
Игорь Буканов
Можно ли сделать called-interactively-pвозврат tв wrappee? Есть, funcall-interactivelyно нетapply-interactively
клемера
1
@compunaut: Конечно, вы можете сделать, (apply #'funcall-interactively #'wrappee args)если хотите. Но делать это нужно только в том случае, если функция вызывается интерактивно, так что-то вроде (apply (if (called-interactively-p 'any) #'funcall-interactively #'funcall) #'wrappee args).
Стефан
Ха, спасибо! Как-то не мог мыслить за пределами моей коробки.
Клемера
1

редактировать: ответ Тобиаса лучше, чем это, так как он получает точную интерактивную форму и строку документации обернутой функции.


Комбинируя ответы Аарона Харриса и Кджо, вы можете использовать что-то вроде:

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

Применение:

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

Вызовите упаковщик с одним из:

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

M-x wrapper-func

Phils
источник