Articulo de referencia

macro higiénico

En informática , las macros higiénicas son macros cuya expansión garantiza que no se produzca la captura accidental de identificadores . Son una característica de lenguajes de p...

En informática , las macros higiénicas son macros cuya expansión garantiza que no se produzca la captura accidental de identificadores . Son una característica de lenguajes de programación como Scheme , [ 1 ] Dylan , [ 2 ] Rust , Nim y Julia . El problema general de la captura accidental era bien conocido en la comunidad Lisp antes de la introducción de las macros higiénicas. Los programadores de macros utilizaban características del lenguaje que generaban identificadores únicos (por ejemplo, gensym) o identificadores ofuscados para evitar el problema. Las macros higiénicas son una solución programática al problema de la captura que está integrada en el expansor de macros. El término "higiene" fue acuñado en el artículo de Kohlbecker et al. de 1986 que introdujo la expansión higiénica de macros, inspirado en la terminología utilizada en matemáticas. [ 3 ]

El problema de higiene

sombreado variable

En los lenguajes de programación que tienen sistemas de macros no higiénicos, es posible que las asignaciones de variables existentes queden ocultas a una macro por asignaciones de variables que se crean durante su expansión. En C , este problema se puede ilustrar con el siguiente fragmento:

#define INCI(i) { int a=0; ++i; } 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 ; }

Al ejecutar lo anterior a través del preprocesador de C se obtiene:

int main ( void ) { int a = 4 , b = 8 ; { int a = 0 ; ++ a ; }; { int a = 0 ; ++ b ; }; printf ( "a ahora es %d, b ahora es %d \n " , a , b ); return 0 ; }

La variable adeclarada en el ámbito superior queda oculta por la avariable en la macro, que introduce un nuevo ámbito . Como resultado, anunca se modifica durante la ejecución del programa, como muestra la salida del programa compilado:

a ahora es 4, b ahora es 9

Redefinición de funciones de la biblioteca estándar

El problema de la higiene puede ir más allá de las vinculaciones de variables. Considere esta macro de Common Lisp :

( defmacro my-unless ( condición &cuerpo cuerpo ) ` ( si ( no , condición ) ( progn ,@ cuerpo )))

Aunque esta macro no hace referencia a variables, asume que los símbolos "if", "not" y "progn" están vinculados a sus definiciones habituales en la biblioteca estándar. Sin embargo, si la macro anterior se utiliza en el siguiente código:

( flet (( no ( x ) x )) ( mi-a menos que t ( formato t "¡Esto no debe imprimirse!" )))

La definición de "no" se ha modificado localmente y, por lo tanto, también la expansión de my-unlesslos cambios.

Sin embargo, cabe señalar que este comportamiento está prohibido en Common Lisp, según lo estipulado en la sección 11.1.2.1.2, Restricciones del paquete COMMON-LISP para programas conformes . Aun así, es posible redefinir completamente las funciones. Algunas implementaciones de Common Lisp proporcionan bloqueos de paquete para evitar que el usuario modifique definiciones en los paquetes por error.

Redefinición de función definida por el programa

Por supuesto, el problema puede ocurrir de manera similar con las funciones definidas por el programa:

( defun operador-definido-por-usuario ( cond ) ( no cond ))( defmacro my-unless ( condición &body body ) ` ( if ( operador-definido-por-usuario , condición ) ( progn ,@ body ))); ... más tarde ...( flet (( operador-definido-por-usuario ( x ) x )) ( my-unless t ( formato t "¡Esto no debería imprimirse!" )))

El sitio de uso redefine user-defined-operatory, por lo tanto, cambia el comportamiento de la macro.

Estrategias utilizadas en lenguajes que carecen de macros higiénicas

El problema de la higiene puede resolverse con macros convencionales utilizando varias soluciones alternativas.

Ofuscación

La solución más sencilla, si se necesita almacenamiento temporal durante la expansión de una macro, es usar nombres de variables poco comunes en la macro con la esperanza de que el resto del programa nunca utilice esos mismos nombres.

#define INCI(i) { int INCIa = 0; ++i; } 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 ; }

INCIaHasta que se cree una variable con ese nombre , esta solución produce el resultado correcto:

a ahora es 5, b ahora es 9

El problema se resuelve para el programa actual, pero esta solución no es robusta. El programador debe mantener sincronizadas las variables utilizadas dentro de la macro y las del resto del programa. En concreto, el uso de la macro INCIen una variable INCIafallará del mismo modo que la macro original falló en una variable a.

