- Clojure no es uno de los lenguajes de programación dominantes y, para algunas personas, puede resultar poco familiar
- Principales ventajas de Clojure
- Productividad del desarrollador: Clojure es interactivo, reduce el trabajo repetitivo y ofrece un entorno de desarrollo eficiente. Los desarrolladores pueden lanzar productos rápido y con satisfacción
- Mantenibilidad a largo plazo: el lenguaje Clojure y su ecosistema son maduros y estables. Es posible construir sistemas de alta calidad reduciendo los costos de mantenimiento
- Cultura centrada en las ideas: la comunidad de Clojure explora ideas del pasado y del presente, tanto de la academia como de la industria, para buscar mejores formas de desarrollar software. Les da a los desarrolladores nuevos retos y oportunidades de aprendizaje
(hello 'clojure)
- Clojure es uno de los lenguajes de la familia Lisp, creada en la década de 1950
- Lisp comenzó como un modelo teórico, pero también ofrece una gran elegancia conceptual en la programación práctica
- Como la sintaxis del código coincide con la estructura de los datos, tiene varias ventajas frente a lenguajes con gramáticas más complejas
- Fue uno de los principales lenguajes usados durante el auge anterior de la IA
- Los lenguajes de la familia Lisp han ganado popularidad por períodos y luego la han perdido, pero en los últimos 10 años Clojure ha llamado la atención
- Clojure corre sobre la JVM de Java y refleja conceptos modernos de programación
- Fuerte soporte para estructuras de datos inmutables
- Diseño pensado para la concurrencia
- Aunque creció sin el respaldo de grandes corporaciones y su comunidad es pequeña, tiene características de lenguaje muy potentes
- Clojure puede ejecutarse en distintos entornos
- ClojureScript: se ejecuta compilando a JavaScript
- ClojureCLR: se ejecuta en entornos .NET
- Babashka: intérprete de scripting rápido basado en GraalVM
- Jank: soporte para compilación nativa
- Aprender Clojure tiene la ventaja de permitir reutilizar el mismo conocimiento en varios entornos
Desarrollo interactivo
- Programar es un proceso repetitivo de escribir código y validarlo
- Sin retroalimentación, es difícil tener certeza de que el código funciona correctamente
- Formas comunes de retroalimentación:
- Repetir la ejecución de scripts
- Interacción con la UI y salida de logs
- Uso de pruebas unitarias
- Uso del compilador y herramientas de análisis estático
- La rapidez de la retroalimentación impacta mucho en la productividad del desarrollador
- Mientras más lenta sea, más tiempo se invierte en depurar y menos en escribir código
- Además de las formas tradicionales de retroalimentación, Clojure ofrece desarrollo interactivo (interactive development)
- El núcleo es el desarrollo basado en REPL (Read-Eval-Print Loop)
- Antes de escribir código, el desarrollador ejecuta el runtime de Clojure y lo conecta al editor
- Puede ejecutar fragmentos de código uno por uno y recibir retroalimentación inmediata
- Gracias a la estructura sintáctica consistente de Lisp, es posible ejecutar libremente desde pequeños fragmentos hasta bloques grandes de código
- A diferencia de un REPL común, Clojure puede ejecutar fragmentos de código mientras está conectado a un sistema en ejecución
- Este enfoque ofrece un ciclo de retroalimentación mucho más potente que el clásico “escribir código → ejecutar → depurar”
- Permite modificar y depurar programas en tiempo real
- No es solo un REPL simple, sino una herramienta de interacción poderosa que también puede usarse en entornos de producción
Una cultura que valora la estabilidad
- Elegir Clojure no solo significa obtener una tecnología poderosa, sino también pasar a formar parte de una comunidad con una filosofía y principios particulares
- Si solo se adopta la tecnología sin interactuar con la comunidad, es difícil experimentar el verdadero valor de Clojure
- La comunidad de Clojure considera la estabilidad y la compatibilidad hacia atrás como valores importantes
- El lenguaje central se mejora y amplía de forma continua casi sin cambios que rompan compatibilidad
- El ecosistema open source de Clojure sigue la misma filosofía y minimiza los cambios innecesarios (
churn)
- Esto contrasta con la mayoría de los ecosistemas modernos de lenguajes de programación
- El desperdicio de recursos provocado por cambios innecesarios asciende a decenas de miles de millones de dólares a nivel mundial
- En Clojure, actualizar a la última versión del lenguaje y las librerías es algo muy natural
- Puedes aprovechar correcciones de errores, actualizaciones de seguridad y mejoras de rendimiento sin tener que reescribir la base de código
- En otros lenguajes, código existente puede romperse con una nueva versión; en Clojure, normalmente se mantiene intacto
- Algunos desarrolladores podrían preguntarse: “¿cómo se puede evolucionar sin cambiar?”
- Pero estabilidad y estancamiento no son lo mismo
- Clojure evoluciona agregando y mejorando funciones sin romper las existentes
- Los desarrolladores pueden seguir usando herramientas cada vez mejores sin tener que hacer cambios innecesarios en el código
Sistemas de información y representación del conocimiento
- En el desarrollo web y de aplicaciones de negocio, lo esencial es recopilar, acceder y procesar información
- Sin embargo, muchos lenguajes dominantes muestran diseños ineficientes para representar y manipular información
- Obligan a usar estructuras de datos de bajo nivel, lo que introduce complejidad innecesaria
- Los sistemas de tipos estáticos suelen ser demasiado rígidos, dificultando la manipulación flexible de datos
- Clojure ofrece por defecto estructuras de datos funcionales y poderosas capacidades de manipulación de datos
- Como lenguaje de tipado dinámico, sigue la “Open World Assumption”
- Es una forma de maximizar la extensibilidad y flexibilidad de los datos
- Está muy influenciado por RDF (framework de modelado de datos para la Web Semántica)
- Un ejemplo representativo es la gran sinergia con bases de datos de grafos como Datomic
- Al usar keywords con namespace, es posible asignar significado independiente del contexto
- La estructura de Map en Clojure con keywords con namespace puede expresar significado de forma más refinada que un JSON simple
- Es fácil extender los datos adicionalmente y expresarlos de forma intuitiva evitando colisiones de nombres
Funciones pequeñas y componibles, y datos inmutables
- En Clojure, lo habitual es programar alrededor de funciones puras (pure functions) y datos inmutables (immutable data)
- Se puede escribir código imperativo al estilo Java, Ruby o C, pero el código idiomático de Clojure es muy distinto
- Funciones puras: devuelven resultados basados solo en sus entradas y no modifican estado externo
- Datos inmutables: su significado se basa en valores, no en referencias (
reference) ni identidades de objeto (identity)
- Como no dependen de estado externo, el código es más predecible
- No hay modificaciones de variables globales ni efectos secundarios inesperados (
side effects)
- Como no existe el riesgo de que los datos cambien, es más fácil resolver problemas de procesamiento paralelo y concurrencia
Manejo de concurrencia
- La computación moderna funciona sobre procesadores multinúcleo, y la concurrencia es un elemento indispensable
- A medida que la ley de Moore llega a sus límites, aprovechar el paralelismo es clave para mejorar el rendimiento
- Pero al tratar con estado mutable (mutable state) aparecen problemas de sincronización y control de tiempos complejos
- Clojure enfatiza la inmutabilidad (immutability) para resolver los problemas de concurrencia desde la raíz
- Manipular memoria mutable introduce dependencia del tiempo y del orden
- Las transformaciones puras de datos (data-in, data-out) siempre pueden ejecutarse en paralelo de forma segura
- Clojure aprovecha las capacidades de concurrencia existentes de la JVM (
java.util.concurrent), pero ofrece herramientas de abstracción de más alto nivel
- Atoms: operaciones atómicas con CAS (Compare-and-Set) y reintentos automáticos
- Refs: memoria transaccional por software (Software Transactional Memory, STM)
- Agents: aplicación de actualizaciones de forma asíncrona
- Futures: interfaz fork-and-join basada en thread pools
- También ofrece la librería core.async
- Soporta el patrón CSP (Communicating Sequential Processes), similar a las goroutines de Go
- Se puede comparar con el modelo Actor de Erlang/Elixir o con Akka en Scala
- Incluso sin usar las abstracciones de alto nivel de Clojure, también es posible usar técnicas de control de concurrencia de nivel más bajo
- Soporta colas sincronizadas, referencias atómicas, locks, semáforos, diversos thread pools y gestión manual de hilos
- Si hace falta, es posible un control fino de la concurrencia, pero en la mayoría de los casos usar herramientas abstraídas es más seguro y eficiente
Razonamiento local (Local Reasoning)
- Hay un límite para la complejidad de código que se puede considerar al mismo tiempo
- Cuando el estado del programa y sus cambios ocurren en muchos lugares, entender y mantener el código se vuelve difícil
- Razones por las que Clojure facilita el razonamiento local
- Código centrado en funciones puras (pure function)
- Es posible entender completamente el comportamiento de una función solo con sus entradas
- No hace falta considerar estado externo a la función
- Diferencias frente a los lenguajes orientados a objetos
- Clojure minimiza el polimorfismo (
polymorphism), así que es fácil saber dónde se ejecuta el código
- En la programación orientada a objetos (OOP), “todo ocurre en otra parte”, pero
en Clojure solo hay que seguir las funciones definidas en los namespaces
- Gracias a la estructura sintáctica consistente de Clojure, refactorizar código es sencillo
- El uso de datos inmutables y funciones puras reduce los efectos secundarios inesperados al modificar código
- Al separar por aparte solo la mínima cantidad de código imperativo, es posible combinar armónicamente código imperativo y funcional
Pruebas sencillas
- En código basado en funciones puras de Clojure, se puede probar solo introduciendo valores de entrada y verificando los de salida
- No hacen falta inicializaciones complejas de estado, configuración de objetos mock ni control de timing para las pruebas
- Por eso, las pruebas son más confiables y tienen menos “flakiness” (fallas inconsistentes en pruebas)
- Soporta técnicas avanzadas de testing como Property Based Testing (Generative Testing)
- Genera entradas aleatorias para buscar casos que violen ciertas propiedades o invariantes
- Incluye técnicas de shrinking para encontrar el caso mínimo que falla
- Este concepto nació en Haskell y se implementó en muchos lenguajes mediante frameworks de pruebas basados en QuickCheck
- En Clojure resulta todavía más efectivo por la sinergia con sus estructuras de datos inmutables y el desarrollo basado en REPL
Ventajas al contratar desarrolladores de Clojure
- En general, hay relativamente menos desarrolladores de Clojure que de lenguajes populares como JavaScript o Python
- Pero así como hay pocos desarrolladores de Clojure, tampoco hay tantas empresas que usen Clojure
- Por lo tanto, el equilibrio general entre oferta y demanda se mantiene
- En la práctica, las empresas que buscan desarrolladores de Clojure y los desarrolladores suelen empatar razonablemente bien
- Los desarrolladores de Clojure suelen tener una gran capacidad para resolver problemas
- En lugar de limitarse a aprender tecnologías populares, muchos exploran nuevas formas de pensar e ideas
- De hecho, las empresas que usan Clojure suelen evaluar que hay menos candidatos, pero muchos de alta calidad
- Ejemplo: Nubank formó directamente en Clojure a cientos de desarrolladores en Brasil y dejó un caso exitoso
- Encontrar desarrolladores de Clojure es difícil, pero cuando se encuentra talento adecuado, hay altas probabilidades de incorporar excelentes desarrolladores
- En vez de buscar únicamente gente con experiencia previa en el lenguaje, también es una buena opción formar desarrolladores con alta capacidad de aprendizaje
- La propia comunidad de Clojure tiene la característica de atraer desarrolladores que piensan a fondo los problemas y los resuelven
Trade-offs y ajuste del nivel de abstracción
- Clojure es un lenguaje de alto nivel (high-level) y busca escribir código conciso y expresivo
- Gracias a sus estructuras de datos funcionales (inmutables) y sus poderosas APIs de manipulación de datos, se reduce la complejidad innecesaria
- Las estructuras de datos de Clojure usan internamente árboles (Hash Array Mapped Trie) para garantizar inmutabilidad
- Al actualizar, se produce
path copying, lo que puede aumentar la carga del GC (garbage collection)
- En la interoperabilidad (
interop) con Java también puede haber runtime reflection y boxing/unboxing
- En aplicaciones comunes, este costo suele ser prácticamente despreciable y la ganancia en productividad es grande
- En casos donde se requiere alto rendimiento, como motores gráficos en tiempo real, procesamiento de señales u operaciones numéricas, es posible optimizar a bajo nivel
- En Clojure se pueden usar type hints para eliminar reflection y optimizar operaciones con tipos primitivos
- El uso de arreglos contiguos de tipos primitivos permite aprovechar la caché de CPU
- También se pueden usar librerías con aceleración por GPU
- La mayoría de los lenguajes de alto nivel necesitan extensiones nativas en C/Rust cuando el rendimiento es crítico, pero
Clojure puede resolver la mayoría de los problemas de rendimiento aprovechando las optimizaciones de la JVM (compilación JIT)
- Con profiling y un poco de optimización, es posible maximizar el rendimiento de cosas como event loops
Metaprogramación y APIs centradas en datos
- Como lenguaje de la familia Lisp, Clojure puede tratar el código como si fuera datos
- Similar a JSON, pero con una estructura más fácil de leer para representar programas
- Usa un formato de datos llamado EDN (Extensible Data Notation) que ofrece una representación parecida a JSON
- Clojure ofrece la capacidad de transformar el propio código usando macros
- Sin embargo, la comunidad de Clojure tiene una cultura de limitar cuidadosamente el uso de macros
- Las macros pueden ser difíciles de depurar y menos compatibles con herramientas de análisis estático
- En su lugar, se prefiere un diseño de APIs orientado por datos (data-driven)
- Ejemplo: ruteo HTTP, generación de HTML/CSS, validación de datos, etc.
- En vez de invocar directamente funciones específicas, se especifica el comportamiento usando estructuras de datos (maps, vectores)
- Las APIs basadas en datos pueden manipularse dinámicamente y permiten guardar y modificar fácilmente la configuración del usuario
- Gracias a estas APIs centradas en datos, es posible reconfigurar dinámicamente sistemas en runtime
- Esto facilita implementar sistemas de simulación muy flexibles, gestión dinámica de configuración y metaprogramación
Interoperabilidad con Java (Interop) y aprovechamiento del ecosistema
- El desarrollo moderno de aplicaciones consiste en combinar innumerables librerías open source y APIs
- Como Clojure corre sobre la JVM, puede aprovechar millones de paquetes Java disponibles en Maven Central
- Es posible invocar librerías Java de forma sencilla incluso sin reflection
- En comparación con Java, Clojure tiene código mucho más conciso y facilita la programación experimental con REPL
- Permite explorar y combinar APIs mucho más rápido que en Java
- Con ClojureScript, se puede aprovechar de la misma manera el ecosistema de JavaScript y NPM
Una cultura centrada en las ideas
- Es posible tener proyectos exitosos con cualquier lenguaje, y por el contrario, si se usa mal, también se puede fracasar con cualquier lenguaje
- Una buena herramienta no crea automáticamente buenos desarrolladores
- Clojure tiene una barrera de entrada alta y requiere ensayo y error durante el aprendizaje, pero eso permite pensar con más profundidad
- Algunos proyectos de Clojure todavía arrastran estilos de código tipo Java o Python y no aprovechan bien el lenguaje
- Pero los equipos que adoptan la filosofía y las ideas de Clojure desarrollan mejores capacidades de diseño de software
- La comunidad de Clojure cuestiona constantemente las formas tradicionales de desarrollo y explora métodos mejores
- Las charlas de Rich Hickey (creador de Clojure) no son simples introducciones técnicas, sino una exploración de principios fundamentales del diseño de software
- En las conferencias de Clojure, el foco está más en ideas, análisis de papers y experiencias compartidas que en la presentación de librerías
Conclusión
- Clojure no es solo un lenguaje de programación, sino una comunidad de personas que reflexionan sobre mejores formas de desarrollar software
- En esta comunidad, ampliar los propios límites y explorar nuevas posibilidades es un valor central
- Los desarrolladores que crecen dentro de esta cultura no solo aprenden a usar bien Clojure, sino que se convierten en ingenieros de software con mejores habilidades para resolver problemas
3 comentarios
Personalmente uso Clojure, y coincido mucho con el contenido del artículo.
En el trabajo he usado principalmente Python y Java(Type)Script, pero si no les das aunque sea un poco de mantenimiento, es muy fácil que el propio lenguaje y las librerías cambien y el código se vuelva legado; en cambio, con Clojure me ha dejado muy satisfecho lo fácil que resulta retomar y modificar código que escribí, incluso un año después.
Desde entonces, para uso personal, salvo que haya limitaciones de alguna librería en particular, sigo prefiriendo Clojure.
¡Jank, Jank~!
Opiniones de Hacker News
Si me preguntaran qué tipo de programación he disfrutado, diría que construir pipelines para procesar datos en el shell y escribir Clojure y ClojureScript durante los últimos 5 años
He usado Clojure durante 12 años, y antes de eso usé Java por más de 12 años
Me encanta escribir Clojure, y al compararlo con otros lenguajes me di cuenta de que no necesito explicar ese afecto profundo por Clojure
El cofundador tenía como meta construir la mayor cantidad posible de producto con la menor empresa posible
He operado un negocio SaaS con Clojure durante 10 años, y sin Clojure no habría sido posible
A quienes usan Clojure les recomiendo <a href="https://www.flow-storm.org/">Flow Storm</a>
Aprendí mucho de Rich Hickey y tenía entusiasmo por Clojure y la FP
Hubo comentarios de que la documentación de ClojureDocs está desactualizada, y quería agregar una función para poder votar las respuestas
Me sorprendió la parte sobre la estabilidad de Clojure, porque cada vez que lo probaba cada año sentía que todo había cambiado
Empecé con Common Lisp y luego me pasé a Go y Rust, pero recientemente he vuelto a mirar Clojure