⏱️ Lectura: 7 min
Doug Hoyte liberó en 2026 la lectura completa en línea de Let Over Lambda, su libro de 2008 sobre macros en Common Lisp (fuente: Let Over Lambda). El libro sigue vigente, pero no porque ahora sea gratis: sigue vigente porque explica algo que casi ningún lenguaje convencional ofrece igual, una forma de reescribir el código antes de que exista, en el mismo lenguaje en que se escribe el programa.
📑 En este artículo
La idea no es nueva. Lisp introdujo los macros mucho antes de que otros lenguajes convencionales tuvieran algo parecido a generar código en tiempo de compilación. Common Lisp heredó esa capacidad y la volvió práctica para producción, y sobre eso está escrito Let Over Lambda.
Macros en Common Lisp: código que escribe código
En Common Lisp, el código fuente ya es una estructura de datos, listas anidadas que el lector convierte en s-expresiones antes de evaluarlas. defmacro aprovecha esa propiedad. Declara una función que corre en tiempo de expansión, recibe la llamada al macro sin evaluar sus argumentos y devuelve otra expresión, que el compilador inserta en el lugar original antes de seguir (especificación de defmacro, HyperSpec de Common Lisp). Una función común recibe valores ya calculados; un macro recibe código y entrega código.
Esa diferencia habilita cosas que en otros lenguajes requieren un paso aparte, como una macro de C, un decorador de Python que corre en import o un plugin de Babel para JavaScript. En Common Lisp, el mismo lenguaje que usás para programar sirve para programar el compilador. Por eso el libro insiste en que programar con macros es, en el fondo, programar el propio lenguaje.
flowchart LR
A["Codigo fuente: llamada al macro"] --> B["Macroexpansion: corre el cuerpo de defmacro"]
B --> C["Codigo expandido: nueva s-expresion"]
C --> D["Compilador"]
D --> E["Codigo maquina"]
Qué enseña Let Over Lambda que un tutorial básico no cubre
El libro arranca con los fundamentos de Common Lisp y avanza hasta sus técnicas más exigentes (fuente: Let Over Lambda). El capítulo 6, por ejemplo, recorre los macros anafóricos, los que capturan una variable implícita como it para evitar repetir una expresión, y termina en pandoric-eval, un macro que el propio Hoyte señala como uno de los más interesantes del libro (fuente: Let Over Lambda).
pandoric-eval usa closures para simular objetos con estado mutable sin necesitar una clase ni un sistema de objetos aparte. Es la misma idea que le da nombre al libro: una variable local (let) que envuelve una función (lambda) y persiste entre llamadas, sin exponer esa variable al resto del programa.
A quién le sirve esto y qué hacer con ello
Esto le sirve, sobre todo, a quien ya mantiene un motor de reglas, un DSL interno o una validación repetitiva en Python, JavaScript o Java. Si tu equipo copia y pega la misma estructura de validación en cada endpoint, un macro resuelve en tiempo de compilación lo que hoy resolvés a mano en cada llamada en tiempo de ejecución.
Para un desarrollador backend que mantiene ese tipo de DSL, la acción concreta es esta: leé el capítulo 6 de Let Over Lambda antes de escribir otro intérprete de reglas a mano, y evaluá si tu problema es realmente de generación de código (lo que resuelve un macro) o solo de configuración (lo que resuelve un archivo YAML). Si decidís probar Common Lisp para eso, no transcribas los macros del libro letra por letra: instalá el paquete thephoeron/let-over-lambda, la versión que Doug Hoyte preparó para producción, lista para ASDF y Quicklisp. Lo instalás siguiendo la guía oficial de Quicklisp, no con código transcrito a mano.
Cómo probarlos sin copiar código a ciegas
Este artículo no corrió el código del libro: no hubo laboratorio para esta nota, así que no hay salidas de consola propias ni mediciones inventadas. Para ver los macros reales, abrí el capítulo 6 en letoverlambda.com y probalos en un REPL con SBCL, instalado vía la guía oficial de Quicklisp. Si preferís no transcribir macros a mano, cargá directamente el paquete thephoeron/let-over-lambda, pensado para eso.
💡 Tip: antes de escribir un macro propio, probá resolver el mismo problema con una función de orden superior. Si no podés, porque necesitás evitar evaluar un argumento o generar código repetitivo, ahí sí un macro se justifica.
Common Lisp frente a otros sistemas de macros
La forma de pensar macros de Common Lisp no es la única. Racket, Clojure y la propia comunidad de Common Lisp manejan la higiene (evitar que una variable del macro choque con una del código que lo usa) de formas distintas.
| Sistema | Higiene | Cómo se obtiene | Para quién conviene |
|---|---|---|---|
| Common Lisp (defmacro) | No higiénico por defecto; el programador maneja la captura con gensym | Incluido en cualquier implementación conforme al estándar (HyperSpec) | Quien quiere control total sobre el código generado |
| thephoeron/let-over-lambda | Igual que Common Lisp, pero los macros ya vienen probados y empaquetados | GitHub, vía ASDF/Quicklisp; unas 155 estrellas al 2026-11-13 | Quien quiere usar los macros del libro sin copiarlos a mano |
| Racket (syntax-rules/syntax-case) | Higiénico por defecto, evita la captura de variables automáticamente | Incluido en Racket | Quien prioriza seguridad sobre control manual |
| Clojure (defmacro) | No higiénico, usa convención de gensym en la sintaxis de backtick | Incluido en Clojure | Equipos que ya corren JVM y quieren macros sin adoptar un Lisp completo |
La falta de higiene de defmacro genera debate real en la comunidad. En un hilo de Hacker News sobre Common Lisp, el usuario kazinator remarca que un macro calcula un dato, no una referencia: una forma citada con quote no resuelve los símbolos que contiene, así que evitar la captura de variables queda en manos de quien escribe el macro (fuente: comentario en Hacker News). Otro usuario, bjoli, defiende esa libertad: prefiere decidir caso por caso si un macro depende de la definición local (con datum->syntax) o evita renombrar variables para no alterar el comportamiento esperado (fuente: comentario en Hacker News). Es el mismo compromiso que separa a Common Lisp de Racket, lo que ahí se resuelve de fábrica, acá queda en manos del programador.
Cuándo no conviene
Common Lisp no es la opción por defecto para todos los casos. Depurar una macroexpansión es más difícil que depurar una función, porque el error puede estar en el código generado y no en el que escribiste. Los editores para Common Lisp, como Emacs con SLIME o Lem, tienen menos soporte automático que los de Python o TypeScript, y el ecosistema de librerías es mucho más chico que el de npm o PyPI. Para un servicio con muchas dependencias externas, eso puede significar escribir más código propio del que escribirías en otro lenguaje.
Tampoco conviene si tu equipo no está dispuesto a leer macroexpansiones ajenas. Un macro mal documentado es, en la práctica, un lenguaje nuevo que cada persona del equipo tiene que aprender antes de poder tocar ese código, y eso es un costo de onboarding que rara vez se mide antes de adoptarlo.
Referencias
- Let Over Lambda: fuente primaria, contenido del libro y su tabla de capítulos.
- thephoeron/let-over-lambda: paquete de producción de los macros del libro, listo para ASDF y Quicklisp.
- HyperSpec de Common Lisp, defmacro: especificación oficial de la forma defmacro.
- Quicklisp: guía oficial para instalar librerías y paquetes de Common Lisp.
Seguimos el tema en @programacion.
Imagen destacada: Foto de Gabriel Heinzer en Unsplash
Así preparamos cada artículo (fuentes, pruebas y criterio editorial): Cómo trabajamos.
¿Te sirvió? ¿Te dio otro error? Contalo abajo: las preguntas se responden y le sirven al siguiente que llegue.
Dejar un comentario
0 Comentarios