如何在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完全复制并同样地扩展它。无论哪种情况,我现在都必须在init文件中重新定义Emacs Lisp发行版的一部分,这在持久性和美观性方面都使我感到不满意。

那么,我的问题是:可以使用这种参数列表处理nadvice.el吗?如果是这样,怎么办?


3
为什么不在ansi-term上定义自己的交互式命令?我认为这是最好的解决方案。
lunaryorn

1
当然,没有什么可以阻止我这样做的,但是这将有必要替换十年来最好的肌肉记忆,如果可以的话,我想避免。
亚伦·米勒

Answers:


5

为了产生类似的效果,这似乎是不幸的,必须复制粘贴代码:[...]我可以复制ansi-term的交互形式

相反,我认为复制并粘贴建议功能的交互形式是一个好主意,即使您实际上不必在此处这样做也是如此。

我从上到下阅读了您的问题。当我到达代码块时,我猜想您的建议可能是在更改缓冲区名称。但是,直到您以后提供签名作为评论,我才知道

在讨论的情况下,建议作者似乎无法ansi-term仅传递缓冲区名称,因为不可能构造一个位置1而不是nil0的列表。

确实,没有什么比没有什么更重要。:-)但这与这里几乎无关。

如您所引用的文档中所见,建议所返回的值用作建议函数的参数。返回值必须是所有参数的列表,而不仅仅是已更改的参数。

尽可能与旧建议保持紧密联系,这是您必须使用的方法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)...会引发错误。
npostavs

你是对的。使用第二个变体的另一个原因-第一个变体有一个错误;-)出于演示目的,我们假设这buffer-name是强制性的。
tarsius

“相反,我认为复制粘贴建议功能的交互形式是个好主意”-为什么呢?在几乎所有其他情况下,粘贴复制代码都是一个坏主意;为什么不在这里?
亚伦·米勒

实际上,在这种情况下,我认为“复制粘贴”不是正确的术语,因为您这样做,我只是使用了它。但是,即使在此处使用该术语是适当的,“不要复制粘贴”也只是一种启发式方法,而不是绝对的规则。我认为这里确实适用的其他启发式方法是“为变量和参数赋予有意义的名称”和“当您在复杂化或变得冗长之间做出选择时,选择冗长”。
tarsius

1
嗯,其实,这仍然是断开的,:filter-args建议得到一个单一的参数,它是参数传递给该通知功能的列表,以便第一变种应该放弃&rest和第二变种将不得不使用某种形式的解构结构来获得不错的名字。
npostavs 2016年

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它的人时,我个人觉得它很少方便。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.