Este artículo puede ser demasiado técnico para que la mayoría de los lectores lo comprendan . ( Noviembre de 2016 ) |
Las macros higiénicas son macros cuya expansión está garantizada para no provocar la captura accidental de identificadores . Son una característica de los lenguajes de programación como Scheme , [1] Dylan , [2] Rust , Nim y Julia . El problema general de la captura accidental era bien conocido dentro del Lispcomunidad antes de la introducción de macros higiénicas. Los escritores de macros usarían características del lenguaje que generarían identificadores únicos (por ejemplo, gensym) o usarían identificadores ofuscados para evitar el problema. Las macros higiénicas son una solución programática al problema de captura que se integra en el propio expansor de macros. El término "higiene" fue acuñado en el artículo de 1986 de Kohlbecker et al. Que introdujo la macroexpansión higiénica, inspirada en la terminología utilizada en matemáticas. [3]
En los lenguajes de programación que tienen sistemas de macros no higiénicos, es posible que los enlaces de variables existentes se oculten de una macro mediante enlaces de variables que se crean durante su expansión. En C , este problema se puede ilustrar con el siguiente fragmento:
#define INCI (i) do {int a = 0; ++ i; } while (0) int main ( void ) { int a = 4 , b = 8 ; INCI ( a ); INCI ( b ); printf ( "a ahora es% d, b ahora es% d \ n " , a , b ); return 0 ; }
Ejecutar lo anterior a través del preprocesador de C produce:
int main ( vacío ) { int a = 4 , b = 8 ; hacer { int a = 0 ; ++ a ; } while ( 0 ); hacer { int a = 0 ; ++ b ; } while ( 0 ); printf ( "a ahora es% d, b ahora es% d \ n " , a , b ); regresar 0 ; }
La variable a
declarada en el ámbito superior está sombreada por la a
variable de la macro, lo que introduce un nuevo ámbito . Como resultado, nunca es alterado por la ejecución del programa, como muestra la salida del programa compilado:
a ahora es 4, b ahora es 9
La solución más simple es dar a las variables de macros nombres que no entren en conflicto con ninguna variable en el programa actual:
#define INCI (i) do {int INCIa = 0; ++ i; } while (0) int main ( void ) { int a = 4 , b = 8 ; INCI ( a ); INCI ( b ); printf ( "a ahora es% d, b ahora es% d \ n " , a , b ); return 0 ; }
Hasta que INCIa
se crea una variable nombrada , esta solución produce el resultado correcto:
a ahora es 5, b ahora es 9
El problema está resuelto para el programa actual, pero esta solución no es sólida. El programador debe mantener sincronizadas las variables utilizadas dentro de la macro y las del resto del programa. Específicamente, el uso de la macro INCI
en una variable INCIa
fallará de la misma manera que la macro original falló en una variable a
.
El "problema de la higiene" puede extenderse más allá de las fijaciones variables. Considere esta macro Common Lisp :
( defmacro mi-a menos que ( condición y cuerpo del cuerpo ) ` ( si ( no , condición ) ( progn , @ cuerpo )))
Si bien no hay referencias a variables en esta macro, se asume que los símbolos "si", "no" y "progn" están vinculados a sus definiciones habituales. Sin embargo, si se utiliza la macro anterior en el siguiente código:
( flet (( no ( x ) x )) ( mi-a menos que t ( formato t "¡Esto no debería imprimirse!" )))
La definición de "no" se ha modificado localmente y, por lo tanto, la expansión de los my-unless
cambios. (La redefinición de funciones y operadores estándar, global o localmente, en realidad invoca un comportamiento indefinido de acuerdo con ANSI Common Lisp. Tal uso puede ser diagnosticado por la implementación como erróneo).
Por otro lado, los sistemas macro higiénicos conservan el alcance léxico de todos los identificadores (como "si" y "no") automáticamente. Esta propiedad se llama transparencia referencial .
Por supuesto, el problema puede ocurrir para funciones definidas por programa que no están protegidas de la misma manera:
( defmacro mi-a menos que ( condición y cuerpo del cuerpo ) ` ( si ( operador-definido por el usuario , condición ) ( progn , @ cuerpo )))( flet (( operador-definido por el usuario ( x ) x )) ( mi-a menos que t ( formato t "¡Esto no debe imprimirse!" )))
La solución de Common Lisp a este problema es utilizar paquetes. La my-unless
macro puede residir en su propio paquete, donde user-defined-operator
hay un símbolo privado en ese paquete. El símbolo que user-defined-operator
aparece en el código de usuario será entonces un símbolo diferente, sin relación con el utilizado en la definición de la my-unless
macro.
Mientras tanto, lenguajes como Scheme que utilizan macros higiénicas evitan la captura accidental y aseguran la transparencia referencial automáticamente como parte del proceso de macro expansión. En los casos en que se desea la captura, algunos sistemas permiten al programador violar explícitamente los mecanismos de higiene del macrosistema.
Por ejemplo, la siguiente implementación de Scheme my-unless
tendrá el comportamiento deseado:
( definir-sintaxis mi-menos ( reglas-de-sintaxis () (( _ cuerpo de condición ... ) ( si ( no condición ) ( cuerpo de comienzo ... ))))) ( let (( no ( lambda ( x ) x ))) ( mi-a menos que #t ( muestre "¡Esto no debería imprimirse!" ) ( nueva línea )))
En algunos lenguajes como Common Lisp , Scheme y otros de la familia de lenguajes Lisp , las macros proporcionan un medio poderoso para extender el lenguaje. Aquí la falta de higiene en las macros convencionales se resuelve mediante varias estrategias.
gensym
podría usarse para generar un nuevo nombre de símbolo. Existen funciones similares (usualmente también nombradas gensym
) en muchos lenguajes similares a Lisp, incluyendo el estándar Common Lisp [4] y Elisp ampliamente implementado .#:
notación), para el cual es imposible que ocurra fuera de la macro.::
notación de dos puntos ( ) para darse permiso para usar el símbolo privado, por ejemplo cool-macros::secret-sym
. En ese momento, el problema de la falta de higiene accidental es discutible. Por lo tanto, el sistema de paquetes Lisp proporciona una solución viable y completa al problema de la higiene macro, que puede considerarse como una instancia de conflicto de nombres.let-syntax
y de Scheme define-syntax
. La estrategia básica es identificar enlaces en la definición de macro y reemplazar esos nombres con gensyms, e identificar variables libres en la definición de macro y asegurarse de que esos nombres se busquen en el alcance de la definición de macro en lugar del alcance donde se encontraba la macro. usado.f
, una macro puede producir una expansión que contenga el objeto real al que hace referencia f
. De manera similar, si la macro necesita usar variables locales u objetos definidos en el paquete de la macro, puede expandirse a una invocación de un objeto de cierre cuyo entorno léxico adjunto es el de la definición de la macro.Los sistemas macro que hacen cumplir automáticamente la higiene se originaron con Scheme. El algoritmo original ( algoritmo KFFD) para un sistema macro higiénico fue presentado por Kohlbecker en el '86. [3] En ese momento, las implementaciones de Scheme no adoptaron ningún sistema macro estándar. Poco después, en 1987, Kohlbecker y Wand propusieron un lenguaje basado en patrones declarativos para escribir macros, que fue el predecesor de la syntax-rules
facilidad de macros adoptada por el estándar R5RS. [1] [5] Los cierres sintácticos, un mecanismo de higiene alternativo, fueron propuestos como una alternativa al sistema de Kohlbecker et al. Por Bawden y Rees en el '88. [6]A diferencia del algoritmo KFFD, los cierres sintácticos requieren que el programador especifique explícitamente la resolución del alcance de un identificador. En 1993, Dybvig et al. Introdujo el syntax-case
sistema de macros, que utiliza una representación alternativa de sintaxis y mantiene la higiene de forma automática. [7] El syntax-case
sistema puede expresar el syntax-rules
lenguaje de patrones como una macro derivada.
El término macro sistema puede ser ambiguo porque, en el contexto de Scheme, puede referirse tanto a una construcción de coincidencia de patrones (p. Ej., Reglas de sintaxis) como a un marco para representar y manipular la sintaxis (p. Ej., Mayúsculas y minúsculas, cierres sintácticos) . Syntax-rules es una función de coincidencia de patrones de alto nivel que intenta hacer que las macros sean más fáciles de escribir. Sin embargo, syntax-rules
no es capaz de describir sucintamente ciertas clases de macros y es insuficiente para expresar otros macro sistemas. Las reglas de sintaxis se describieron en el documento R4RS en un apéndice, pero no fueron obligatorias. Más tarde, R5RS lo adoptó como una instalación macro estándar. Aquí hay una syntax-rules
macro de ejemplo que intercambia el valor de dos variables:
( intercambio de sintaxis de definición ! ( reglas-de-sintaxis () (( _ a b ) ( let (( temp a )) ( set! a b ) ( set! b temp )))))
Debido a las deficiencias de un syntax-rules
macro sistema puramente basado, también se han propuesto e implementado macro sistemas de bajo nivel para el Esquema. Syntax-case es uno de esos sistemas. A diferencia syntax-rules
, syntax-case
contiene un lenguaje de coincidencia de patrones y una función de bajo nivel para escribir macros. El primero permite escribir macros de forma declarativa, mientras que el segundo permite la implementación de interfaces alternativas para escribir macros. El ejemplo de intercambio de antes es casi idéntico syntax-case
porque el lenguaje de coincidencia de patrones es similar:
( intercambio de sintaxis de definición ! ( lambda ( stx ) ( caso de sintaxis stx () (( _ a b ) ( sintaxis ( let (( temp a )) ( set! a b ) ( set! b temp ))))) ))
Sin embargo, syntax-case
es más poderoso que las reglas de sintaxis. Por ejemplo, las syntax-case
macros pueden especificar condiciones laterales en sus reglas de coincidencia de patrones mediante funciones de esquema arbitrarias. Alternativamente, un escritor de macros puede optar por no utilizar la interfaz de coincidencia de patrones y manipular la sintaxis directamente. Usando la datum->syntax
función, las macros de mayúsculas y minúsculas también pueden capturar identificadores intencionalmente, rompiendo así la higiene. El estándar R6RS Scheme adoptó el sistema de macros de sintaxis-case. [8]
Los cierres sintácticos y el cambio de nombre explícito [9] son otros dos sistemas macro alternativos. Ambos sistemas son de un nivel más bajo que las reglas de sintaxis y dejan la aplicación de la higiene al escritor de macros. Esto difiere tanto de las reglas de sintaxis como de las mayúsculas y minúsculas, que imponen automáticamente la higiene de forma predeterminada. Los ejemplos de intercambio de arriba se muestran aquí usando un cierre sintáctico y una implementación explícita de cambio de nombre respectivamente:
;; cierres sintácticas ( define-sintaxis de intercambio! ( sc-macro-transformador ( lambda ( forma ambiente ) ( dejar que (( un ( Primer sintaxis ( cadr forma ) ambiente )) ( b ( Primer sintaxis ( Caddr forma ) ambiente ))) ` ( dejar que (( temp , un )) ( set! , un , b ) (¡colocar! , b temp ))))));; cambio de nombre explícito ( define-syntax swap! ( er-macro-transformer ( lambda ( formulario renombrar comparar ) ( let (( a ( formulario cadr )) ( b ( formulario caddr )) ( temp ( renombrar 'temp ))) ` ( , ( renombrar 'let ) (( , temp , a )) ( , ( renombrar ' set! ) , a , b ) ( , ( renombrar 'conjunto! ) , b , temp ))))))
Las macros higiénicas ofrecen cierta seguridad para el programador a expensas de limitar la potencia de las macros. Como consecuencia directa, las macros Common Lisp son mucho más poderosas que las macros Scheme, en términos de lo que se puede lograr con ellas. Doug Hoyte, autor de Let Over Lambda , declaró: [14]
Casi todos los enfoques adoptados para reducir el impacto de la captura de variables sirven solo para reducir lo que puede hacer con defmacro. Las macros higiénicas son, en el mejor de los casos, una barandilla de seguridad para principiantes; en la peor de las situaciones, forman una cerca eléctrica, atrapando a sus víctimas en una prisión desinfectada y segura.
- Doug Hoyte
Este artículo incluye una lista de referencias generales , pero permanece en gran parte sin verificar porque carece de suficientes citas en línea correspondientes . ( Abril de 2012 ) |