¿Puede una función o macro especificar advertencias de compilador de bytes?


15

Estoy escribiendo una función que, en principio, toma una cantidad arbitraria de argumentos. En la práctica, sin embargo, solo se debe pasar un número par de argumentos, y de lo contrario producirá resultados indeseables.

Aquí hay un ejemplo ficticio para el contexto:

(defun my-caller (&rest args)
  (while args
    (call-other-function (pop args) (pop args))))

Cuando un archivo elisp se compila en bytes, el compilador de bytes emite una advertencia cuando ve que se invoca una función con el número incorrecto de argumentos. Obviamente, eso nunca va a suceder my-caller, ya que está definido para tomar cualquier número.

Aún así, tal vez haya una propiedad de símbolo que pueda establecer, o un (declare)formulario que pueda agregar a su definición. Algo para notificar al usuario que esta función solo debe recibir un número par de argumentos.

  1. ¿Hay alguna manera de informar al compilador de bytes de esta restricción?
  2. Si no, ¿es posible con una macro, en lugar de una función?

"... cuando ve que se invoca una función con el número incorrecto de argumentos"?
itsjeyd

Respuestas:


13

EDITAR : Una mejor manera de hacer esto en Emacs reciente es definiendo una macro compiladora para verificar el número de argumentos. Mi respuesta original utilizando una macro normal se conserva a continuación, pero un compilador-macro es superior porque no impide pasar a la función funcallo applyen tiempo de ejecución.

En versiones recientes de Emacs, puede hacer esto definiendo una macro compiladora para su función que verifica el número de argumentos y produce una advertencia (o incluso un error) si no coincide. La única sutileza es que la macro compiladora debe devolver el formulario de llamada de función original sin cambios para evaluación o compilación. Esto se hace usando un &wholeargumento y devolviendo su valor. Esto podría lograrse así:

(require 'cl-lib)

(defun my-caller (&rest args)
  (while args
    (message "%S %S" (pop args) (pop args))))

(define-compiler-macro my-caller (&whole form &rest args)
  (when (not (cl-evenp (length args)))
    (byte-compile-warn "`my-caller' requires an even number of arguments"))
  form)

(my-caller 1 2 3 4)
(my-caller 1 2)
(funcall #'my-caller 1 2 3 4)       ; ok
(apply #'my-caller '(1 2))          ; also ok
(my-caller 1)                       ; produces a warning
(funcall #'my-caller 1 2 3)         ; no warning!
(apply #'my-caller '(1 2 3))        ; also no warning

Tenga en cuenta que funcally applyahora se puede usar, pero omiten la comprobación de argumentos por parte de la macro compiladora. A pesar de su nombre, las macros del compilador también parecen ser ampliado en el curso de 'interpretado' evaluación a través de C-xC-e, M-xeval-buffer, por lo que obtendrá errores en la evaluación, así como en la elaboración de este ejemplo.


La respuesta original sigue:

Así es como podría implementar la sugerencia de Jordon de "usar una macro que proporcionará advertencias en el momento de la expansión". Resulta muy fácil:

(require 'cl-lib)

(defmacro my-caller (&rest args)
  (if (cl-evenp (length args))
      `(my-caller--function ,@args)
    (error "Function `my-caller' requires an even number of arguments")))

(defun my-caller--function (&rest args)
  ;; function body goes here
  args)

(my-caller 1 2 3 4)
(my-caller 1 2 3)

Intentar compilar lo anterior en un archivo fallará (no .elcse produce ningún archivo), con un bonito mensaje de error en el que se puede hacer clic en el registro de compilación, que indica:

test.el:14:1:Error: `my-caller' requires an even number of arguments

También puede reemplazar (error …)con (byte-compile-warn …)para generar una advertencia en lugar de un error, permitiendo que continúe la compilación. (Gracias a Jordon por señalar esto en los comentarios).

Como las macros se expanden en el momento de la compilación, no hay penalización en tiempo de ejecución asociada con esta verificación. Por supuesto, no puede evitar que otras personas llamen my-caller--functiondirectamente, pero al menos puede anunciarlo como una función "privada" utilizando la convención de doble guión.

Una desventaja notable de usar una macro para este propósito es que my-callerya no es una función de primera clase: no puede pasarla funcallo applyen tiempo de ejecución (o al menos no hará lo que espera). En ese sentido, esta solución no es tan buena como poder simplemente declarar una advertencia del compilador para una función real. Por supuesto, el uso applyharía imposible verificar el número de argumentos que se pasan a la función en tiempo de compilación de todos modos, por lo que tal vez sea una compensación aceptable.


2
Las advertencias de compilación se crean conbyte-compile-warn
Jordon Biondo

Ahora me pregunto si esto podría lograrse de manera más efectiva definiendo una macro compiladora para la función. Esto eliminaría la desventaja de no ser a applyo funcallla envoltura macro. Lo probaré y editaré mi respuesta si funciona.
Jon O.

11

Sí, puede usar byte-defop-compilerpara especificar realmente una función que compila su función, byte-defop-compilertiene algunas sutilezas incorporadas para ayudarlo a especificar que sus funciones deben generar advertencias basadas en tener una serie de argumentos.

Documentación

Agregue un formulario de compilación para FUNCTION. Si la función es un símbolo, entonces la variable "byte-SYMBOL" debe nombrar el código de operación que se utilizará. Si function es una lista, el primer elemento es la función y el segundo elemento es el bytecode-symbol. El segundo elemento puede ser nulo, lo que significa que no hay código de operación. COMPILE-HANDLER es la función que se utiliza para compilar este byte-op, o pueden ser las abreviaturas 0, 1, 2, 3, 0-1 o 1-2. Si es nulo, el controlador es "byte-compile-SYMBOL".


Uso

En su caso específico, podría usar una de las abreviaturas para definir que su función debería recibir dos argumentos.

(byte-defop-compiler my-caller 2)

Ahora su función dará advertencias cuando se compila con cualquier cosa menos 2 argumentos.

Si desea dar advertencias más específicas y escribir sus propias funciones de compilación. Mire byte-compile-one-argy otras funciones similares en bytecomp.el como referencia.

Tenga en cuenta que no solo está especificando alguna función para manejar la validación, sino que también está compilando. Nuevamente, compilar funciones en bytecomp.el le proporcionará una buena referencia.


Rutas más seguras

Esto no es algo que haya visto documentado o discutido en línea, pero en general diría que esta es una ruta desaconsejada. La ruta correcta (IMO) sería escribir sus defuns con firmas descriptivas o usar una macro que proporcionará advertencias en el momento de la expansión, verificando la longitud de sus argumentos y usando byte-compile-warno errorpara mostrar errores. También puede beneficiarle hacer uso de la eval-when-compileverificación de errores.

También necesitará que su función se defina antes de que alguna vez se use, y la llamada a byte-defop-compilertendrá que ser antes de que el compilador llegue a las llamadas reales de su función.

Una vez más, parece no estar realmente documentado o aconsejado por lo que he visto (podría estar equivocado), pero imagino que el patrón a seguir aquí sería especificar algún tipo de archivo de encabezado para su paquete que esté lleno de un montón de defuns vacíos. y llama a byte-defop-compiler. Básicamente, este sería un paquete que se requiere antes de que se pueda compilar su paquete real.

Opinión: Basado en lo que sé, que no es mucho, porque acabo de enterarme de todo esto, te aconsejaría que nunca hagas nada de esto. nunca


1
Relacionado: Hay bytecomp-simplify que enseña advertencias adicionales al compilador de bytes.
Wilfred Hughes
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.