Durante su charla en Clojure South, Alex Miller, Ingeniero Principal de Software en Nubank, abordó un problema que está en el corazón del trabajo diario de todo desarrollador: las formas tan dolorosas en las que solemos manejar los datos. Nuestra industria está plagada de formatos frágiles, objetos cargados de código repetitivo (como getters y setters) y las constantes complejidades del estado mutable que exigen capa tras capa de protección. Esta fricción no es solo deuda técnica; es un impuesto a nuestra creatividad y a nuestro entusiasmo.

Clojure ofrece una alternativa poderosa a este paradigma. Coloca los datos simples e inmutables y las funciones puras en el núcleo mismo de su filosofía. Se trata de una forma de pensar el software fundamentalmente distinta, más allá de ser solo una sintaxis diferente. Al adoptar este modelo, podemos construir modelos mentales más limpios, más matemáticos e increíblemente poderosos. Este enfoque, como demostró Alex, conduce a sistemas que no solo son más robustos, sino también más gratificantes de crear.

El camino hacia esta forma de programar más simple y potente comienza por repensar una palabra que usamos todos los días: «valor».

Volviendo a lo básico: ¿qué es realmente un «valor»?

Aunque la programación implica manipular diversas entidades, el poder de Clojure proviene de una definición rigurosa de los «valores». Esta distinción está lejos de ser una trivialidad semántica; es el principio de diseño central que le otorga al lenguaje su elegancia característica. En este contexto, un valor representa una categoría específica de datos, definida por propiedades inmutables que cambian la forma en que razonamos sobre el estado.

Según Alex, para que algo sea considerado un verdadero valor, debe poseer cuatro características clave:

  • Inmutable: Un valor no se puede cambiar. El número 100 no se convierte mágicamente en 50. Una vez creado, es constante para siempre.
  • Comparable: Puedes tomar dos valores y determinar si son iguales. Esta propiedad permite ordenar, indexar y razonar sobre la igualdad de forma directa.
  • Compartible: Un valor se puede pasar a otro hilo de ejecución, enviar por una red o escribir en un disco y volver a leerlo sin temor a que sea alterado. Su integridad está garantizada.
  • Preciso: Para ser compartible y comparable, un valor debe tener una representación concreta y precisa; en última instancia, como una secuencia específica de bits.

Esta definición aclara qué no es un valor. Cosas como los sockets de red, los manejadores de archivos (file handles), los streams y los objetos mutables no pasan esta prueba.

«Esas cosas no son valores… No puedes compararlas ni escribirlas con precisión».

Alex Miller, Ingeniero Principal de Software en Nubank

Son procesos o recursos, no hechos. Aquí existe una distinción sutil pero importante: la ruta de un archivo (file path) puede tratarse como un valor, ya que es una descripción inmutable y precisa. Sin embargo, un manejador de archivos, que representa una conexión activa a un recurso cambiante, no puede serlo.

Los beneficios de la inmutabilidad

Adoptar la inmutabilidad para todos los valores, incluyendo las colecciones, tiene consecuencias profundas que simplifican enormemente el diseño de sistemas:

Sin necesidad de bloqueos (locks) para lectura: Dado que un valor nunca cambia, puedes leerlo desde cualquier número de hilos sin necesidad de bloqueos o sincronización.

Compartir sin miedo: Puedes pasar estructuras de datos libremente entre procesos concurrentes o sistemas distribuidos, con la confianza de que estás compartiendo información y no una fuente potencial de errores.

Caché e historial sin esfuerzo: El almacenamiento en caché se vuelve trivial cuando los datos nunca cambian. También permite funciones potentes como pilas de «deshacer» (undo) o depuración de «viaje en el tiempo», ya que simplemente puedes mantener referencias a estados anteriores.

Este diseño no está exento de costos; es una elección de ingeniería consciente hecha para obtener beneficios inmensos.

«Estas son propiedades asombrosas. No son gratis… pero la inmutabilidad definitivamente vale el precio que pagas por ella».

Alex Miller, Ingeniero Principal de Software en Nubank

Esta poderosa filosofía no es solo para números individuales o cadenas de texto; en Clojure, se extiende a las colecciones mismas que estructuran nuestros datos.

Check our job opportunies

La base fundamental: simplicidad en cuatro colecciones principales

Si Clojure South tuvo un protagonista silencioso, fue el humilde mapa. Esto no es casualidad. Clojure rechaza deliberadamente un ecosistema disperso de tipos de colecciones en favor de una base pequeña y poderosa de cuatro estructuras fundamentales. Esta elección estratégica de priorizar un conjunto mínimo de estructuras de datos fomenta una reutilización y claridad inmensas.

