Artículos en profundidad sobre la tecnología que da forma al futuro.

Por qué Lisp sigue importando seis décadas después

Lisp tiene más de 65 años y sigue influyendo en los lenguajes modernos. Qué lo hace perdurar y qué pueden aprender hoy los desarrolladores.

Árbol antiguo con ramas formadas por paréntesis anidados que dan frutos brillantes

Cada pocos años alguien escribe un ensayo titulado «Lisp está muerto», y cada pocos años alguien más señala que la mitad de las funciones de su lenguaje moderno favorito se copiaron de Lisp. Recolección de basura, funciones de primera clase, clausuras, tipado dinámico, homoiconicidad, desarrollo guiado por REPL, macros: todo fue inventado o popularizado en Lisp antes de que naciera la mayoría de los desarrolladores actuales. Lisp es el tipo de proyecto que necesita décadas para apreciarse por completo.

Y, sin embargo. Pregunta a un desarrollador en activo si ha usado Lisp y la mayoría dirá que no. Pregúntale si empezaría un proyecto de producción en Lisp y la mayoría te mirará raro. Aquí hay una paradoja: las ideas de Lisp conquistaron el mundo de la programación, pero el lenguaje en sí sigue siendo de nicho. Entender por qué es más interesante que el típico «Lisp es bueno, los demás lenguajes son malos» que encuentras en la mayoría de los discursos a favor de Lisp.

Lo que Lisp hizo bien de verdad

John McCarthy creó Lisp en 1958. Para ponerlo en contexto, FORTRAN tenía apenas un año. COBOL todavía no existía. La minicomputadora PDP-1 no saldría al mercado hasta dos años después. Lisp se diseñó en papel y se implementó en una IBM 704, una máquina que ocupaba una habitación entera y trabajaba con palabras de 36 bits.

Pese a ello, McCarthy tomó decisiones de diseño que siguen vigentes seis décadas después. Las más importantes:

Código como datos (homoiconicidad)

En la mayoría de los lenguajes, código y datos son cosas fundamentalmente distintas. Escribes código con una sintaxis, y ese código manipula estructuras de datos. En Lisp, el código es datos. Un programa en Lisp está hecho de listas. La expresión (+ 1 2) es al mismo tiempo una llamada a función que suma 1 y 2, y una lista con tres elementos: el símbolo +, el número 1 y el número 2. Puedes manipular esa lista (reordenarla, añadir elementos, transformarla) y luego ejecutar el resultado.

