Programar macros en Lisp es como reprogramar Matrix
Si algo echo de menos cuando programo con Python, Ruby u otro lenguaje similar son las macros de Lisp. Programar con macros (no tienen nada que ver con las macros del preprocesador de C como piensan algunos) es como ser Neo y cambiar todo lo que quieras en Matrix.
Además, ahora que se ponen de moda otra vez los Domain Specific Languages (DSL), lo cierto es que tener el poder de las macros de tu parte te soluciona muchos problemas.
Vamos a probar con un problema fácil que resulta imposible de resolver en Python: implementar un if-then-else usando if y else. ¿Ein? :-D La respuesta rápida es:
def if_then_else(cond, if_true, if_false): if cond: return if_true else: return if_false
¡Vamos a probarlo!
>>> if_then_else(True, "si", "no") 'si' >>> if_then_else(False, "si", "no") 'no' >>> if_then_else(True, "si", 1/0) ZeroDivisionError: integer division or modulo by zero
Y aquí nos encontramos el principal problema de esta implementación. ¿Por qué se evalua la división por 0 si se encuentra en la parte else y nuestra condición es True? La respuesta es que Python, al no tener macros ni nada parecido, solamente nos proporciona funciones para este tipo de cosas y las funciones evaluan todos sus parámetros.
Por lo tanto, resulta imposible crear estructuras tipo if-then-else. Y lo mismo ocurre con operadores como and (si el primer parámetro es falso no evaluamos el resto), or (si el primer parámetro es cierto no evaluamos el resto), etc.
Algunos me han sugerido soluciones usando los decoradores de Python para capturar los errores de la evaluación, pero el verdadero problema reside en la evaluación en si misma.
La solución en Lisp utiliza macros. Si usaramos funciones obtendríamos el mismo resultado que con Python.
(defmacro if-then-else (cond if-true if-false) `(if ,cond ,if-true ,if-false))
Esta expresión es bastante legible incluso para los profanos. Se define una macro de nombre if-then-else que recibe tres parametros. La gran diferencia es que los parámetros no se evaluan cuando se evalua la macro y la evaluación de la macro nos desvuelve lo que especificamos en el cuerpo.
Para que nos entendamos, una macro es un generador de código. Si ahora ejecuto
(if-then-else 'True "si" "no"), el intérprete evaluará la macro, que devolverá el resultado
(if 'True "si" "no"), y ese resultado será evaluado a su vez proporcionando el resultado adecuado. Vamos a verlo.
> (if-then-else 1 "si" "no") "si" > (if-then-else nil "si" "no") "no" > (if-then-else 1 "si" (/ 1 0)) "si"
Ahora la división por cero ya no da error porque no llega a evaluarse
Las macros nos dan el poder de crear programas que a su vez crean programas, ya que nos permiten jugar en el núcleo mismo del lenguaje, algo que no nos permiten los lenguajes convencionales.
A pesar de su potencia, las macros tienen los llamados "problemas de higiene", algo que se ha solucionado en Scheme y que explicaré en el próximo artículo.