Як написати прозору обгортку функції «прохідна»?


10

Що я маю на увазі під "прозорою" обгорткою функції пропуску "- це функція, назвемо її wrapper, яка повертає результат від передачі всіх його аргументів на якусь іншу функцію, назвемо її wrappee.

Як це робиться в Emacs Lisp?

NB: Ідеальна wrapperфункція є агностичною щодо wrappeeпідпису функції; тобто він нічого не знає про кількість, позиції, імена тощо wrappeeаргументів 's; він просто передає всі свої аргументи 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 до функції з кількома аргументами . Мені не вдається пояснити, чому, оскільки два питання мені так очевидно виглядають по-різному. Це як би попросити "пояснити, чому яблуко не слід вважати рівнозначним апельсину". Просте запитання настільки божевільне, що можна сумніватися, що коли-небудь можна було б відповісти, що задовольнить людину, яка його запитує.


Чи розглядали ви, як скористатися порадою / надвіркою?
wasamasa

@wasamasa: ні, і, крім того, я не бачу, як поради / надвикористання стосуватимуться цього питання. У будь-якому випадку, я вважаю, що цей adviceматеріал є досить проблематичним, і я вважаю, що я не хочу його залишати. Насправді, мотивація цього питання намагалася знайти рішення інакше нерозв'язної проблеми, яку я маю з функцією, яку
дозволено

1
@wasamasa: порада представляє ту саму проблему. Ви можете сказати, що робити з будь-яким із аргументів, але щоб зробити його інтерактивним, потрібно вказати, як аргументи надаватимуться. IOW, вам потрібно надати interactiveспецифікацію.
Дрю

1
Що я мав на увазі, це порадити оригінальній інтерактивній функції робити все, що ви хочете, щоб відбутися до і після нього, таким чином вам не потрібно турбуватися про інтерактивну специфікацію.
wasamasa

2
@wasamasa: Так, але це інакше. Порада завжди стосується певної функції, будь то інтерактивна чи ні. А якщо це команда, то проблем немає - її інтерактивна поведінка успадковується за командою, що рекомендується (якщо тільки порада не визначає інтерактивну поведінку). Це питання стосується довільної функції / команди, а не конкретної.
Дрю

Відповіді:


11

Звичайно, можливо включення interactiveспецифікації. Ми маємо справу тут із Еліспом ! (Lisp - це мова, на якій найважливіші конструкції - це списки. Форми, що викликаються - це лише списки. Отже, ви можете їх сконструювати за своїм смаком.)

Застосування: Ви хочете додати деякі функції деяким функціям автоматично. Розширеним функціям слід давати нові імена, щоб defadviceце не було застосовано.

Спочатку версія, яка цілком відповідає вашим цілям. Ми встановлюємо функціональну комірку ( 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. У більшості випадків більш розумним варіантом є розібратися з цим і записати функцію, виконуючи обгортання вручну (будь то застосувати або переписавши частини інтерактивної специфікації.
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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.