Compilación de bytes de un paquete de varios archivos: "no se sabe que la función esté definida"

7

Imagine que tengo los siguientes archivos en mi paquete (ridículo):

Archivo test1.el:

;;; test1.el ---                                   

;;; Code:

(defvar test-var1)

(defun test-fun1 (test)
  nil)

(require 'test2 "./test2.el)

(provide 'test1)
;;; test1.el ends here

Archivo test2.el:

;;; test2.el ---  

;;; Code:

(defun test-fun2 ()
  (let ((test test-var1))
    (test-fun1 test)))

(provide 'test2)
;;; test2.el ends here

Si entonces corro:

emacs -batch -f batch-byte-compile *.el

Obtengo el siguiente resultado:

Compiling .../test1.el...
Wrote .../test1.elc
Compiling .../test2.el...

In test-fun2:
test2.el:9:15:Warning: reference to free variable `test-var1'

In end of data:
test2.el:14:1:Warning: the function `test-fun1' is not known to be defined.
Wrote .../test2.elc

Entiendo por qué aparecen estas advertencias, y entiendo que son solo advertencias. Sin embargo, sería fácil omitir un error tipográfico en el nombre de una función al descartar todas las advertencias de este tipo.

De alguna manera pensé que agregar una (require 'test2)línea test2.eldebería solucionarlo. Sin embargo, en este caso obtengo:

Compiling .../test1.el...

In toplevel form:
test1.el:10:1:Error: Recursive `require' for feature `test2'
Compiling .../test2.el...

In toplevel form:
test2.el:5:1:Error: Recursive `require' for feature `test1'

Esto es críptico, porque pensé que el objetivo requireera precisamente evitar la carga recursiva. Supongo que requirese comporta como loaddurante el tiempo de compilación.

¿Cuál es una buena (y segura) forma de deshacerse de estas advertencias?

El manual ofrece una solución alternativa (lo publico como una respuesta mejor que nada a continuación), pero en última instancia, me gustaría que la solución sea bastante automática (no requiriendo que enumere todas las funciones y variables que necesitaré en cada archivo).

La solución ideal sería incorporada en emacs o provista con Cask. Si no existe, tomaré lo que está disponible, por supuesto.

T. Verron
fuente

Respuestas:

8

Sobre requiere

requireno está destinado a evitar la carga recusiva, sino a la carga repetitiva . Entonces no, no resuelve tu problema aquí.

Sobre el problema

La forma correcta de abordar esto (en mi opinión) sería evitar la dependencia mutua.

El test1archivo en su ejemplo no tiene ninguna razón para requerir test2. Incluso si eso no es cierto para su paquete real, tal vez pueda rediseñar cómo está delegando el código entre los archivos. En general, es posible evitar la dependencia mutua entre sus archivos.

Trabajar alrededor

  1. Si no se puede evitar la dependencia mutua, el manual menciona una solución. Tendrá que agregar líneas como las siguientes para cada función / variable que necesite.

    (declare-function test-fun1 "./test1.el")
    (defvar test-var1)
    
  2. Otra opción es para requirelos archivos solo condicionalmente. Agregue algo como esto al archivo 1:

    (defvar test1-is-loading t)
    (unless (and (boundp 'test2-is-loading)
                 test2-is-loading)
      (require 'test2))
    

    Y algo como esto para archivar 2:

    (defvar test2-is-loading t)
    (unless (and (boundp 'test1-is-loading)
                 test1-is-loading)
      (require 'test1))
    
Malabarba
fuente
La situación realista es un paquete en el que el archivo principal declara algunas cosas (variables como un mapa de modo, rutas ejecutables, grupo personalizado), requiere otros archivos que proporcionen un montón de funciones para el paquete y lo reúne todo junto (al menos en un -modefunción). El ejemplo es un ejemplo de juguete, si se usa test-fun2no cambiaría el problema en absoluto.
T. Verron
1
@ T.Verron Sí, trabajé suponiendo que su ejemplo no era completamente exacto, y le ofrecí soluciones. Aún así, mantengo mi declaración de que el paquete puede estar mejor diseñado. En su ejemplo realista, no hay razón para que el archivo que reúne todo junto también sea el que defina las variables y los grupos. Cree un archivo adicional que contenga estas definiciones (como test-variables), y este archivo no tendrá que requerir ninguna de las otras.
Malabarba
Como mencioné en mi respuesta inicial, su primera solución funciona bien, pero es bastante tediosa. El segundo suena prometedor, pero será un poco más complicado: las declaraciones deberán estar envueltas en eval-when-compile's, y los paquetes deberán establecer su variable al nilfinal del archivo (porque todos los archivos están compilados en Una sola sesión). También tiene la ventaja de mostrarme por qué es exactamente más complicado evitar la carga recursiva que la carga repetida.
T. Verron
Y gracias por la sugerencia de refactorización, esto de hecho funcionaría.
T. Verron
4

Tu ejemplo es raro:

  • Usted requireprueba2 al final de la prueba1, mientras requireque "siempre" debe estar al comienzo de un archivo.
  • Su test1 no llama a ninguna función test2, por lo que no necesita test2 para funcionar (por lo tanto, requirees innecesariamente), y OTOH su test2 llama a funciones test1, por lo que necesita test1, pero no requirelo hace.

IOW, tienes tu requires al revés.

Stefan
fuente
En un ejemplo real, test1por supuesto, usaría las funciones definidas por test2, y el paquete solo se cargaría test1(a través de carga automática). ¿No debería ser esta respuesta un comentario en su lugar? Simplemente estoy señalando que mi ejemplo está bastante mal elegido y no proporciona una respuesta a la pregunta.
T. Verron
@ T.Verron Esta respuesta respondió el caso específico que ofreció. El hecho de que fuera un ejemplo mal elegido no es su culpa. ;-)
Malabarba
Dado que ambas respuestas abordan este punto de refactorización, debo suponer que es un punto válido dada la pregunta. El suyo tuvo el beneficio de abordar los problemas generales, más los indicadores en el comentario para mi caso específico. ¿Es realmente probable que este sea útil para alguien con un caso de uso real?
T. Verron
Y con una visión más a corto plazo, si hubiera sido publicado como un comentario, podría haber editado la pregunta para hacerla (ligeramente) más realista. Pero se publica como una respuesta, por lo que no puedo editar la pregunta sin convertirla en una pregunta diferente (diferente porque una respuesta anterior ya no sería aplicable). Imo, esta respuesta cae precisamente en el ámbito de los comentarios: pedir aclaraciones (en este caso, una confirmación de que el ejemplo mínimo es realmente representativo del problema) del autor de la pregunta.
T. Verron
3

El manual sugiere sumar declare-functiony defvarlíneas.

El test2archivo resultante es:

;;; test2.el ---  

;;; Code:

(declare-function test-fun1 "./test1.el")
(defvar test-var1)

(defun test-fun2 ()
  (let ((test test-var1))
    (test-fun1 test)))

(provide 'test2)
;;; test2.el ends here

Sin embargo, esto debe hacerse para todas las funciones y todas las variables definidas en los archivos "primarios".

T. Verron
fuente