7

Tengo problemas para entender qué sucede con las macros leídas al compilar un archivo de código lisp en un bytecode o ensamblado sin formato (o un archivo fasl para el caso). O tal vez lo entiendo pero no sé. Estoy realmente confundido.Compilación de código Lisp con macros de lectura

Cuando utiliza una macro de lectura, ¿no tiene que tener la fuente disponible?

Si lo hace, entonces tiene que estar ejecutando el código fuente que conforma la función de la macro de lectura. Si no lo haces, ¿cómo pueden funcionar cuando puedes hacer cosas como read-char?

Para hacer eso, si quiere que la macro de lectura use variables predefinidas, tiene que estar ejecutando todo el código anterior, por lo que esto se convierte en tiempo de ejecución que arruina todo.

Si no ejecuta el código antes, entonces las cosas definidas anteriormente no estarán disponibles.

¿Qué ocurre con las funciones o las macros del compilador que definen las macros de lectura? Supongo que no funcionarían a menos que require o load un archivo o algo que no se haya compilado. Pero si fueran compilados, ¿entonces no podrían usarlos?

Si algunas de mis suposiciones son correctas, significa que hay una gran diferencia en "qué datos estarán disponibles para las macros" y "qué macros estarán disponibles para las funciones" dependiendo de si está compilando un archivo completo para ejecute más tarde o interprete un archivo una línea a la vez (es decir, leyendo, compilando y evaluando una expresión después de otra).

En resumen, parece que para compilar una línea a un formulario donde se puede ejecutar sin más procesamiento de macros o lo que sea, debe leer, compilar y ejecutar las líneas anteriores.

Recuerda una vez más que estas preguntas se aplican a la compilación de Lisp, no interpretarlo donde puede ejecutar cada línea, ya que viene en.

Lo siento por mi laberíntica, pero yo soy nuevo en Lisp y quiero saber más acerca de cómo se trabajos.

Respuesta

1

Las macros (incluidas las macros de lectura) no son más que funciones, y se tratan del mismo modo que todas las otras funciones. No es necesario que conserve un código fuente una vez que se haya compilado una función o una macro.

Muchas implementaciones de Lisp no harían ninguna interpretación en absoluto. Por ejemplo, SBCL de forma predeterminada solo compilará, sin cambiar a un modo de interpretación, incluso para eval. Un matiz importante es que la compilación de Common Lisp es incremental (se opone a la compilación separada, común en muchas implementaciones de Scheme e idiomas como C y Java), que le permite compilar una función o macro y usarla de inmediato, en el mismo " unidad de compilación ".

+0

En realidad, SBCL tiene el modo de interpretación, simplemente está desactivado por defecto: http://www.sbcl.org/manual/Interpreter.html –

+0

La compilación Common Lisp es incremental, pero la compilación de archivos se define de forma ligeramente diferente. –

+0

@Rainer ¿puedes dar más detalles? No estoy familiarizado con él –

5

Esto es realmente una pregunta interesante, y algo con lo que muchos programadores de Lisp principiantes luchan. Una de las principales razones para esto es que todo funciona en su mayoría "como se esperaba" y solo comienzas a pensar en estas cosas cuando comienzas a usar las funciones más avanzadas de Lisp.

La respuesta breve a su pregunta es, sí, para que el código se compile correctamente, parte del código anterior debe haberse ejecutado. Tenga en cuenta la palabra algunos, esa es la clave. Hagamos un pequeño ejemplo.Considere un archivo con el siguiente contenido:

(print 'a) 

(defmacro bar (x) `(print ,x)) 

(bar 'b) 

Como ya se ha dado cuenta, si ejecuta COMPILE-FILE en este archivo, el .fasl archivo resultante contener simplemente la versión compilada del código siguiente:

(print 'a) 
(print 'b) 

"Pero", podría preguntar: "¿Por qué se ejecutó el formulario DEFMACRO durante la compilación, pero el formulario PRINT no?". La respuesta se explica en la sección de Hyperspec 3.2.3. Contiene la siguiente frase:

Normalmente, las formas de nivel superior que aparecen en un archivo compilado con compilación de archivos se evalúan sólo cuando el archivo compilado resultante es cargado, y no cuando se compila el archivo. Sin embargo, normalmente es el caso en que algunos formularios del archivo deben evaluarse en el momento de la compilación para que el resto del archivo pueda leerse y compilarse correctamente.

Hay un formulario que se puede utilizar para controlar exactamente cuándo se evalúa un formulario. Utiliza EVAL-WHEN para este propósito. De hecho, así es exactamente como el compilador Lisp implementa DEFMACRO. Se puede ver cómo su Lisp implementa escribiendo lo siguiente desde el REPL:

(macroexpand '(defmacro bar (x) `(print ,x))) 