Clojure se basa en cuatro tipos de colecciones primarias, cada una con un propósito estructural distinto:

  • Listas: Colecciones secuenciales donde los valores se agregan eficientemente al principio.
  • Vectores: Colecciones secuenciales indexadas donde puedes acceder rápidamente a cualquier elemento por su posición.
  • Mapas: Colecciones desordenadas de asociaciones clave-valor.
  • Conjuntos (Sets): Colecciones desordenadas de valores únicos.

Lo que hace que estas colecciones sean tan poderosas no es solo que son inmutables, sino que todas son accesibles a través de un vocabulario único y universal de funciones. En muchos lenguajes orientados a objetos, «cada clase inventa un nuevo lenguaje». Para obtener el nombre de un usuario, llamas a user.getFirstName(); para el total de un pedido, order.getTotal(). Esta es una fuente importante de fricción: los desarrolladores deben cambiar de contexto constantemente, aprendiendo un nuevo mini-protocolo para cada objeto que encuentran.

Clojure rechaza esta complejidad personalizada. En su lugar, proporciona un conjunto unificado de funciones —get, keys, vals, seq, count— que funcionan con todos los datos. Este vocabulario común reduce drásticamente la carga cognitiva. Como señaló Alex Miller, una vez que:

«Aprendes a usar map, filter y remove, ya sabes cómo navegar por todo tu conjunto de datos».

Alex Miller, Ingeniero Principal de Software en Nubank

Las mismas herramientas funcionan en todas partes, haciendo que los datos se sientan cercanos, transparentes y fáciles de transformar. Este lenguaje común para manipular datos es la llave que desbloquea el algoritmo central, y notablemente simple, de Clojure para resolver problemas.

El algoritmo central de la programación

En el corazón mismo de la charla, Alex presentó la diapositiva clave: un algoritmo simple de dos pasos que resume toda la filosofía de desarrollo de Clojure. Es una idea tan poderosa que puede transformar fundamentalmente la manera en que uno aborda la construcción de software.

«Existe un algoritmo muy simple para programar en Clojure: representa tu dominio como datos y escribe funciones que transformen esos datos. Eso es todo».

Alex Miller, Ingeniero Principal de Software en Nubank

Este modelo de dos pasos de «datos entran, datos salen» es profundamente eficaz por varias razones:

  • Predecible y testeable: Las funciones puras que operan sobre valores inmutables son la base de la predictibilidad. Dado el mismo aporte (input), siempre producirán el mismo resultado (output), lo que hace que sea trivial razonar sobre ellas, probarlas de forma aislada y depurarlas.
  • Composable (Combinable): Las funciones puras y pequeñas son como piezas de Lego. Pueden combinarse y componerse en funciones más grandes y complejas que conservan las mismas propiedades deseables de predictibilidad y capacidad de prueba.
  • Seguro para la concurrencia: Al eliminar el estado mutable compartido, este modelo evita toda una clase de errores de concurrencia notoriamente difíciles. No existen condiciones de carrera (race conditions) ni bloqueos mutuos (deadlocks) cuando las funciones simplemente están transformando datos inmutables.
  • Estable y evolutivo: Los sistemas construidos de esta manera son notablemente estables y fáciles de evolucionar. Agregar una nueva funcionalidad a menudo solo significa agregar una nueva clave a un mapa. Las funciones existentes que no conocen la nueva clave seguirán funcionando sin verse afectadas en absoluto. Esto permite que los sistemas crezcan por adición en lugar de por modificación, reduciendo el riesgo de romper el código existente.

Esto puede sonar elegante en teoría, pero su verdadero poder se vuelve evidente cuando se aplica a problemas del mundo real.

De listas de tareas a la música: datos en acción

Para demostrar el algoritmo central en la práctica, Alex presentó una serie de ejemplos diversos, mostrando cómo cualquier dominio puede ser modelado como datos y manipulado mediante funciones.

Una lista de tareas simple

Una tarea no es un objeto con métodos; es simplemente un mapa que contiene claves como :task (tarea), :errand (recado) y :duration (duración). El consejo de Miller aquí es una piedra angular del diseño pragmático en Clojure:

«Ten siempre una capa de datos base que llegue al fondo y usa mapas descriptivos; me lo agradecerás después».

Alex Miller, Ingeniero Principal de Software en Nubank