Creación de símbolos temporales

En algunos lenguajes de programación, es posible generar un nuevo nombre de variable o símbolo y vincularlo a una ubicación temporal. El sistema de procesamiento del lenguaje garantiza que esto nunca entre en conflicto con otro nombre o ubicación en el entorno de ejecución. La responsabilidad de elegir usar esta característica dentro del cuerpo de una definición de macro recae en el programador. Este método se usó en MacLisp , donde una función llamada gensympodía usarse para generar un nuevo nombre de símbolo. gensymExisten funciones similares (generalmente también llamadas) en muchos lenguajes tipo Lisp, incluido el estándar Common Lisp ampliamente implementado [ 4 ] y Elisp .

Aunque la creación de símbolos resuelve el problema de la ocultación de variables, no resuelve directamente el problema de la redefinición de funciones. [ 5 ] Sin embargo, gensymlas funcionalidades de macros y las funciones de la biblioteca estándar son suficientes para incorporar macros higiénicas en un lenguaje no higiénico. [ 6 ]

Símbolo no interno de tiempo de lectura

Esto es similar a la ofuscación en el sentido de que un solo nombre es compartido por múltiples expansiones de la misma macro. Sin embargo, a diferencia de un nombre inusual, se utiliza un símbolo no interno en tiempo de lectura (denotado por la #:notación), para el cual es imposible que ocurra fuera de la macro, similar a gensym.

Paquetes

Utilizando paquetes como en Common Lisp, la macro simplemente usa un símbolo privado del paquete en el que se define. El símbolo no aparecerá accidentalmente en el código del usuario. El código del usuario tendría que acceder al interior del paquete usando la ::notación de doble punto ( ) para darse permiso para usar el símbolo privado, por ejemplo cool-macros::secret-sym. En ese punto, el problema de la falta accidental de higiene es irrelevante. Además, el estándar ANSI Common Lisp clasifica la redefinición de funciones y operadores estándar, global o localmente, como invocación de comportamiento indefinido . Dicho uso puede ser diagnosticado por la implementación como erróneo. Por lo tanto, el sistema de paquetes de Lisp proporciona una solución viable y completa al problema de la higiene de macros, que puede considerarse un caso de conflicto de nombres.

Por ejemplo, en el ejemplo de redefinición de función definida por el programa , la my-unlessmacro puede residir en su propio paquete, donde user-defined-operatores un símbolo privado en ese paquete. El símbolo user-defined-operatorque aparece en el código del usuario será entonces un símbolo diferente, sin relación con el utilizado en la definición de la my-unlessmacro.

objetos literales

En algunos lenguajes, la expansión de una macro no tiene por qué corresponder a código textual; en lugar de expandirse a una expresión que contenga el símbolo f, una macro puede producir una expansión que contenga el objeto real al que se refiere f. Del mismo modo, si la macro necesita usar variables locales u objetos definidos en su paquete, puede expandirse a una invocación de un objeto de cierre cuyo entorno léxico sea el de la definición de la macro.

Transformación higiénica

Los sistemas de macros higiénicos en lenguajes como Scheme utilizan un proceso de expansión de macros que preserva el alcance léxico de todos los identificadores y evita la captura accidental. Esta propiedad se denomina transparencia referencial . En los casos en que se desea la captura, algunos sistemas permiten al programador infringir explícitamente los mecanismos de higiene del sistema de macros.

Por ejemplo, los sistemas de creación let-syntaxde define-syntaxmacros de Scheme son higiénicos, por lo que la siguiente implementación de Scheme my-unlesstendrá el comportamiento deseado:

( define-syntax my-unless ( syntax-rules () (( _ condition body ... ) ( if ( not condition ) ( begin body ... )))))( let (( not ( lambda ( x ) x ))) ( my-unless #t ( display "¡Esto no debería imprimirse!" ) ( newline )))

El procesador de macros higiénico, responsable de transformar los patrones del formulario de entrada en un formulario de salida, detecta conflictos de símbolos y los resuelve modificando temporalmente los nombres de los símbolos. La estrategia básica consiste en identificar las vinculaciones en la definición de la macro y reemplazar esos nombres con gensyms, e identificar las variables libres en la definición de la macro y asegurarse de que esos nombres se busquen en el ámbito de la definición de la macro en lugar del ámbito donde se utilizó la macro.

Implementaciones

Los sistemas de macros que aplican automáticamente la higiene se originaron con Scheme. El algoritmo KFFD original para un sistema de macros higiénico fue presentado por Kohlbecker en 1986. [ 3 ] En ese momento, ninguna implementación de Scheme había adoptado un sistema de macros estándar. Poco después, en 1987, Kohlbecker y Wand propusieron un lenguaje declarativo basado en patrones para escribir macros, que fue el predecesor de la syntax-rulesfacilidad de macros adoptada por el estándar R5RS. [ 1 ] [ 7 ] Los cierres sintácticos, un mecanismo de higiene alternativo, fueron propuestos como alternativa al sistema de Kohlbecker et al. por Bawden y Rees en 1988. [ 8 ] 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. introdujeron el syntax-casesistema de macros, que utiliza una representación alternativa de la sintaxis y mantiene la higiene automáticamente. [ 9 ] El syntax-casesistema puede expresar el syntax-ruleslenguaje de patrones como una macro derivada. El término macrosistema puede resultar ambiguo porque, en el contexto de Scheme, puede referirse tanto a una construcción de coincidencia de patrones (por ejemplo, reglas de sintaxis) como a un marco para representar y manipular la sintaxis (por ejemplo, casos de sintaxis, cierres sintácticos).

Reglas de sintaxis

Syntax-rules es una herramienta de coincidencia de patrones de alto nivel que intenta simplificar la escritura de macros. Sin embargo, syntax-rulesno puede describir de forma concisa ciertas clases de macros y resulta insuficiente para expresar otros sistemas de macros. Syntax-rules se describió en un apéndice del documento R4RS, pero no era obligatorio. Posteriormente, R5RS lo adoptó como una herramienta estándar para macros. A continuación, se muestra un ejemplo syntax-rulesde macro que intercambia el valor de dos variables:

( define-syntax swap! ( syntax-rules () (( _ a b ) ( let (( temp a )) ( set! a b ) ( set! b temp )))))

Caso de sintaxis

Debido a las deficiencias de un syntax-rulessistema de macros puramente basado en, el estándar Scheme R6RS adoptó el sistema de macros sintáctica-case. [ 10 ] A diferencia de syntax-rules, syntax-casecontiene tanto un lenguaje de coincidencia de patrones como una funcionalidad 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 anterior es casi idéntico en syntax-caseporque el lenguaje de coincidencia de patrones es similar:

( define-syntax swap! ( lambda ( stx ) ( syntax-case stx () (( _ a b ) ( syntax ( let (( temp a )) ( set! a b ) ( set! b temp )))))))

Sin embargo, syntax-casees más potente que las reglas de sintaxis. Por ejemplo, syntax-caselas macros pueden especificar condiciones secundarias en sus reglas de coincidencia de patrones mediante funciones Scheme arbitrarias. Alternativamente, quien escribe la macro puede optar por no usar la interfaz de coincidencia de patrones y manipular la sintaxis directamente. Al usar la datum->syntaxfunción, las macros de sintaxis también pueden capturar identificadores intencionadamente, rompiendo así la higiene.

Otros sistemas

También se han propuesto e implementado otros sistemas de macros para Scheme. Los cierres sintácticos y el renombramiento explícito [ 11 ] son ​​dos sistemas de macros alternativos. Ambos sistemas son de nivel inferior a `syntax-rules` y dejan la aplicación de la higiene al escritor de macros. Esto difiere tanto de `syntax-rules` como de `syntax-case`, que aplican la higiene automáticamente por defecto. Los ejemplos de intercambio anteriores se muestran aquí utilizando una implementación de cierre sintáctico y renombramiento explícito, respectivamente:

;; cierres sintácticos ( define-syntax swap! ( sc-macro-transformer ( lambda ( form environment ) ( let (( a ( close-syntax ( cadr form ) environment )) ( b ( close-syntax ( caddr form ) environment ))) ` ( let (( temp , a )) ( set! , a , b ) ( set! , b temp )))))));; renombramiento explícito ( ¡ definir intercambio de sintaxis! ( er-macro-transformer ( lambda ( forma renombrar comparar ) ( let (( a ( forma cadr )) ( b ( forma caddr )) ( temp ( renombrar 'temp ))) ` ( , ( renombrar 'let ) (( , temp , a )) ( , ( renombrar 'set! ) , a , b ) ( , ( renombrar 'set! ) , b , temp )))))))

Lenguajes con macrosistemas higiénicos

Crítica

Las macros higiénicas ofrecen seguridad y transparencia referencial a costa de hacer que la captura intencional de variables sea menos directa. Doug Hoyte, autor de Let Over Lambda , escribe: [ 17 ]

Casi todos los métodos para reducir el impacto de la captura variable solo sirven para limitar las posibilidades de defmacro. En el mejor de los casos, las macros higiénicas son una medida de seguridad para principiantes; en el peor, se convierten en una valla electrificada que atrapa a sus víctimas en una prisión higiénica y segura.

Doug Hoyte

Muchos sistemas de macros higiénicos ofrecen vías de escape sin comprometer las garantías que proporciona la higiene; por ejemplo, Racket permite definir parámetros de sintaxis , lo que permite introducir variables ligadas de forma selectiva. Gregg Hendershott ofrece un ejemplo en Fear of Macros [ 18 ] de cómo implementar un operador if anafórico de esta manera.

Véase también

Notas

  1. 1 2 Kelsey, Richard; Clinger, William; Rees, Jonathan; et  al. (agosto de 1998). " Informe revisado sobre el esquema del lenguaje algorítmico" . Higher -Order and Symbolic Computation . 11 (1): 7– 105. doi : 10.1023/A:1010051815785 .
  2. Feinberg, N.; Keene, SE; Matthews, RO; Withington, PT (1997), Dylan programming: an object-oriented and dynamic language , Addison Wesley Longman Publishing Co., Inc.
  3. 1 2 Kohlbecker, E.; Friedman, DP; Felleisen, M.; Duba, B. (1986). "Expansión macro higiénica" (PDF) . Conferencia ACM sobre LISP y programación funcional .
  4. "CLHS: Función GENSYM" .
  5. "higiene-versus-gensym" . community.schemewiki.org . Consultado el 11 de junio de 2022 .
  6. Costanza, Pascal; D'Hondt, Theo (2010). "Incorporación de macros compatibles con la higiene en un sistema de macros poco higiénico" . Journal of Universal Computer Science . 16 (2): 271– 295. CiteSeerX 10.1.1.424.5218 . doi : 10.3217/jucs-016-02-0271 . 
  7. Kohlbecker, E.; Wand, M. (1987). "Macro-by-example: Deriving syntactic transformations from their Specifications" (PDF) . Simposio sobre Principios de Lenguajes de Programación .
  8. Bawden, A.; Rees, J. (1988). "Cierres sintácticos" (PDF) . Lisp y programación funcional . Archivado (PDF) del original el 3 de septiembre de 2019.
  9. Dybvig, K; Hieb, R; Bruggerman, C (1993). "Abstracción sintáctica en Scheme" (PDF) . LISP and Symbolic Computation . 5 (4): 295– 326. doi : 10.1007/BF01806308 . S2CID 15737919 . 
  10. Sperber, Michael; Dybvig, R. Kent; Flatt, Matthew; Van Straaten, Anton; et al. (agosto de 2007). "Informe revisado 6 sobre el esquema de lenguaje algorítmico (R6RS)" . Comité directivo del esquema . Recuperado el 13 de septiembre de 2011 . 
  11. Clinger, Will (1991). "Macros higiénicas mediante renombramiento explícito". ACM SIGPLAN Lisp Pointers . 4 (4): 25– 28. doi : 10.1145/1317265.1317269 . S2CID 14628409 . 
  12. Skalski, K.; Moskal, M; Olszta, P, Metaprogramming in Nemerle (PDF) , archivado del original (PDF) el 13 de noviembre de 2012
  13. "Macros" .
  14. "Metaprogramación: el lenguaje Julia" . Archivado del original el 4 de mayo de 2013. Consultado el 3 de marzo de 2014 .
  15. "Sinopsis 6: Subrutinas" . Archivado del original el 6 de enero de 2014. Consultado el 3 de junio de 2014 .
  16. Ullrich, Sebastian; de Moura, Leonardo (2020-01-28). "Más allá de las notaciones: expansión macro higiénica para lenguajes de demostración de teoremas". arXiv : 2001.10490 [ cs ].
  17. , Let Over Lambda—50 Years of Lisp por Doug Hoyte
  18. Miedo a las macros

Referencias

  • Sobre Lisp , Paul Graham
  • reglas de sintaxis en schemewiki
  • sintaxis-case en schemewiki
  • ejemplos de sintaxis-case en schemewiki
  • cierres sintácticos en Schemewiki
  • macros más simples en schemewiki
  • ejemplos de macros más simples en schemewiki
  • Escritura de macros higiénicas en Scheme con Syntax-Case