stringtranslate.com

Macro higiénica

En informática , las macros higiénicas son macros cuya expansión está garantizada para no causar 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 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 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 de macros higiénicas, inspirada en la terminología utilizada en matemáticas. [3]

El problema de la higiene

Sombreado variable

En los lenguajes de programación que tienen sistemas de macros no higiénicos, es posible que las vinculaciones de variables existentes se oculten de una macro mediante vinculaciones 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 ; }               

Ejecutar lo anterior a través del preprocesador C produce:

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 ); devuelve 0 ; }                           

La variable adeclarada en el ámbito superior queda oculta por la avariable en la macro, lo que introduce un nuevo ámbito . Como resultado, anunca se ve alterada por la ejecución del programa, como lo 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 extenderse más allá de las vinculaciones de variables. Considere esta macro de Common Lisp :

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

Si bien no hay referencias a variables en esta macro, se supone que los símbolos "if", "not" y "progn" están todos vinculados a sus definiciones habituales en la biblioteca estándar. Sin embargo, la macro anterior se utiliza en el siguiente código:

( flet (( not ( x ) x )) ( my-unless t ( format t "¡Esto no debería imprimirse!" )))        

La definición de "no" ha sido alterada localmente y con ello la expansión de my-unlesslos cambios.

Sin embargo, tenga en cuenta que para Common Lisp este comportamiento está prohibido, según 11.1.2.1.2 Restricciones del paquete COMMON-LISP para programas conformes. De todos modos, también es posible redefinir funciones por completo. Algunas implementaciones de Common Lisp proporcionan bloqueos de paquetes para evitar que el usuario cambie las definiciones de los paquetes por error.

Redefinición de funciones definidas por el programa

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

( defun operador definido por el usuario ( cond ) ( no cond ))    ( defmacro my-unless ( condición &body cuerpo ) ` ( if ( operador definido por el usuario , condición ) ( progn ,@ cuerpo )))         ; ... más tarde ...( flet (( operador-definido-por-el-usuario ( x ) x )) ( my-unless t ( format 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 idiomas que carecen de macros higiénicas

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

Ofuscación

La solución más simple, si se necesita almacenamiento temporal durante la expansión de una macro, es utilizar nombres de variables inusuales en la macro con la esperanza de que el resto del programa nunca utilice los 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 denominada , 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 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 INCIava a fallar de la misma forma 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 se deja al programador. Este método se utilizó en MacLisp , donde una función nombrada gensympodía usarse para generar un nuevo nombre de símbolo. gensymExisten funciones similares (generalmente también nombradas) en muchos lenguajes similares a Lisp, incluido el estándar Common Lisp ampliamente implementado [4] y Elisp .

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

Símbolo no internado de tiempo de lectura

Esto es similar a la ofuscación en el sentido de que un nombre único 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 el momento de la lectura (indicado por la #:notación), para el cual es imposible que aparezca fuera de la macro, de manera similar a gensym.

Paquetes

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

Por ejemplo, en el ejemplo de redefinición de una 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 de usuario será entonces un símbolo diferente, no relacionado con el utilizado en la definición de la my-unlessmacro.

Objetos literales

En algunos lenguajes, la expansión de una macro no necesita corresponderse con un 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 hace referencia f. De manera similar, si la macro necesita utilizar 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 envolvente sea el de la definición de la macro.

Transformación higiénica

Los macrosistemas 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 los que se desea la captura, algunos sistemas permiten al programador violar explícitamente los mecanismos de higiene del macrosistema.

let-syntaxPor ejemplo, los sistemas de creación de macros y de Scheme define-syntaxson higiénicos, por lo que la siguiente implementación de Scheme my-unlesstendrá el comportamiento deseado:

( define-sintaxis mi-a-menos ( reglas-de-sintaxis () (( _ cuerpo de la condición ... ) ( if ( no condición ) ( comienzo cuerpo ... )))))             ( let (( not ( lambda ( x ) x ))) ( my-unless #t ( display "¡Esto no debería imprimirse!" ) ( nueva línea )))         

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

Implementaciones

Los sistemas macro que hacen cumplir automáticamente la higiene se originaron con Scheme. El algoritmo KFFD original para un sistema macro higiénico fue presentado por Kohlbecker en 1986. [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 declarativo basado en patrones para escribir macros, que fue el predecesor de la syntax-rulesfunción macro adoptada por el estándar R5RS. [1] [7] Bawden y Rees propusieron en 1988 cierres sintácticos, un mecanismo de higiene alternativo, como una alternativa al sistema de Kohlbecker et al. [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 macro, 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 ser 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, sintaxis-caso, cierres sintácticos).

Reglas de sintaxis

Syntax-rules es una herramienta de comparación de patrones de alto nivel que intenta hacer que las macros sean más fáciles de escribir. Sin embargo, syntax-rulesno puede describir de forma sucinta ciertas clases de macros y es insuficiente para expresar otros sistemas de macros. Syntax-rules se describió en el documento R4RS en un apéndice, pero no se impuso. Más tarde, R5RS lo adoptó como una herramienta de macros estándar. A continuación, se muestra un ejemplo de syntax-rulesmacro que intercambia el valor de dos variables:

( define-sintaxis swap! ( reglas-sintaxis () (( _ a b ) ( let (( temp a )) ( set! a b ) ( set! b temp )))))               

Sintaxis-case

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

( define-sintaxis swap! ( lambda ( stx ) ( sintaxis-case stx () (( _ a b ) ( sintaxis ( 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 a través de funciones de Scheme arbitrarias. Alternativamente, un escritor de macros puede elegir no usar la interfaz de coincidencia de patrones y manipular la sintaxis directamente. Al usar la datum->syntaxfunción, las macros de sintaxis-case también pueden capturar identificadores intencionalmente, 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 cambio de nombre explícito [11] son ​​dos sistemas de macros alternativos. Ambos sistemas son de nivel inferior a las reglas de sintaxis y dejan la aplicación de la higiene al escritor de la macro. Esto difiere tanto de las reglas de sintaxis como de la sintaxis de mayúsculas y minúsculas, que aplican automáticamente la higiene de forma predeterminada. Los ejemplos de intercambio anteriores se muestran aquí utilizando una implementación de cierre sintáctico y cambio de nombre explícito respectivamente:

;; cierres sintácticos ( define-sintaxis swap! ( sc-macro-transformer ( lambda ( form environment ) ( let (( a ( close-sintaxis ( cadr form ) environment )) ( b ( close-sintaxis ( caddr form ) environment ))) ` ( let (( temp , a )) ( set! , a , b ) ( set! , b temp ))))))                         ;; cambio de nombre explícito ( sintaxis de definición swap! ( transformador de macros er ( 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 ))))))                            

