Tema 5: Macros
Hoy veremos...
• Conceptos generales
• Quasiquotation
• Macros en Scheme
Referencias
Definición de macro
• Una macro consiste en una plantilla o meta-expresión que define un patrón de sustitución formado por unas variables libres y unas expresiones
textuales.
• Es una técnica de generación de código
• Se realiza en la fase de preprocesamiento (en lenguajes compilados como C o C++)
• Previamente a la evaluación de la expresión (en lenguajes interpretados)
• Las macros son un meta-lenguaje, en el sentido de que permiten controlar y definir expresiones del propio lenguaje
Evolución histórica de las macros
• Orígen de las macros: ensamblador
• Se incorporan en lenguajes más próximos al ensamblador como C y en lenguajes avanzados como LISP o Scheme como una posibilidad de extender el lenguaje
• En la actualidad no se usan demasiado, aunque su característica de meta-programación ha dejado una huella muy importante en los lenguajes de programación:
• Anotaciones en Java
• Scaffolding en Ruby & Rails
Macros en C
• Ejemplos de Macros:
• Una llamada a la macro:
• Se sustituirá (expansión de la macro) por:
#define LINE_LEN 80
#define MAX(a,b) ((a) > (b) ? (a) : (b))
#define SWAP(a,b) {int t = (a); (a) = (b); (b) = t;}
x = MAX(p+q, r+s);
Las macros permiten definir nuevos lenguajes
• Ejemplo en C:
• Lo probamos:
• Esta posibilidad es mucho más potente en las macros de Scheme
• Lenguajes de dominio: las universidades y departamentos de investigación inventan lenguajes adaptados especificamente a dominios específicos como robótica. Es muy fácil diseñar prototipos de estos lenguajes con las macros de Scheme #define then #define begin { #define end ;} if (i > 0) then begin a = 1; b = 2 end
Problemas de la expansión en C
• El preprocesador de C extiende la macro de forma ciega sin tener ninguna relación con el proceso de compilación posterior
• Esto puede producir errores. Por ejemplo, si llamamos a MAX(x++, y++) la
expansión de la macro produce el siguiente resultado:
• LISP y Scheme utilizan las llamadas macros higiénicas que encapsulan implícitamente sus argumentos y evitan los errores de las macros de C
Quasiquotation
• La forma especial quasiquote junto con unquote permite evaluar de forma
arbitraria las expresiones que nos interesan en una lista:
• Se utiliza el símbolo backquote y la coma: (define bar 2)
(quasiquote (foo (unquote bar) baz)) --> (foo 2 baz)
`(foo ,bar baz) --> (foo 2 baz)
(define a 2) (define b 'hola) '(1 a b) (quasiquote (1 ,a ,b)) `(1 ,a ,b) `(1 ,a ,b ,c) `(1 ,+ ,-)
Macros en Scheme
• Se basan en las formas especiales define-syntax y syntax-rules
• Sintaxis de una macro:
• Ejemplo: mi-or (define-syntax <keyword> (syntax-rules (<literales>) ((<patron-1> <plantilla-1>) ... (<patron-n> <plantilla-n>)))) (define-syntax mi-or (syntax-rules () ((mi-or) #t) ((mi-or e) e) ((mi-or e1 e2 e3 ...) (if e1 #t (mi-or e2 e3 ...)))))
Reglas de evaluación de una macro
• Para evaluar una llamada a una macro (op exp_1 ... exp_n) debemos
seguir las siguientes reglas:
• Buscar la definición de la macro. Buscar la forma especial define-syntax en la que aparece op como clave.
• Emparejar. Buscar en la definición de la macro la regla sintáctica con la que es posible emparejar la expresión (op exp_1 ... exp_n) que
estamos evaluando. Si hay más de una regla con la que se puede emparejar la expresión, escogemos la primera de ellas.
• Transformar. Aplicar la regla para transformar la expresión.
• Evaluar. Evaluar la expresión resultante. En el caso en que la expresión resultante contenga una llamada a una macro se evaluará siguiendo estas mismas reglas.
Expansión de una macro
• Expansión de la llamada a la macro:
Sintaxis alternativas válidas
(define-syntax mi-or (syntax-rules () ((_) #t) ((_ e) e) ((_ e1 e2 e3 ...) (if e1 #t (mi-or e2 e3 ...))))) (define-syntax mi-or (syntax-rules () ((op) #t) ((op e) e) ((op e1 e2 e3 ...) (if e1 #t (mi-or e2 e3 ...)))))Depuración de macros
(define-syntax mi-or (syntax-rules () ((op) ‘#t) ((op e) ‘e) ((op e1 e2 e3 ...) ‘(if e1 #t (mi-or e2 e3 ...)))))Macro
make-procedure
(define-syntax make-procedure(syntax-rules ()
((make-procedure (x ...) expr ...) (lambda (x ...) expr ...))))
Macro
mi-let
(define-syntax mi-let (syntax-rules ()
((mi-let ((x v) ...) e ...)
Macro
mi-cond
(define-syntax mi-cond(syntax-rules (=> else)
((mi-cond (else => expr)) expr)
((mi-cond (test1 => expr1)) (if test1 expr1))
((mi-cond (test1 => expr1) (test2 => expr2) ...)
Macro
multi-print
(paradigma procedural)
(define-syntax multi-print(syntax-rules ()
((multi-print arg1 arg2 ...) (begin (print arg1)
(newline)
(multi-print arg2 ...))) ((multi-print) #t)))
Macro
when
(paradigma procedural)
(define-syntax when(syntax-rules ()
((when condition expr1 expr2 ...)