Как манипулировать списком аргументов в 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), и ее интерактивная форма запрашивает PROGRAM с несколькими возможными значениями по умолчанию, но ничего не делает в отношении 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 в моем файле инициализации, что мне кажется нежелательным с точки зрения как долговечности, так и эстетики.

Мой вопрос, таким образом, заключается в следующем: можно ли сделать такого рода искажение списка аргументов nadvice.el? Если так, то как?

Аарон Миллер
источник
3
Почему бы вам не определить свою собственную интерактивную команду поверх термина? Я думаю, что это предпочтительное решение здесь.
lunaryorn
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)))))

Этот вариант на самом деле не требует пояснений.

Tarsius
источник
Для 1-го варианта вам необходимо расширить argsсписок в случае вызова, как (ansi-term "foo"), иначе, (setf (nth 1 args)...возникнет ошибка.
npostavs
Да, ты прав. Еще одна причина использовать второй вариант - в первом есть ошибка ;-) Давайте в демонстрационных целях предположим, что buffer-nameэто обязательно.
tarsius
«Напротив, я думаю, что было бы неплохо скопировать и вставить интерактивную форму рекомендуемой функции» - почему так? Копирование кода является плохой идеей практически во всех других случаях; почему не здесь?
Аарон Миллер
На самом деле я не думаю, что «копировать-вставить» является правильным термином в данном случае, я просто использовал его, потому что вы сделали. Но даже если здесь уместно использовать этот термин, то «не копировать-вставить» - это просто эвристика, а не абсолютное правило. Другие эвристики, которые, на мой взгляд , применимы здесь, «дают значимые имена переменным и аргументам» и «когда у вас есть выбор между усложнением чего-либо или многословием, переходите к многословному».
tarsius
1
Хм, на самом деле, это все еще не работает, :filter-argsadvice получает единственный аргумент, который представляет собой список аргументов для функции advised, поэтому 1-й вариант должен быть отброшен, &restа 2-й вариант должен будет использовать какую-то конструкцию деструктуризации для получения хороших имен.
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лично я нахожу это редко удобным.

Стефан
источник