;; This is a function call
(+ 1 2)  ; => 3
;; This is a list containing the same elements
'(+ 1 2)  ; => (+ 1 2)
;; You can build code as data and then evaluate it
(def my-expr '(+ 1 2))
(eval my-expr)  ; => 3
;; Or transform it
(def doubled (list '* 2 my-expr))
;; doubled is now (* 2 (+ 1 2))
(eval doubled)  ; => 6

Esto parece una curiosidad hasta que entiendes lo que permite: programas que escriben programas. Las macros de Lisp no hacen sustitución de texto como las macros del preprocesador de C. Reciben la estructura real del programa como una estructura de datos, la transforman con todo el poder del lenguaje y devuelven una nueva estructura de programa. Esto es generación de código en tiempo de compilación, con el propio compilador como tu API.

El REPL como entorno de desarrollo

Lisp fue pionero del bucle Leer-Evaluar-Imprimir (REPL): la idea de que puedas escribir una expresión, evaluarla al instante y ver el resultado. Hoy eso suena poco novedoso, cuando todos los lenguajes tienen REPL, pero el de Lisp llega más lejos que la mayoría.

En Common Lisp o Clojure no te limitas a probar expresiones aisladas en el REPL. Desarrollas sistemas completos de forma interactiva. Defines una función, la pruebas, la vuelves a definir, todo mientras el programa sigue en ejecución. Puedes inspeccionar y modificar el estado en marcha y recompilar una sola función sin reiniciar la aplicación. El ciclo de desarrollo no es escribir-compilar-ejecutar-depurar, sino una conversación continua con un sistema vivo.

Los desarrolladores de Clojure, en particular, suelen trabajar así: conectan su editor a un REPL en ejecución, escriben código y evalúan expresiones individuales con una sola pulsación de tecla, y ven los resultados de inmediato. El ciclo de retroalimentación se mide en milisegundos, no en minutos. Una vez que trabajas así, el clásico ciclo de editar-compilar-reiniciar se siente como comunicarse por correo postal.

Sintaxis mínima, máxima flexibilidad

La sintaxis de Lisp, o más bien su falta de sintaxis, es su rasgo más polémico. No hay formas especiales para sentencias if, bucles for ni definiciones de clases. Todo es una lista con el operador al principio: (if condición expr-then expr-else), (defn nombre [args] cuerpo), (for [x (range 10)] (* x x)). Los paréntesis que a los recién llegados les resultan incómodos son el precio de entrada al lenguaje sintácticamente más regular que se haya diseñado.

Esta regularidad tiene una ventaja práctica: las herramientas. Cuando cada construcción sigue el mismo patrón (operador operandos...), los editores pueden indentar, refactorizar y navegar el código de forma estructural con fiabilidad. «Seleccionar la expresión que lo envuelve» no tiene ambigüedad en Lisp: siempre son los paréntesis que coinciden. En lenguajes con sintaxis variada, la edición estructural es un problema sin resolver. En Lisp se resolvió en los años sesenta.

Por qué Lisp no triunfó

Si Lisp es tan bueno, ¿por qué no lo usa todo el mundo? La respuesta habitual de los defensores de Lisp es «la industria se equivoca», que no ayuda y casi nunca es cierta. Lisp no alcanzó la adopción masiva por razones reales y nada triviales.

  • La brecha del ecosistema. Para la mayor parte del trabajo práctico, las librerías importan más que las características del lenguaje. Python no es popular por la belleza de su sintaxis, sino porque pip install te da acceso inmediato a miles de paquetes bien mantenidos para ciencia de datos, desarrollo web, aprendizaje automático y todo lo demás. Los ecosistemas de Lisp (Common Lisp, Scheme, Clojure) son sólidos, pero más pequeños. Pasarás más tiempo escribiendo cosas desde cero o adaptando librerías menos mantenidas.
  • La curva de aprendizaje está al principio. La sintaxis con paréntesis es trivial una vez que la interiorizas, pero supone una barrera real para quien empieza. Y, más importante, escribir Lisp idiomático exige pensar de una forma poco familiar si vienes de lenguajes procedurales u orientados a objetos. La recompensa llega más tarde, pero muchos desarrolladores abandonan antes de llegar.
  • Fragmentación. «Lisp» no es un solo lenguaje, es una familia: Common Lisp, Scheme, Racket, Clojure, Emacs Lisp, Hy, Janet, cada uno con fortalezas, ecosistemas y comunidades distintas. Esta fragmentación impide que el esfuerzo se concentre, y eso es justo lo que hace crecer a los ecosistemas.
  • El respaldo corporativo importa. Java tuvo a Sun. C# tuvo a Microsoft. Go tuvo a Google. Python tuvo a Google y a una enorme comunidad de ciencia de datos. El lenguaje de la familia Lisp más exitoso en producción, Clojure, ganó tracción en parte porque Rich Hickey es un pensador y comunicador excepcionalmente claro, pero aún carece del empuje institucional que impulsa la adopción masiva.

Clojure: Lisp que aprendió de la historia

Clojure merece atención especial porque demuestra cómo tomar las fortalezas de Lisp y hacerlas prácticas para el desarrollo de software moderno. Rich Hickey diseñó Clojure en 2007 con plena conciencia de por qué los Lisp anteriores no habían alcanzado la adopción masiva, y tomó decisiones de diseño deliberadas.

  • Corre sobre la JVM. En lugar de construir un ecosistema separado desde cero, Clojure se ejecuta en la Máquina Virtual de Java y puede usar cualquier librería de Java directamente. ¿Necesitas un driver de base de datos? Usa el de Java. ¿Necesitas un cliente HTTP? Usa el de Java. Esta única decisión dio a Clojure acceso a miles de librerías probadas en batalla desde el primer día.
  • Inmutabilidad por defecto. Las estructuras de datos centrales de Clojure (listas, vectores, mapas y conjuntos) son inmutables. No modificas un mapa: creas uno nuevo con tus cambios. Esto elimina categorías enteras de errores de concurrencia y hace los programas más fáciles de razonar. Las estructuras de datos persistentes que hay debajo comparten partes entre versiones para que esto sea eficiente.
  • Primitivas de concurrencia prácticas. Átomos, refs y agentes: Clojure ofrece varios modelos de concurrencia, cada uno adecuado a patrones distintos. No fue elegancia teórica, sino la experiencia directa de Hickey con el dolor del estado mutable compartido en aplicaciones Java de producción.
  • ClojureScript. Clojure compila a JavaScript, lo que le da acceso a los ecosistemas del navegador y de Node.js. Escribe el backend en Clojure, el frontend en ClojureScript y comparte código entre ambos. Es la misma promesa que TypeScript o Kotlin Multiplatform, pero llegó antes.

Lo que los lenguajes modernos tomaron prestado

Aunque nunca escribas una línea de Lisp, estás usando sus ideas a diario. Su influencia es tan omnipresente que seguirle la pista se convierte en un ejercicio para ver lo profunda que es.

Los map, filter y reduce de JavaScript son descendientes directos de las funciones de procesamiento de listas de Lisp; de hecho, el lenguaje lleva el nombre de ellas (LISt Processing). Las list comprehensions de Python son azúcar sintáctico para las mismas operaciones. El pattern matching de Rust se remonta, a través de ML, a las expresiones cond de Lisp. Las clausuras de Swift, la sintaxis de lambdas de Kotlin y la API de streams de Java son todas ideas de Lisp con otra ropa.

Los sistemas de macros de Rust (macro_rules! y las macros procedurales), Elixir, Nim y Julia se inspiran directamente en las macros de Lisp, aunque ninguno alcanza la misma fluidez, porque ninguno tiene sintaxis homoicónica. Cuando tu código es datos, las macros son triviales. Cuando tu código tiene una sintaxis compleja, las macros necesitan analizar y generar esa sintaxis, lo que añade fricción.

El desarrollo guiado por REPL se ha vuelto la norma en la ciencia de datos (los cuadernos de Jupyter son básicamente REPL con persistencia), y herramientas como Figwheel y shadow-cljs llevaron la recarga en caliente al desarrollo frontend, una idea que vino directamente de la tradición de Lisp de desarrollar contra un sistema en ejecución.

¿Deberías aprender Lisp?

La respuesta honesta depende de lo que quieras obtener.

Si quieres volverte mejor programador ampliando tu forma de pensar sobre el código: sí, sin duda. Aprender Lisp, en concreto aprender a pensar en términos de transformación de datos, estructuras recursivas y código como datos, cambiará la forma en que abordas problemas en cualquier lenguaje. Es como aprender programación funcional: aunque nunca uses Haskell en producción, los conceptos te harán escribir mejor Python y JavaScript.

Si quieres construir software de producción con Lisp, Clojure es la opción práctica. Tiene un ecosistema real, una comunidad profesional y empresas (Nubank, Walmart, CircleCI) que lo ejecutan a gran escala. Common Lisp es viable, pero de nicho: pasarás más tiempo en infraestructura y menos en tu problema real. Scheme y Racket son excelentes para la enseñanza y la investigación en lenguajes, pero menos prácticos para el desarrollo de propósito general.

Si tienes curiosidad pero no estás listo para comprometerte, lee código de Clojure. No tutoriales: código de producción de verdad. Fíjate en cómo los desarrolladores de Clojure componen funciones pequeñas, cómo usan las macros de encadenado (-> y ->>) para construir pipelines de datos y cómo modelan dominios con mapas simples en lugar de clases. Verás un estilo de programación más conciso, más componible y más centrado en el flujo de datos que el que encontrarás en las bases de código OOP convencionales.

La longevidad de Lisp no es nostalgia. Es el resultado de acertar tanto con unas pocas cosas fundamentales que seis décadas de diseño de lenguajes no las han mejorado. Los paréntesis son raros. El ecosistema es más pequeño de lo que te gustaría. Pero las ideas son atemporales, y si pasas tiempo con ellas, te encontrarás escribiendo mejor código en el lenguaje que uses día a día.