Obviamente diferentes implementaciones de Lisp implementarán esto de manera diferente, pero lo importante es clave que envuelve la definición de una forma: (eval-when (:compile-toplevel :load-toplevel :execute) ...). Esto le dice al compilador que el formulario debe evaluarse tanto cuando se compila el archivo como cuando se carga el archivo. Si no hiciera esto, no podría usar la macro en el mismo archivo como se definió. Si el formulario solo se evaluó cuando se compiló el archivo, no podría usar la macro en un archivo diferente después de cargarlo.

+2

El archivo compilado no solo contiene las dos instrucciones de impresión, sino que también contiene la definición de macro. –

+1

Ejecutar una macro de compilación tanto en tiempo de compilación como en tiempo de ejecución es en realidad lo que esperaba para las macros del compilador. Sin embargo, esto no (que veo) direcciones macros de lectura. Las macros de compilación son funciones simples (toman los objetos reales como parámetros, por lo que teóricamente pueden retrasar la expansión hasta el tiempo de ejecución), pero las macros de lectura dependen del texto. ¿Cómo trabajan? –

+0

@SethCarnegie la compilación es incremental. Cada formulario se procesa a medida que se leen (procesados ​​en este contexto significa compilados o evaluados, o ambos). Esto significa que la primera forma puede modificar el comportamiento del lector, y este nuevo comportamiento afectará a los formularios posteriores a medida que se leen. –

4

Compilación de archivos se define en Common Lisp: CLHS Section 3.2.3 File Compilation

Durante la compilación: hacer uso de un formulario mediante una macro de lectura, lo que tiene que hacer que leer la ejecución de macro disponible para el compilador.

Normalmente, estas dependencias se manejan con una instalación defsystem, donde se describen las dependencias entre varios archivos de un sistema (algo así como un proyecto). Para compilar un determinado archivo, se debe cargar otro archivo (preferiblemente la versión compilada) en el compilador Lisp.

Ahora, si quiere definir la macro de lectura y tener formularios usando su notación en el mismo archivo, entonces nuevamente debe asegurarse de que el compilador conozca la macro de lectura y su implementación. El compilador de archivos tiene un entorno de compilación. No carga las funciones compiladas del mismo archivo en este entorno de forma predeterminada.

Para que el compilador tenga conocimiento de cierto código en un archivo que compila, Common Lisp proporciona EVAL-WHEN. mirada

Vamos a un ejemplo de macro lee:

(set-syntax-from-char #\] #\)) 

(defun reader-example (stream char) 
    (declare (ignore char)) 
    (let ((class (read stream t nil t)) 
     (args (read-delimited-list #\] stream t))) 
    (apply #'make-instance 
      class 
      args))) 

(set-macro-character #\[ 'reader-example) 

(defclass example() 
    ((name :initarg :name))) 

(defvar *examples* 
    (list [example :name e1] 
     [example :name e2] 
     [example :name e3])) 

Si carga la fuente anterior, todo está bien. Pero si usamos un compilador de archivos, no compila sin cargarlo primero. El compilador de archivos, por ejemplo, se invoca llamando a la función COMPILE-FILE con un nombre de ruta.

Ahora la elaboración del expediente:

(set-syntax-from-char #\] #\)) 

encima no se ejecutará en tiempo de compilación. El nuevo cambio de sintaxis no estará disponible en tiempo de compilación.

(defun reader-example (stream char) 
    (declare (ignore char)) 
    (let ((class (read stream t nil t)) 
     (args (read-delimited-list #\] stream t))) 
    (apply #'make-instance 
      class 
      args))) 

La función anterior se compila, pero no se carga. Su implementación no está disponible para el compilador en pasos posteriores.

(set-macro-character #\[ 'reader-example) 

Una vez más, el formulario de arriba no se ejecuta - sólo código para ello se genera.

(defclass example() 
    ((name :initarg :name))) 

El compilador toma nota de la clase, pero no puede hacer instancias de ella más adelante.

(defvar *examples* 
    (list [example :name e1] 
     [example :name e2] 
     [example :name e3])) 

Por encima de código desencadena un error, ya que la lectura macro no está disponible en tiempo de compilación - a menos que se haya cargado antes.

En la actualidad hay dos soluciones fáciles:

  • poner la ejecución de la macro de lectura en un archivo separado y asegurarse de que se compila y se carga antes que cualquier archivo que utiliza la macro de lectura.

  • poner una EVAL-WHEN todo el código que debe tener efecto en tiempo de compilación:

Ejemplo:

(EVAL-WHEN (:compile-toplevel :load-toplevel :execute) 
    (do-something-also-at-compile-time)) 

Sobre será visto por el compilador y también ejecutó a continuación. Ahora debe asegurarse de que el código tenga todo lo que llama (todas las definiciones necesarias) en tiempo de compilación.

No hace falta decir: es un buen estilo para reducir las dependencias de compilación tanto como sea posible. Por lo general, coloque la funcionalidad necesaria en un archivo separado y asegúrese de que este archivo se compile y cargue en el compilador Lisp antes de compilar un archivo que lo use.

Cuestiones relacionadas