stringtranslate.com

Macro higiénica

En informática , las macros higiénicas son macros cuya expansión está garantizada para no provocar 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 macroexpansor. El término "higiene" fue acuñado en el artículo de Kohlbecker et al. de 1986 que introdujo la macroexpansión higiénica, 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 los enlaces de variables existentes queden ocultos 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:

#definir INCI(i) { int a=0; ++yo; } int principal ( vacío ) { int a = 4 , b = 8 ; INCI ( a ); INCI ( b ); printf ( "a ahora es %d, b ahora es %d \n " , a , b ); devolver 0 ; }               

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

int principal ( vacío ) { int a = 4 , b = 8 ; { int a = 0 ; ++ un ; }; { int a = 0 ; ++ b ; }; printf ( "a ahora es %d, b ahora es %d \n " , a , b ); devolver 0 ; }                           

La variable adeclarada en el ámbito superior queda sombreada por la avariable de la macro, lo que introduce un nuevo ámbito . Como resultado, anunca se ve alterado por la ejecución del programa, como muestra el resultado del programa compilado:

a ahora es 4, b ahora es 9

Redefinición de la función de biblioteca estándar

El problema de higiene puede extenderse más allá de las fijaciones variables. Considere esta macro de Common Lisp :

( defmacro mi-a menos que ( condición & cuerpo cuerpo ) ` ( si ( 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 vinculados a sus definiciones habituales en la biblioteca estándar. Sin embargo, si la macro anterior se utiliza en el siguiente código:

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

La definición de "no" se ha modificado localmente y, por lo tanto, 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 en el paquete COMMON-LISP para programas conformes. De todos modos, también es posible redefinir completamente las funciones. 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 programa

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

( defun operador definido por el usuario ( cond ) ( no cond ))    ( defmacro mi-a menos que ( condición y cuerpo cuerpo ) ` ( si ( operador definido por el usuario , condición ) ( progn , @ cuerpo )))         ; ... más tarde ...( flet (( operador-definido por el usuario ( x ) x )) ( my-a menos que t ( formato t "¡Esto no debe imprimirse!" )))        

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

Estrategias utilizadas en lenguajes que carecen de macros higiénicas.

El problema de higiene se puede resolver 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 la 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; ++yo; } int principal ( vacío ) { int a = 4 , b = 8 ; INCI ( a ); INCI ( b ); printf ( "a ahora es %d, b ahora es %d \n " , a , b ); devolver 0 ; }               

Hasta que se cree una variable denominada INCIa, 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 INCIen una variable INCIafallará de la misma manera 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 utilizar esta característica dentro del cuerpo de una definición de macro queda en manos del programador. Este método se utilizó en MacLisp , donde gensymse podía utilizar una función denominada para generar un nuevo nombre de símbolo. gensymFunciones similares (generalmente también nombradas ) existen en muchos lenguajes similares a Lisp, incluido el estándar Common Lisp [4] y Elisp, ampliamente implementado .

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

Símbolo no internado en tiempo de lectura