Lenguas 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 sencilla. Doug Hoyte, autor de Let Over Lambda , escribe: [16]

Casi todos los enfoques adoptados para reducir el impacto de la captura variable sólo sirven para reducir lo que se puede hacer con defmacro. Las macros higiénicas son, en el mejor de los casos, una barandilla de seguridad para principiantes; en el peor de los casos, forman una valla eléctrica que atrapa a sus víctimas en una prisión higienizada y segura para la captura.

—Doug  Hoyte

Muchos sistemas macro higiénicos ofrecen salidas de emergencia sin comprometer las garantías que brinda la higiene; por ejemplo, Racket permite definir parámetros de sintaxis que permiten introducir variables ligadas de forma selectiva. Gregg Henderschott ofrece un ejemplo en Fear of Macros [17] de implementación de un operador if anafórico de esta manera.

Véase también

Notas

  1. ^ ab Kelsey, Richard; Clinger, William; Rees, Jonathan; et al. (agosto de 1998). "Informe revisado5 sobre el esquema de lenguaje algorítmico". Computación simbólica y de orden superior . 11 (1): 7–105. doi :10.1023/A:1010051815785.
  2. ^ Feinberg, N.; Keene, SE; Matthews, RO; Withington, PT (1997), Programación Dylan: un lenguaje orientado a objetos y dinámico , Addison Wesley Longman Publishing Co., Inc.
  3. ^ ab Kohlbecker, E.; Friedman, DP; Felleisen, M.; Duba, B. (1986). "Hygienic Macro Expansion" (PDF) . Conferencia de la 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 no 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: Derivación de transformaciones sintácticas a partir de sus especificaciones" (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 y computación simbólica . 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 revisado6 sobre el esquema de lenguaje algorítmico (R6RS)". Comité directivo del esquema . Consultado el 13 de septiembre de 2011 .
  11. ^ Clinger, Will (1991). "Macros higiénicas mediante cambio de nombre explícito". ACM SIGPLAN Lisp Pointers . 4 (4): 25–28. doi :10.1145/1317265.1317269. S2CID  14628409.
  12. ^ Skálski, K.; Moskal, M; Olszta, P, Metaprogramación en Nemerle (PDF) , archivado desde el original (PDF) el 13 de noviembre de 2012
  13. ^ "Macros".
  14. ^ "Metaprogramación: el lenguaje Julia". Archivado desde el original el 4 de mayo de 2013. Consultado el 3 de marzo de 2014 .
  15. ^ "Sinopsis 6: Subrutinas". Archivado desde el original el 6 de enero de 2014. Consultado el 3 de junio de 2014 .
  16. ^ [1] Let Over Lambda: 50 años de Lisp, de Doug Hoyte
  17. ^ [2], Miedo a las macros

Referencias