Como manipular a lista de argumentos no nadvice.el?

12

Na sequência de uma resposta a outra pergunta sobre o novo sistema de aconselhamento :

No estilo antigo advice.el, era possível manipular membros individuais da lista de argumentos de uma função aconselhada, sem fazer nenhuma afirmação sobre os membros não tão manipulados. Por exemplo, o seguinte conselho:

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

permite o fornecimento (opcional) de um argumento de nome de buffer para uma ansi-termchamada, enquanto ansi-termainda obtém seu primeiro argumento solicitando de acordo com sua própria forma interativa.

(Para referência posterior, ansi-terma assinatura é (PROGRAM &optional BUFFER-NAME)e seu formulário interativo solicita PROGRAM com vários padrões possíveis, mas não faz nada em relação ao BUFFER-NAME.)

Não sei se isso é possível ou não nadvice.el. Se for, não tenho certeza de como isso pode ser feito. Encontrei algumas maneiras de substituir a lista de argumentos de uma função recomendada.

Por exemplo, de * info * (elisp) combinadores de conselhos :

`: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)))

Outros combinadores fornecem recursos semelhantes, e a linha comum entre eles é que, embora a lista de argumentos de uma função possa ser substituída, truncada, estendida etc., não há maneira aparente para o aconselhamento de funções modificar o argumento em uma determinada posição na lista sem afirmando qualquer coisa sobre o resto .

No caso em discussão, parece impossível para o autor do conselho passar ansi-termapenas um nome de buffer, porque não é possível construir uma lista que tenha um valor na posição 1, mas nada, nem mesmo nilna posição 0. No caso geral, parece impossível para o autor do conselho modificar arbitrariamente argumentos além da posição 0.

Isso parece lamentável, pois, para produzir um efeito semelhante, é necessário copiar e colar o código: especificamente, ou eu posso copiar ansi-termo formulário interativo e estendê-lo ao meu gosto, ou posso copiar ansi-termcompletamente e estendê-lo da mesma forma. Nos dois casos, agora devo redefinir parte da distribuição Emacs Lisp no meu arquivo init, o que me parece indesejável em termos de durabilidade e estética.

Minha pergunta, então, é: esse tipo de lista de argumentos pode ser confundido nadvice.el? Se sim, como?

Aaron Miller
fonte
3
Por que você não define seu próprio comando interativo além do termo ansi? Eu acho que é a solução preferível aqui.
precisa saber é
1
É claro que não há nada que me impeça de fazer isso, mas seria necessário substituir a maior parte da memória muscular de uma década, o que eu gostaria de evitar se puder.
Aaron Miller

Respostas:

5

Isso parece lamentável, pois, para produzir um efeito semelhante, é necessário copiar e colar o código: [...] eu posso copiar ansi-termo formulário interativo

Pelo contrário, acho que seria uma boa ideia copiar e colar a forma interativa da função recomendada, mesmo que você não precise fazer isso aqui.

Eu li sua pergunta de cima para baixo. Quando cheguei ao bloco de código, imaginei que seu conselho provavelmente estivesse mudando o nome do buffer. Mas eu não sabia até que você mais tarde forneceu a assinatura como um comentário.

No caso em discussão, parece impossível para o autor do conselho passar ansi-termapenas um nome de buffer, porque não é possível construir uma lista que tenha um valor na posição 1, mas nada, nem mesmo nil, na posição 0.

De fato, nada é menos nada que nada. :-) Mas isso não é relevante aqui.

Como você pode ver na documentação que você citou, o valor retornado pelo aviso é usado como argumento para a função recomendada. O valor de retorno deve ser uma lista de todos os argumentos, não apenas os que foram alterados.

Ficando o mais próximo possível do conselho antigo, é isso que você precisa fazer usando 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)

Mas eu recomendo que você defina o conselho como este:

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

Essa variante, na verdade, é auto-explicativa.

tarso
fonte
Para a 1ª variante, você precisa estender a argslista em caso de uma chamada como (ansi-term "foo"), ou isso (setf (nth 1 args)...geraria um erro.
npostavs
Sim, você está certo. Outro motivo para usar a segunda variante - a primeira possui um bug ;-) Vamos, para fins de demonstração, assumir que isso buffer-nameé obrigatório.
tarsius
"Pelo contrário, acho que seria uma boa ideia copiar e colar a forma interativa da função recomendada" - por que? O código de copiar e colar é uma má ideia em praticamente todos os outros casos; por que não aqui?
Aaron Miller
Na verdade, eu não acho que "copiar e colar" é o termo certo neste caso, eu apenas o usei porque você fez. Mas mesmo que fosse apropriado usar esse termo aqui, "não copiar e colar" é apenas uma regra heurística e não absoluta. Outras heurísticas, que eu acho que se aplicam aqui, são "dê nomes significativos a variáveis ​​e argumentos" e "quando você tiver uma escolha entre complicar algo ou ser detalhado, vá com detalhado".
tarsius
1
Hum, na verdade, isso ainda está quebrado, o :filter-argsconselho obtém um único argumento, que é uma lista de argumentos para a função recomendada, portanto a 1ª variante deve cair &reste a 2ª variante precisaria usar algum tipo de construção de desestruturação para obter nomes legais.
npostavs
3

Aqui está como eu faria isso:

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

enquanto fui eu quem apresentou :filter-args, pessoalmente acho raramente conveniente.

Stefan
fonte