Як маніпулювати списком аргументів у nadvice.el?


12

Виходячи з відповіді на інше запитання щодо нової системи консультацій :

У старому стилі advice.elможна було маніпулювати окремими членами аргументованого списку аргументів, не роблячи жодних тверджень щодо тих членів, які не так маніпулювали. Наприклад, наступна порада:

(defadvice ansi-term (around prompt-for-name last)
  (let ((name (read-from-minibuffer "Tag: ")))
    (and (not (string= name ""))
         (ad-set-arg 1 (concat "Term: " name)))
    ad-do-it))

дозволяє (необов'язково) надання аргументу буферного імені для ansi-termвиклику, при ansi-termцьому все одно отримувати свій перший аргумент шляхом запиту відповідно до власної інтерактивної форми.

(Для подальшого ознайомлення, ansi-termпідпис є (PROGRAM &optional BUFFER-NAME), і його інтерактивна форма вимагає ПРОГРАМИ з кількома можливими за замовчуванням, але нічого не стосується BUFFER-NAME.)

Я не впевнений, чи можливо це зробити nadvice.el? Якщо це так, я не впевнений, як це можна зробити. Я знайшов пару способів замінити список аргументів рекомендованої функції.

Наприклад, з * info * (elisp) поради комбінаторів :

`:filter-args'
 Call FUNCTION first and use the result (which should be a list) as
 the new arguments to pass to the old function.  More specifically,
 the composition of the two functions behaves like:
      (lambda (&rest r) (apply OLDFUN (funcall FUNCTION r)))

Інші комбінатори надають подібні можливості, і загальний потік серед них полягає в тому, що, хоча список аргументів функції може бути замінений, усічений, розширений та ін., Очевидний спосіб порадити функції змінити аргумент на заданій позиції у списку без видимого стверджуючи що-небудь про решту .

У обговорюваному випадку автору поради видається неможливим передати ansi-termлише ім’я буфера, оскільки неможливо побудувати список, який має значення в позиції 1, але нічого, навіть навіть nil, у позиції 0. У загальному випадку, Авторові поради видається неможливим довільно змінювати аргументи поза позицією 0.

Це здається прикро тим, що для отримання подібного ефекту необхідно скопіювати і вставити код: конкретно, або я можу скопіювати ansi-termінтерактивну форму і розширити її на свій смак, або я можу скопіювати ansi-termі розширити її аналогічно. У будь-якому випадку я тепер повинен переглядати частину дистрибутива Emacs Lisp у своєму файлі init, що вважає мене небажаним з точки зору довговічності та естетичності.

Моє запитання полягає в тому, чи можна з цим списком аргументів списувати аргументи nadvice.el? Якщо так, то як?


3
Чому б вам не визначити власну інтерактивну команду поверх ансі-терміну? Я думаю, що це найкраще рішення тут.
місячник

1
Звичайно, ніщо не заважає мені це робити, але це потребує заміни на більшу частину десятиліття вартості м'язової пам’яті, якої я хотів би уникнути, якщо зможу.
Аарон Міллер

Відповіді:


5

Це здається прикро тим, що для отримання подібного ефекту необхідно скопіювати і вставити код: [...] Я можу скопіювати ansi-termінтерактивну форму

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

Я читаю вам питання зверху вниз. Коли я дійшов до блоку коду, я здогадався, що ваша порада, ймовірно , змінює ім’я буфера. Але я не знав, поки ви пізніше не надали підпис як коментар.

У випадку, що обговорюється, автору порад видається неможливим передати ansi-termлише ім’я буфера, оскільки неможливо побудувати список, який має значення в позиції 1, але нічого, навіть навіть nil, у позиції 0.

Насправді ніщо не є меншим, ніж нічого. :-) Але це навряд чи актуально.

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

Дотримуючись якомога ближче до старої поради, це те, що вам доведеться зробити, використовуючи nadvice:

(defun ansi-term--tag-buffer (args)
  ;; As npostavs pointed out we also have to make sure the list is
  ;; two elements long.  Which makes this approach even more undesirable.
  (when (= (length args) 1)
    (setq args (nconc args (list nil))))
  (let ((name (read-from-minibuffer "Tag: ")))
    (and (not (string= name ""))
         (setf (nth 1 args) (concat "Term: " name))))
  args)

(advice-add 'ansi-term :filter-args 'ansi-term--tag-buffer)

Але я рекомендую вам визначити наступні поради:

(defun ansi-term--tag-buffer (program &optional buffer-name)
  (list program
        (let ((tag (read-from-minibuffer "Tag: ")))
          (if (string= tag "")
              buffer-name
            (concat "Term: " tag)))))

Цей варіант насправді є роз'яснювальним.


Для першого варіанту вам потрібно розширити argsсписок у випадку, як (ansi-term "foo"), наприклад , дзвінка , інакше виникне (setf (nth 1 args)...помилка.
nпостаs

Так, ви праві. Ще одна причина використовувати другий варіант - у першому є помилка ;-) Давайте для демонстраційних цілей просто припускаємо, що buffer-nameце обов’язково.
Тарсій

"Навпаки, я думаю, було б корисно скопіювати та вставити інтерактивну форму рекомендованої функції" - чому так? Копіювати-вставляти код - це погана ідея майже в кожному іншому випадку; чому б не тут?
Аарон Міллер

Насправді я не думаю, що "копіювати-вставити" - це правильний термін у цьому випадку, я просто використав це, тому що ви. Але навіть якщо тут було б доречно використовувати цей термін, то "не копіювати-вставляти" - це просто евристика, а не абсолютне правило. Інші евристики, які, на мою думку , застосовуються тут, - це "давати змінні назви змінним та аргументам" та "коли у вас є вибір між тим, що щось ускладнювати чи бути багатослівним, перейдіть до багатослівного".
Тарсій

1
Гм, насправді це все-таки порушено, :filter-argsпоради отримують єдиний аргумент, який є списком аргументів до рекомендованої функції, тому 1-й варіант повинен бути відхиленим, &restа другий варіант повинен використати якусь конструкцію деструктування, щоб отримати гарні імена.
npostavs

3

Ось як я це зробив:

(defun my-ansi-term-prompt-for-name (orig-fun program
                                     &optional buffer-name &rest args)
  (apply orig-fun program
         (or buffer-name
             (let ((name (read-string "Tag: ")))
               (and (> (length name) 0)
                    (concat "Term: " name))))
         args))
(advice-add 'ansi-term :around #'my-ansi-term-prompt-for-name)

в той час як я був тим, хто представив, :filter-argsмені особисто здається, що це рідко зручно.

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