A partir de esta base, surge un patrón común: escribir una función pequeña para manejar un solo elemento y luego otra para manejar la colección. La tarea de transformar elementos de una lista en HTML sigue esto a la perfección, con una función que convierte un solo mapa en una estructura de datos de Hiccup, y una segunda que aplica esa función (mapping) sobre una colección. Este énfasis en funciones pequeñas de propósito único es omnipresente. Como observó Miller: «no es raro mirar un código base de Clojure y ver que… el tamaño promedio de una función es de cinco líneas».

El Juego de la Vida de Conway

La cuadrícula infinita del Juego de la Vida de Conway se representa elegantemente no con una matriz masiva bidimensional de ceros y unos, sino con una representación dispersa: un conjunto (set) simple que contiene las coordenadas [x, y] de las celdas «vivas». Esta elección de modelado de datos es increíblemente poderosa. Toda la lógica del juego se convierte en un flujo (pipeline) de funciones puras que recibe un conjunto de coordenadas como entrada y produce el siguiente conjunto como salida, implementando las reglas del juego con claridad matemática.

Gráficos y música como datos

El patrón se extiende incluso a dominios creativos. Un círculo en una pantalla puede representarse como un mapa con las claves :x, :y, :radius y :color. Del mismo modo, una nota musical puede ser un mapa con :pitch (tono), :octave (octava) y :duration. Al modelar estas entidades como datos, Alex Miller pudo construir un mini-sintetizador en el escenario, utilizando una secuencia de mapas para tocar el icónico tema de cinco notas de «Encuentros cercanos del tercer tipo».

En cada caso, la solución no se encontró diseñando jerarquías de clases complejas, sino preguntándose primero: «¿Cuál es la estructura de datos más simple que puede representar este problema?», y luego aplicando funciones simples y combinables para transformarla.

Si bien este modelo cubre la gran mayoría de las tareas de programación, Clojure también proporciona un enfoque basado en principios para esos raros momentos en los que los valores realmente necesitan cambiar con el tiempo.

Gestionando cambios: un enfoque basado en principios para el estado

Inevitablemente, algunas partes de una aplicación necesitan cambiar. La sesión de un usuario, una conexión de base de datos, el estado de un componente de interfaz; todo esto requiere gestionar cambios a lo largo del tiempo. Para estos escenarios, Clojure ofrece una respuesta clara que evita el caos de la mutabilidad no gestionada. El objetivo es crear una «identidad lógica estable que tendrá diferentes valores inmutables a lo largo del tiempo».

Clojure ofrece un conjunto pequeño de constructos de estado para este fin, principalmente atoms, refs y agents. En lugar de mutar directamente el valor que contienen, se les proporciona una función pura que calcula el próximo estado a partir del actual. Este diseño significa que los desarrolladores no realizan bloqueos manuales (locking); las lecturas siempre están disponibles sin coordinación y las escrituras son gestionadas de forma segura por el entorno de ejecución (runtime) de Clojure.

Lo más revelador es cómo esto se aplica en la práctica. Cuando se creó Clojure, su sistema de Memoria Transaccional de Software (STM), construido alrededor de las refs, era visto como una característica fundamental. Sin embargo, tras años de uso en producción, surgió una verdad sorprendente. Como observó Miller, los desarrolladores descubrieron que «es posible sobrevivir con una cantidad increíblemente pequeña de estado en tu sistema». El enfoque disciplinado, que prioriza los datos, conduce naturalmente a arquitecturas donde la gran mayoría del código es puro y sin estado, reforzando la filosofía central del lenguaje.

Conclusión: construir sobre la roca, no sobre la arena

El mensaje central de la charla de Alex es un llamado al retorno a la simplicidad. La filosofía es clara: construye sistemas sobre una base de valores inmutables y funciones puras, y gestiona el estado solo cuando sea absolutamente necesario. Este enfoque da como resultado programas más fáciles de entender, evolucionar y mantener.

Miller concluyó con una metáfora poderosa que captura perfectamente la diferencia entre este enfoque y los paradigmas convencionales, cargados de estados mutables. Seguir esta filosofía es como «construir sobre la roca en lugar de construir sobre la arena».

La estabilidad y la claridad que surgen de esta base no son solo logros técnicos; son la fuente de una verdadera satisfacción profesional. Esto permite que los desarrolladores se concentren en resolver problemas en lugar de luchar contra la complejidad accidental de sus herramientas. La ovación de pie que recibió Alex no fue solo por una charla bien ejecutada, sino por articular una filosofía que devuelve la alegría al oficio de la programación.

Check our job opportunies