¿Cómo escribir un contenedor de funciones transparente de "paso a través"?

10

Lo que quiero decir con un "contenedor transparente de funciones de 'transferencia' 'es una función, llamémosla wrapper, que devuelve el resultado de pasar todos sus argumentos a alguna otra función, llamémosla wrappee.

¿Cómo se hace esto en Emacs Lisp?

Nota: la wrapperfunción ideal es independiente de la wrappeefirma de la función; es decir, no sabe nada del número, las posiciones, los nombres, etc. de wrappeelos argumentos de '; simplemente pasa todos sus argumentos wrappee, como si wrappeehubiera sido el llamado originalmente. (Sin embargo, no es necesario meterse con la pila de llamadas para reemplazar la llamada por wrapperuna llamada a wrappee).

Publiqué una respuesta parcial a mi pregunta:

(defun wrapper (&rest args) (apply 'wrappee args))

Esto funciona solo cuando nowrappee es interactivo. Aparentemente, la forma en que las funciones interactivas obtienen sus argumentos representa un "canal" diferente del que cubre el encantamiento. Lo que todavía necesito, por lo tanto, es una contraparte igualmente de diagnóstico de la firma para el caso en el que es una función interactiva .(&rest args)wrappee(&rest args)wrappee

(Esta pregunta fue motivada por un problema descrito en esta pregunta anterior ).


En caso de que se necesite una mayor aclaración de lo que estoy pidiendo, a continuación hay un par de ejemplos que muestran los equivalentes de Python y JavaScript de lo que busco.

En Python, se muestran a continuación un par de formas estándar para implementar dicho contenedor:

def wrapper(*args, **kwargs):
    return wrappee(*args, **kwargs)

# or

wrapper = lambda *args, **kwargs: wrappee(*args, **kwargs)

(Aquí *argssignifica "todos los argumentos posicionales" y **kwargssignifica "todos los argumentos de palabras clave").

El equivalente de JavaScript sería algo como esto:

function wrapper () { return wrappee.apply(this, arguments); }

// or

wrapper = function () { return wrappee.apply(this, arguments); }

Para el registro, no estoy de acuerdo con que esta pregunta sea un duplicado de Cómo aplicar mapcar a una función con múltiples argumentos . No puedo explicar por qué, ya que las dos preguntas me parecen muy diferentes. Es como preguntar "explicar por qué una manzana no debe considerarse equivalente a una naranja". La mera pregunta es tan loca que uno duda que alguna vez pueda llegar a una respuesta que satisfaga a la persona que la pregunta.

kjo
fuente
¿Consideró usar consejos / nadvice?
wasamasa
@wasamasa: no, y, además, no veo cómo se aplicarían los consejos / consejos a esta pregunta. En cualquier caso, considero que las advicecosas son lo suficientemente problemáticas como para preferir evitarlas. De hecho, la motivación para esta pregunta estaba tratando de encontrar una solución a un problema que es intratable que tengo con una función aconsejó ...
KJo
1
@wasamasa: el asesoramiento presenta el mismo problema. Puede decirle qué hacer con cualquiera de los argumentos, pero para que sea interactivo necesita especificar cómo se proporcionarán los argumentos. IOW, debe proporcionar una interactiveespecificación.
Dibujó el
1
Lo que sí quise decir es aconsejar a la función interactiva original que haga lo que quiera que suceda antes y después, de esa manera no debería preocuparse por las especificaciones interactivas.
wasamasa
2
@wasamasa: Sí, pero eso es diferente. El consejo es siempre para una función particular , ya sea interactiva o no. Y si se trata de un comando, no hay problema: su comportamiento interactivo se hereda para el comando recomendado (a menos que el consejo redefina el comportamiento interactivo). Esta pregunta es sobre una función / comando arbitrario , no uno particular.
Dibujó el

Respuestas:

11

Por supuesto, es posible incluyendo la interactiveespecificación. ¡Estamos tratando aquí con elisp ! (Lisp es el lenguaje donde las construcciones más importantes son las listas. Los formularios invocables son solo listas. Para que pueda construirlos después de su gusto).

Aplicación: desea agregar alguna funcionalidad a algunas funciones de forma automática. Las funciones extendidas deben recibir nuevos nombres para que defadviceno sea aplicable.

Primero, una versión que se ajuste exactamente a su propósito. Establecemos la celda de función ( fset) del símbolo wrappercon toda la información requerida wrappeey agregamos nuestras cosas adicionales.

Funciona para ambas wrappeedefiniciones. La primera versión de wrappeees interactiva y la segunda no.

(defun wrappee (num str)
  "Nontrivial wrappee."
  (interactive "nNumber:\nsString:")
  (message "The number is %d.\nThe string is \"%s\"." num str))

(defun wrappee (num str)
  "Noninteractive wrappee."
  (message "The number is %d.\nThe string is \"%s\"." num str))

(fset 'wrapper (list 'lambda
             '(&rest args)
             (concat (documentation 'wrappee t) "\n Wrapper does something more.")
             (interactive-form 'wrappee)
             '(prog1 (apply 'wrappee args)
            (message "Wrapper does something more."))))

Pero es más conveniente definir una macro que construya las funciones extendidas. Con esto, incluso podemos especificar los nombres de las funciones después. (Bueno para una versión automatizada).

Después de ejecutar el siguiente código, puede llamar de forma wrapper-interactiveinteractiva y wrapper-non-interactiveno interactiva.

(defmacro make-wrapper (wrappee wrapper)
  "Create a WRAPPER (a symbol) for WRAPPEE (also a symbol)."
  (let ((arglist (make-symbol "arglist")))
  `(defun ,wrapper (&rest ,arglist)
     ,(concat (documentation wrappee) "\n But I do something more.")
     ,(interactive-form wrappee)
     (prog1 (apply (quote ,wrappee) ,arglist)
       (message "Wrapper %S does something more." (quote ,wrapper))))))

(defun wrappee-interactive (num str)
  "That is the doc string of wrappee-interactive."
  (interactive "nNumber:\nsString:")
  (message "The number is %d.\nThe string is \"%s\"." num str))

(defun wrappee-non-interactive (format &rest arglist)
  "That is the doc string of wrappee-non-interactive."
  (apply 'message format arglist))

(make-wrapper wrappee-interactive wrapper-interactive)
(make-wrapper wrappee-non-interactive wrapper-non-interactive)
;; test of the non-interactive part:
(wrapper-non-interactive "Number: %d, String: %s" 1 "test")

Tenga en cuenta que, hasta ahora, todavía no había encontrado una manera de transferir los formularios de declaración, pero eso también debería ser posible.

Tobias
fuente
2
Hm, alguien rechazó esta respuesta. Realmente no me importa el puntaje, pero lo que me importa es la razón para rechazar la respuesta. Si vota abajo, ¡también deje un comentario! Esto me daría la oportunidad de mejorar la respuesta.
Tobias
No estoy seguro, pero esto hará que cualquiera que lea el código de un paquete que lo usa se vuelva WTF. En la mayoría de los casos, la opción más sensata es lidiar con él y escribir una función haciendo el ajuste manualmente (ya sea con apply o reescribiendo partes de la especificación interactiva.
Wasamasa
2
@wasamasa Estoy parcialmente de acuerdo. Sin embargo, hay casos en que la instrumentación automática es obligatoria. Un ejemplo es edebug. Además, hay funciones en las que la interactiveespecificación-es considerablemente más grande que el cuerpo de la función. En tales casos, la reescritura de la interactiveespecificación puede ser bastante tediosa. La pregunta y la respuesta abordan los principios requeridos.
Tobias
1
Personalmente, encuentro esta respuesta bastante instructiva, no solo en lo que respecta al alcance de la pregunta, sino también porque muestra una aplicación natural de las macros y cómo se pasa de defun a macro. ¡Gracias!
gsl
11

Tuve que resolver un problema muy similar nadvice.el, así que aquí hay una solución (que usa parte del código de nadvice.el):

(defun wrapper (&rest args)
  (interactive (advice-eval-interactive-spec
                (cadr (interactive-form #'wrappee))))
  (apply #'wrappee args))

En comparación con las otras soluciones publicadas hasta ahora, esta tiene la ventaja de funcionar correctamente si wrappeese redefine con una especificación interactiva diferente (es decir, no seguirá usando la especificación anterior).

Por supuesto, si desea que su envoltorio sea verdaderamente transparente, puede hacerlo de manera más simple:

(defalias 'wrapper #'wrappee)
Stefan
fuente
Esta es la única respuesta que permite definir un contenedor que encuentra lo que envuelve en tiempo de ejecución. Por ejemplo, quiero agregar un acceso directo que realice una acción definida por algún comando que se busca en tiempo de ejecución. Utilizando advice-eval-interactive-speccomo se sugiere aquí, puedo construir la especificación interactiva que corresponde a ese contenedor dinámico.
Igor Bukanov
¿Es posible hacer called-interactively-pvolver ten wrappee? Hay funcall-interactivelypero noapply-interactively
clemera
1
@compunaut: Por supuesto, puedes hacerlo (apply #'funcall-interactively #'wrappee args)si quieres. Pero solo debe hacerlo si la función se llama de forma interactiva, por ejemplo (apply (if (called-interactively-p 'any) #'funcall-interactively #'funcall) #'wrappee args).
Stefan
Ja, gracias! De alguna manera no podía pensar fuera de mi caja.
clemera
1

editar: la respuesta de Tobias es mejor que esta, ya que obtiene la forma interactiva precisa y la cadena de documentos de la función envuelta.


Combinando las respuestas de Aaron Harris y kjo, podría usar algo como:

(defmacro my-make-wrapper (fn &optional name)
  "Return a wrapper function for FN defined as symbol NAME."
  `(defalias ',(or (eval name)
                   (intern (concat "my-" (symbol-name (eval fn)) "-wrapper")))
     (lambda (&rest args)
       ,(format "Generic wrapper for %s."
                (if (symbolp (eval fn))
                    (concat "`" (symbol-name (eval fn)) "'")
                  fn))
       (interactive)
       (if (called-interactively-p 'any)
           (call-interactively ,fn)
         (apply ,fn args)))))

Uso:

(my-make-wrapper 'find-file 'wrapper-func)

Call wrapper con cualquiera de:

(wrapper-func "~/.emacs.d/init.el")

M-x wrapper-func

phils
fuente