De dónde sale Lua
Tecgraf, Petrobras y la reserva de mercado brasileña: el problema industrial concreto que produjo Lua en 1993, por qué nació como lenguaje de configuración y qué presión lo convirtió en uno de propósito general.
Casi todos los lenguajes que importan nacen de una queja técnica. Lua nace de una restricción comercial: un país que durante casi una década blindó su mercado informático, un laboratorio universitario que facturaba a una petrolera, y dos lenguajes de descripción que se quedaron cortos el mismo año. Esa génesis no es anécdota de museo. Explica por qué Lua se distribuye como biblioteca y no como programa, por qué su gramática cabe en una página, y por qué la palabra que mejor lo describe no es minimalista sino embebible.
- Reconstruir el contexto industrial y político del que sale Lua en 1993.
- Distinguir DEL y SOL, los dos antecesores, y qué resolvía cada uno.
- Explicar la presión concreta que convirtió un lenguaje de descripción en uno de propósito general.
- Identificar las influencias reales de Lua y la única decisión que no copió de nadie.
Un laboratorio que vendía software a una petrolera
Tecgraf se funda en 1987 dentro de la Pontificia Universidad Católica de Río de Janeiro como grupo de computación gráfica, y su principal cliente desde el primer día es Petrobras. No es un laboratorio de investigación pura: entrega software de ingeniería que usan geólogos y proyectistas, gente que sabe de yacimientos y no de compiladores.
Sobre eso cae una circunstancia que hoy resulta difícil de imaginar. Entre 1984 y comienzos de los noventa, Brasil mantuvo una política de reserva de mercado informático que encarecía y burocratizaba la importación de hardware y, en la práctica, la de software comercial. La consecuencia para Tecgraf fue prosaica y decisiva: construir era más barato que comprar. Un equipo pequeño con clientes exigentes y sin acceso cómodo a herramientas de terceros acaba escribiendo sus propias herramientas, y las escribe a la medida exacta del problema que tiene delante.
El problema que tenían delante era este. Sus programas de ingeniería necesitaban que el usuario final describiera cosas: los campos de un formulario de entrada de datos, los atributos de un objeto gráfico, los parámetros de una simulación. Esa descripción no podía estar dentro del código en C, porque cambiaba cada semana y el usuario que la cambiaba no era programador. Tenía que vivir en un archivo aparte, legible por un ingeniero.
Merece la pena detenerse en esa última condición, porque es la que descarta todas las soluciones habituales. No bastaba con externalizar la configuración a un formato de datos: hacía falta que el formato lo escribiera un geólogo, que la aplicación anfitriona en C lo leyera sin fricción, y que un cambio en la descripción no obligara a recompilar ni a redesplegar nada. Ese triángulo de restricciones (usuario no técnico, anfitrión en C, ciclo de cambio corto) es exactamente el mismo que treinta años después define el nicho de Lua en un motor de juego o en un servidor web. La forma del problema no ha cambiado; solo han cambiado los clientes.
DEL y SOL: dos lenguajes de configuración
De ahí salen dos lenguajes hermanos, ambos declarativos, ambos de 1992 y 1993.
DEL
Data Entry Language. Describía formularios de entrada de datos: qué campos hay, de qué tipo es cada uno, qué rango admite, qué valor toma por defecto. El programa anfitrión leía la descripción y generaba el diálogo. Nada de flujo de control, solo declaración.
SOL
Simple Object Language, con una sintaxis inspirada en la de BibTeX. Describía objetos con atributos con tipo para un programa de visualización de perfiles litológicos. Su forma de escribir un objeto es el antepasado directo del constructor de tablas que usarás mil veces.
Los dos comparten un rasgo que explica la forma actual de Lua: su unidad sintáctica básica es un bloque de pares de clave y valor, con anidamiento, que describe un objeto. Escrito hoy, el equivalente sería indistinguible de un archivo de configuración moderno.
-- la forma que heredamos de SOL, escrita en Lua actual
campo {
nombre = "profundidad",
tipo = "numero",
minimo = 0,
maximo = 8000,
unidad = "m",
}
Los dos funcionaban. Los dos chocaron con la misma pared casi a la vez, y la pared tiene un nombre: los usuarios querían calcular dentro de la descripción. Un campo cuyo valor por defecto dependiera de otro. Un atributo activo solo bajo cierta condición. Una unidad convertida al vuelo. Peticiones minúsculas por separado, imposibles de satisfacer sin salir del terreno declarativo.
La salida obvia era añadir a DEL un evaluador de expresiones, y a SOL otro. Eso significaba mantener dos medio lenguajes, cada uno creciendo por acumulación hacia el mismo sitio, y ninguno de los dos suficientemente general para el siguiente cliente. Roberto Ierusalimschy, Luiz Henrique de Figueiredo y Waldemar Celes tomaron la decisión que define todo lo demás: un solo lenguaje, procedural completo, diseñado desde el principio para ser incrustado dentro de una aplicación en C, que conservara la comodidad sintáctica de describir datos.
El nombre lo dice sin disimulo. Sol es el sol en portugués; Lua es la luna. El sucesor orbitando alrededor de lo que vino antes. La primera versión funcional es de julio de 1993.
flowchart TD A[Tecgraf en PUC-Rio desde 1987] --> B[Cliente Petrobras y usuarios que no programan] C[Reserva de mercado: comprar software es caro] --> B B --> D[DEL en 1992: describir formularios] B --> E[SOL en 1993: describir objetos] D --> F[Los usuarios piden calcular dentro de la descripcion] E --> F F --> G[Decision: un lenguaje procedural embebible] G --> H[Lua 1.0 en julio de 1993] style F fill:#f9e2af,color:#11111b style G fill:#a6e3a1,color:#11111b style H fill:#89b4fa,color:#11111b
De describir a calcular: el salto que crea un lenguaje
El detalle más instructivo de esta historia es que Lua no abandonó su origen declarativo al volverse general. Se lo tragó. El constructor de tablas es literalmente la sintaxis de descripción de SOL convertida en expresión de primera clase del lenguaje, y por eso un archivo de configuración en Lua sigue pareciendo un archivo de configuración:
-- esto es a la vez un dato y un programa
local perfil = {
nombre = "pozo-7",
unidades = "metros",
tramos = { 120, 340, 512 },
activo = true,
-- y aqui aparece lo que DEL y SOL no podian hacer
profundidad_total = (function()
local total = 0
for _, t in ipairs { 120, 340, 512 } do total = total + t end
return total
end)(),
}
Ese archivo se puede leer como datos, pero lo ejecuta un intérprete completo con funciones, closures y recursión. La configuración y el programa dejaron de ser categorías distintas, y ese difuminado es el rasgo comercial de Lua: te vendes como formato de configuración, entras en el proyecto sin resistencia, y resulta que eras un lenguaje.
La segunda decisión fundacional es igual de importante y menos evidente: Lua se diseñó como biblioteca, no como programa. El artefacto principal no es el ejecutable lua, que es apenas un envoltorio de doscientas líneas, sino una API en C que cualquier aplicación puede enlazar. Eso significa que el anfitrión sigue siendo el dueño del proceso, del bucle principal y de la memoria, y que Lua es un invitado con un contrato explícito.
/* el nucleo del contrato, desde 1993 hasta hoy: crear, cargar, ejecutar */
lua_State *L = luaL_newstate();
luaL_openlibs(L);
if (luaL_dofile(L, "perfil.lua") != LUA_OK)
fprintf(stderr, "%s\n", lua_tostring(L, -1));
lua_close(L);
La cronología posterior confirma que la apuesta era buena. Lua 1.1 se publica en 1994; la licencia se vuelve plenamente libre en la rama 2.x y termina siendo la licencia MIT desde Lua 5.0, en 2003. Un artículo en Dr. Dobb’s Journal en 1996 le da visibilidad fuera de Brasil, y a finales de esa década los estudios de videojuegos lo descubren: LucasArts lo usa en Grim Fandango en 1998. Un lenguaje pensado para que un geólogo describiera un pozo acabó describiendo los diálogos de un juego de aventuras, y por el mismo motivo en ambos casos.
Entre medias hay dos hitos internos que conviene conocer porque explican el lenguaje actual. En Lua 4.0, del año 2000, desaparece el último resto de estado global del intérprete: a partir de ahí toda función de la API recibe un puntero al estado, y por tanto un proceso puede alojar tantos intérpretes independientes como quiera. Esa decisión, tomada por limpieza, es la que años después haría posible dar un estado por petición HTTP o por entidad de un juego. En Lua 5.0, de 2003, la máquina virtual pasa de estar basada en una pila a estarlo en registros, aparecen las corrutinas, y el sistema de métodos por etiqueta se sustituye por las metatablas tal como las conoces. Con 5.0 el lenguaje ya tiene la forma que mantiene hoy.
Lo que hereda y lo que inventa
Conviene desmontar la idea romántica de que Lua se inventó de la nada. Es un lenguaje profundamente derivativo en su sintaxis y muy original en un solo punto.
- De Modula toma la forma de las estructuras de control: bloques que abren con una palabra y cierran con
end, sin llaves y sin sensibilidad a la indentación. Es la razón de que Lua se lea en voz alta con facilidad. - De CLU toma la asignación múltiple y, sobre todo, los retornos múltiples, que en Lua no son un truco sino un mecanismo central con reglas propias de ajuste.
- De Scheme toma las funciones como valores de primera clase y el ámbito léxico. La deuda se paga entera en Lua 5.0, cuando aparecen los closures reales con upvalues.
- De SNOBOL y de Icon toma la idea de un lenguaje de patrones propio, que no es una implementación de expresiones regulares sino un dialecto distinto y mucho más barato de implementar.
- De los arrays asociativos de la tradición de AWK toma la tabla indexada por cualquier valor.
- De C toma la portabilidad como religión y el hecho de que la interfaz principal del lenguaje sea una API en C, no un ejecutable.
Hay una consecuencia estilística de esa mezcla que conviene reconocer pronto. Lua parece un lenguaje imperativo sencillo con cuerpo de Pascal, y por dentro es un lenguaje funcional con ámbito léxico y closures reales. Esa doble naturaleza es la que hace que dos programadores escriban Lua de formas irreconciliables y ambas idiomáticas: uno construye clases con metatablas, el otro construye fábricas de closures, y el lenguaje no arbitra.
Lo que no copió de nadie, y lo que hay que subrayar, es la decisión de tener un único tipo de datos compuesto. Ni registros más listas más diccionarios más objetos: una sola estructura, la tabla, que hace de array, de diccionario, de objeto, de módulo y de espacio de nombres. Esa unificación no es una simplificación cosmética; es la que permite que el intérprete sea diminuto y la que obliga a que los objetos se construyan con metatablas en lugar de venir de fábrica.
-- una sola estructura haciendo cinco papeles distintos
local secuencia = { 10, 20, 30 } -- array
local diccionario = { host = "localhost" } -- registro
local objeto = setmetatable({}, { __index = {} }) -- objeto
local M = {} -- modulo
_G.variable_global = 1 -- espacio de nombres global
La última línea de ese fragmento es la que sorprende: el propio entorno global de Lua es una tabla ordinaria a la que puedes acceder, recorrer y sustituir. No hay una categoría privilegiada de espacio de nombres implementada en el intérprete; hay una tabla más, que resulta ser la que el compilador consulta cuando un identificador no es local. Esa uniformidad radical es lo que después hace triviales los sandboxes.
Y hay una ausencia igual de deliberada: Lua no trae sistema de tipos estático, ni modelo de objetos, ni sistema de módulos en el lenguaje. require es una función de biblioteca, no una palabra reservada. Un laboratorio de tres personas que mantiene un lenguaje para clientes industriales no puede permitirse mantener lo que no ha demostrado ser imprescindible, y ese límite de recursos, que en cualquier otro proyecto sería una debilidad, funcionó aquí como el filtro de diseño más eficaz de todos.
Lee la historia de Lua como un caso de estudio sobre de dónde vienen de verdad las decisiones de diseño y dejarás de evaluar lenguajes por su lista de características. Nada de lo que hace especial a Lua se deduce de la teoría de lenguajes de programación. La API en C como interfaz principal viene de que el cliente ya tenía una aplicación en C y no iba a reescribirla. El tamaño diminuto viene de que había que enviarlo dentro de programas de ingeniería, no instalarlo en servidores. La sintaxis que describe datos con comodidad viene de dos lenguajes de configuración muertos cuyo cadáver sigue dentro del constructor de tablas. Incluso la ausencia de hilos preemptivos, que parece una decisión de arquitectura, es en realidad una consecuencia de exigir C portable estricto en una época en la que C no tenía hilos. La lección transferible no es sobre Lua. Es que cuando heredas un sistema y una decisión te parece absurda, tu ignorancia casi siempre es del contexto y no del código: alguien resolvía un problema real que ya no está a la vista y cuya solución sigue en producción. Reconstruir ese contexto antes de tocar nada es lo que separa refactorizar de romper.
- Localiza el artículo de historia de Lua publicado en la conferencia HOPL III y extrae la lista exacta de razones que los autores dan para no haber añadido un sistema de objetos al lenguaje.
- Escribe un archivo de configuración en Lua que un no programador pueda editar, y después añade una entrada cuyo valor se calcule a partir de otras dos. Razona qué acabas de romper del contrato declarativo.
- Compara el constructor de tablas de Lua con una entrada de BibTeX y señala las tres diferencias sintácticas que separan describir de ejecutar.
- Enumera tres lenguajes de configuración actuales que hayan seguido el camino contrario, es decir, que hayan añadido lógica sin volverse lenguajes completos, y argumenta qué precio pagan por ello.
- Elige una decisión de diseño de Lua que te parezca hoy discutible y trata de reconstruir el contexto de 1993 que la hacía razonable. Si no encuentras ninguno, es que aún no has entendido la decisión.