¿Cómo manipular la lista de argumentos en nadvice.el?

12

Después de una respuesta a otra pregunta sobre el nuevo sistema de consejos :

En el estilo antiguo advice.el, era posible manipular miembros individuales de la lista de argumentos de una función recomendada, sin hacer ninguna afirmación con respecto a aquellos miembros no tan manipulados. Por ejemplo, el siguiente consejo:

(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 la provisión (opcional) de un argumento de nombre de búfer para una ansi-termllamada, mientras ansi-termque todavía obtendrá su primer argumento al solicitar de acuerdo con su propia forma interactiva.

(Para referencia posterior, ansi-termla firma de es (PROGRAM &optional BUFFER-NAME), y su forma interactiva solicita PROGRAMA con varios valores predeterminados posibles, pero no hace nada con respecto a BUFFER-NAME).

No estoy seguro de si esto es posible o no en nadvice.el. Si es así, no estoy seguro de cómo se puede hacer. He encontrado un par de formas de reemplazar la lista de argumentos de una función recomendada.

Por ejemplo, de * info * (elisp) Combinadores de consejos :

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

Otros combinadores proporcionan capacidades similares, y el hilo común entre ellos es que, si bien la lista de argumentos de una función puede ser reemplazada, truncada, extendida, etc., no hay una forma aparente para que el consejo de la función modifique el argumento en una posición dada en la lista sin afirmando cualquier cosa sobre el resto .

En el caso en discusión, parece imposible que el autor del consejo pase ansi-termsolo un nombre de búfer, porque no es posible construir una lista que tenga un valor en la posición 1 pero nada, ni siquiera nil, en la posición 0. En el caso general, Parece imposible para el autor del consejo modificar arbitrariamente los argumentos más allá de la posición 0.

Esto parece desafortunado porque, para producir un efecto similar, es necesario copiar y pegar el código: específicamente, o puedo copiar ansi-termel formulario interactivo y extenderlo a mi gusto, o puedo copiarlo por ansi-termcompleto y extenderlo de la misma manera. En cualquier caso, ahora debo redefinir parte de la distribución Emacs Lisp en mi archivo init, lo que me parece indeseable en términos de durabilidad y estética.

Mi pregunta, entonces, es: ¿se puede hacer este tipo de lista de argumentos nadvice.el? ¿Si es así, cómo?

Aaron Miller
fuente
3
¿Por qué no define su propio comando interactivo además de ansi-term? Creo que esa es la solución preferible aquí.
lunaryorn
1
Por supuesto, no hay nada que me impida hacer eso, pero necesitaría reemplazar la mayor parte de la memoria muscular de una década, lo que me gustaría evitar si pudiera.
Aaron Miller

Respuestas:

5

Esto parece desafortunado porque, para producir un efecto similar, es necesario copiar y pegar el código: [...] puedo copiar ansi-termel formulario interactivo

Por el contrario, creo que sería una buena idea copiar y pegar la forma interactiva de la función recomendada, aunque en realidad no tiene que hacerlo aquí.

Leí tu pregunta de arriba a abajo. Cuando llegué al bloque de código, supuse que su consejo probablemente está cambiando el nombre del búfer. Pero no lo supe hasta que luego proporcionaste la firma como comentario.

En el caso en discusión, parece imposible que el autor del consejo pase ansi-termsolo un nombre de búfer, porque no es posible construir una lista que tenga un valor en la posición 1 pero nada, ni siquiera nil, en la posición 0.

De hecho, nada es menos nada que nada. :-) Pero eso no es relevante aquí.

Como puede ver en la documentación que citó, el valor devuelto por el aviso se utiliza como argumento para la función recomendada. El valor de retorno debe ser una lista de todos los argumentos, no solo los que han cambiado.

Manteniéndose lo más cerca posible del viejo consejo, esto es lo que tendría que hacer 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)

Pero le recomiendo que defina los consejos de esta manera:

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

Esta variante en realidad se explica por sí misma.

tarsius
fuente
Para la primera variante, debe ampliar la argslista en caso de una llamada como (ansi-term "foo"), de lo contrario, (setf (nth 1 args)...generaría un error.
npostavs
Sí tienes razón. Otra razón para usar la segunda variante: la primera tiene un error ;-) Permite, con fines de demostración, asumir que buffer-namees obligatorio.
tarsius
"Por el contrario, creo que sería una buena idea copiar y pegar la forma interactiva de la función recomendada", ¿por qué? Copiar y pegar código es una mala idea en casi todos los demás casos; ¿por qué no aquí?
Aaron Miller
En realidad, no creo que "copiar-pegar" sea el término correcto en este caso, simplemente lo usé porque lo hiciste. Pero incluso si fuera apropiado usar ese término aquí, entonces "no copiar y pegar" es solo una regla heurística, no absoluta. Otras heurísticas, que creo que se aplican aquí, son "dar nombres significativos a las variables y argumentos" y "cuando tienes la opción de complicar algo o ser detallado, ve con detalle".
tarsius el
1
Um, en realidad, esto todavía está roto, el :filter-argsconsejo obtiene un único argumento que es una lista de argumentos para la función recomendada, por lo que la primera variante debería caer &resty la segunda variante tendría que usar algún tipo de construcción de desestructuración para obtener buenos nombres.
npostavs
3

Así es como lo haría:

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

mientras yo fui quien presentó :filter-args, personalmente encuentro que rara vez es conveniente.

Stefan
fuente