Clojure es un Lisp moderno sobre la JVM, diseñado por una sola persona con una tesis muy clara: el problema del software no es la complejidad del dominio, sino la complejidad que añadimos nosotros — y la mayor parte viene del estado mutable. Todo el lenguaje es la consecuencia de esa idea.
🎯 Por qué está en este programa
Clojure es un primo de la familia JVM (Atlas) y también de la familia Lisp: comparte máquina virtual con Java y modelo con Common Lisp.
Aporta al programa la inmutabilidad como decisión total (clase 102) y las estructuras de datos persistentes que la hacen viable; y aporta un modelo de concurrencia distinto de todos los del curso: identidades separadas de valores, con
atomy con memoria transaccional (clases 135 y 136).
| Año | 2007; 1.0 en 2009; 1.12 actual |
| Autoría | Rich Hickey, tras dos años y medio de trabajo en solitario |
| Familia | Lisp sobre JVM; con influencia de Common Lisp, Scheme, Haskell y ML |
| Paradigma | Funcional, con datos inmutables; sin orientación a objetos |
| Tipado | Dinámico y fuerte; con spec y malli para contratos en ejecución |
| Memoria | La de la JVM |
| Ejecución | Bytecode JVM; también ClojureScript (a JS) y ClojureCLR |
| Estado | 🟢 Nicho sólido y estable; comunidad pequeña y muy influyente |
Rich Hickey llevaba veinte años programando en C++, Java y .NET cuando, en 2005, se tomó un paréntesis de dos años y medio —financiado con sus ahorros— para diseñar el lenguaje que quería usar.
El resultado, publicado en 2007, parte de un diagnóstico que expuso en su charla Simple Made Easy, una de las más citadas de la historia de la disciplina:
Confundimos "simple" con "fácil". Simple es no entrelazado; fácil es familiar. El estado mutable compartido es lo contrario de simple, aunque nos resulte facilísimo.
Y de ahí las decisiones:
atom, ref, agent) apunta a un valor
inmutable, y cambiarla es cambiar a qué apunta, de forma controlada.ClojureScript (2011) llevó el lenguaje al navegador, y de ahí salió Reagent/Re-frame, cuya arquitectura de estado influyó en el ecosistema React (clase 169).
Y su comunidad tiene una particularidad que merece nombrarse: es de las más estables de esta lista. El lenguaje cambia poco y a propósito, y la compatibilidad hacia atrás se cuida — la misma disciplina de Go y Tcl (clase 154).
Esta es la idea que hay que llevarse, y no la tiene ningún otro lenguaje del curso:
(def cuenta (atom {:saldo 100})) ; una IDENTIDAD que apunta a un VALOR inmutable
(swap! cuenta update :saldo + 50) ; ← cambia a QUÉ VALOR apunta, atómicamente
@cuenta ; {:saldo 150}
El mapa {:saldo 100} nunca cambia. Lo que cambia es a qué valor apunta cuenta, y ese cambio
es atómico y sin cerrojos — con comparación e intercambio por debajo.
Y de ahí sale una propiedad enorme para la clase 136: no hay carreras de datos sobre los valores, porque los valores no se pueden modificar. Solo hay que coordinar las identidades, que son pocas y están declaradas.
Y las estructuras persistentes son lo que lo hace asequible:
(def a [1 2 3])
(def b (conj a 4)) ; b es nuevo... y COMPARTE la estructura interna de a
No se copia el vector. Por debajo hay un árbol con factor de ramificación 32, y añadir un elemento crea unos pocos nodos nuevos. Es la técnica que la clase 102 describe y que también usan Scala y las bibliotecas inmutables de JavaScript.
Y hay una tercera idea, muy práctica, que Clojure defiende: usar mapas en lugar de definir clases.
{:nombre "Ana" :edad 30} ; ← no hace falta declarar un tipo Persona
Los datos son datos, y las funciones genéricas de la biblioteca —get, assoc, update,
merge, select-keys— funcionan sobre todos ellos. Es lo contrario del modelado con clases, y
clojure.spec añade la validación donde hace falta en lugar de en todas partes.
clojure.spec y malli: contratos y validación de datos en ejecución, con generación de
casos de prueba a partir de la especificación (clases 118 y 140).deps.edn y la CLI de Clojure, con dependencias declarativas y
fichero de bloqueo (clase 143).core.async: canales y procesos ligeros al estilo de Go (clase 134), implementados con
macros que transforman el código en una máquina de estados.babashka: un intérprete de Clojure con arranque instantáneo compilado con GraalVM, para
guiones de línea de comandos — la respuesta al problema de arranque de la JVM (clase 167).clojure -M main.clj < entrada.txt # el comando de la clase 041
bb main.clj # babashka: arranque en milisegundos
clj -X:test # pruebas
clj-kondo --lint src # análisis estático, muy bueno (clase 146)
(require '[clojure.string :as str])
(let [[precio cantidad descuento] (map read-string (str/split (read-line) #" "))
total (* precio cantidad (- 1 descuento))]
(println (format "Total: %.2f" (double total))))
Lo que hay que ver.
(* precio cantidad ...)— es la de Lisp: el operador
va primero y admite cualquier número de argumentos. Es lo que permite que el código sea una
estructura de datos (clase 123).let con desestructuración [[a b c] ...] liga tres nombres de una vez, y no son variables:
son enlaces que no se reasignan. En todo el programa no hay una sola mutación (clase 102).(double total) no sobra: read-string puede devolver un entero o una fracción exacta
—Clojure tiene racionales, como Smalltalk (clase 045)— y %.2f necesita un doble.
Ese detalle es un buen recordatorio de que el modelo numérico varía mucho entre lenguajes.⏮️ Volver al Atlas · 🗂️ Todas las fichas · 🔗 Relacionadas: Common Lisp · Scheme · Java · Scala · Elixir