Esto es similar a la ofuscación en el sentido de que varias expansiones de la misma macro comparten un único nombre. Sin embargo, a diferencia de un nombre inusual, se utiliza un símbolo no interno de tiempo de lectura (indicado por la #:notación), que es imposible que aparezca fuera de la macro, similar a gensym.

Paquetes

Al usar paquetes como Common Lisp, la macro simplemente usa un símbolo privado del paquete en el que está definida 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 dobles ( ) para darse permiso para utilizar el símbolo privado, por ejemplo cool-macros::secret-sym. En ese punto, la cuestión de la falta accidental de higiene es discutible. Además, el estándar ANSI Common Lisp clasifica la redefinición de funciones y operadores estándar, global o localmente, como invocando un comportamiento indefinido . De este modo, la implementación puede diagnosticar que dicho uso es erróneo. Por lo tanto, el sistema de paquetes Lisp proporciona una solución viable y completa al problema de macrohigiene, que puede considerarse como un caso de conflicto de nombres.

Por ejemplo, en el ejemplo de redefinición de la función definida por el programa, la my-unlessmacro puede residir en su propio paquete, donde user-defined-operatorhay 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, sin relación con el utilizado en la definición de la my-unlessmacro.

Objetos literales

En algunos idiomas, la expansión de una macro no necesita corresponder al código textual; en lugar de expandirse a una expresión que contiene 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 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.

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 llama transparencia referencial . En los casos en que se desea la captura, algunos sistemas permiten que el programador viole explícitamente los mecanismos de higiene del macrosistema.

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

( definir-sintaxis mi-a menos que ( sintaxis-reglas () (( _ condición cuerpo ... ) ( si ( no condición ) ( comenzar cuerpo ... )))))             ( let (( not ( lambda ( x ) x ))) ( my-a menos que #t ( muestre "¡Esto no debe imprimirse!" ) ( nueva línea )))         

El macroprocesador higiénico responsable de transformar los patrones del formulario de entrada en un formulario de salida detecta conflictos de símbolos y los resuelve cambiando temporalmente los nombres de los símbolos. 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 estaba la macro. usado.

Implementaciones

Macrosistemas que hacen cumplir automáticamente la higiene se originaron con Scheme. El algoritmo KFFD original para un macrosistema higiénico fue presentado por Kohlbecker en el año 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 de macros adoptada por el estándar R5RS. [1] [7] Bawden y Rees propusieron cierres sintácticos, un mecanismo de higiene alternativo, como alternativa al sistema de Kohlbecker et al. 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. introdujo el syntax-casesistema macro, que utiliza una representación alternativa de 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, casos de sintaxis, cierres sintácticos). .

Reglas de sintaxis

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-rulesno es capaz de describir de manera sucinta ciertas clases de macros y es insuficiente para expresar otros sistemas macro. Las reglas de sintaxis se describieron en el documento R4RS en un apéndice, pero no son obligatorias. Más tarde, R5RS lo adoptó como una macroinstalación estándar. A continuación se muestra una syntax-rulesmacro de ejemplo que intercambia el valor de dos variables:

( ¡definir intercambio de sintaxis! ( reglas de sintaxis () (( _ a b ) ( let (( temp a )) ( set! a b ) ( set! b temp )))))               

Caso de sintaxis

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

( ¡definir intercambio de sintaxis! ( lambda ( stx ) ( sintaxis-case stx () (( _ a b ) ( sintaxis ( let (( temp a )) ( set! a b ) ( set! b temp ))))) ))                   

Sin embargo, syntax-casees más poderoso que las reglas de sintaxis. Por ejemplo, syntax-caselas macros pueden especificar condiciones secundarias 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. Al utilizar la datum->syntaxfunción, las macros de casos de sintaxis también pueden capturar identificadores intencionalmente, rompiendo así la higiene.

Otros sistemas

También se han propuesto e implementado otros macrosistemas para Scheme. Los cierres sintácticos y el cambio de nombre explícito [11] son ​​dos macrosistemas alternativos. Ambos sistemas son de nivel inferior 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 los casos de sintaxis, que imponen automáticamente la higiene de forma predeterminada. Los ejemplos de intercambio anteriores se muestran aquí usando un cierre sintáctico y una implementación de cambio de nombre explícito, respectivamente:

;; cierres sintácticos ( definir- intercambio de sintaxis! ( sc-macro-transformer ( lambda ( entorno de formulario ) ( let (( a ( entorno de sintaxis cerrada ( forma cadr ) )) ( b ( entorno de sintaxis cerrada ( forma caddr ) ))) ` ( let (( temp , a )) ( set! , a , b ) ( set! , b temp ))))))                         ;; cambio de nombre explícito ( definir- intercambio de sintaxis! ( er-macro-transformer ( lambda ( forma cambiar nombre comparar ) ( let (( a ( forma cadr )) ( b ( forma caddr )) ( temp ( renombrar 'temp ))) ` ( , ( renombrar 'let ) (( , temp , a )) ( , ( renombrar 'set! ) , a , b ) ( , ( rename 'set! ) , b , temp ))))))                            

Idiomas con macrosistemas higiénicos.

Crítica

Las macros higiénicas ofrecen seguridad y transparencia referencial a expensas 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 de variables sirven sólo para reducir lo que se puede hacer con defmacro. Las macros higiénicas son, en el mejor de los casos, una barrera 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

Muchos macrosistemas higiénicos ofrecen trampillas de escape sin comprometer las garantías que proporciona la higiene; por ejemplo, Racket le permite definir parámetros de sintaxis, que le permiten introducir variables vinculadas de forma selectiva. Gregg Henderschott da un ejemplo en Fear of Macros [17] de cómo implementar un operador if anafórico de esta manera.

Ver también

Notas

  1. ^ ab Kelsey, Richard; Clinger, William; Rees, Jonathan; et al. (Agosto de 1998). "Informe revisado 5 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; Mateos, RO; Withington, PT (1997), Programación Dylan: un lenguaje dinámico y orientado a objetos , Addison Wesley Longman Publishing Co., Inc.
  3. ^ ab 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". comunidad.schemewiki.org . Consultado el 11 de junio de 2022 .
  6. ^ Costanza, Pascal; D'Hondt, Theo (2010). "Integración de macros compatibles con la higiene en un sistema de macros antihigiénico". Revista de Informática Universal . 16 (2): 271–295. CiteSeerX 10.1.1.424.5218 . doi : 10.3217/jucs-016-02-0271 . 
  7. ^ Kohlbecker, E.; Varita, M. (1987). "Macro por ejemplo: derivar 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) desde el 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, Mateo; Van Straaten, Antón; et al. (Agosto de 2007). "Informe revisado6 sobre el esquema de lenguaje algorítmico (R6RS)". Comité Directivo del Plan . Consultado el 13 de septiembre de 2011 .
  11. ^ Clinger, Will (1991). "Macros higiénicos mediante cambio de nombre explícito". Punteros Lisp de ACM SIGPLAN . 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 por Doug Hoyte
  17. ^ [2], Miedo a las macros

Referencias