- Clojure 1.12.0 mantiene el bytecode de Java 8, y se anticipa como la última versión basada en Java 8 antes de que futuras releases muevan la compatibilidad mínima con Java y la referencia de bytecode al Java LTS más reciente
- En el entorno de hilos virtuales de JDK 21,
lazy-seqydelayusan locks en lugar desynchronized, reduciendo los casos en que I/O bloqueante fija hilos reales - En el REPL se pueden agregar bibliotecas con
add-lib,add-libsysync-depssin reiniciar la JVM, pero esta función está limitada al uso interactivo durante el desarrollo - La interoperabilidad con Java se amplía con valores de método,
:param-tags, sintaxis de clases de arreglos, conversión de interfaces funcionales,Suppliery funciones para procesarStreamde Java - En rendimiento y compatibilidad se incluyen spliterator para
PersistentVector, procesamiento más eficiente dedrop/particiones, una política más estricta de interning de Vars, corrección de CVE-2024-22871 y ordenamiento de identificadores de serialización de Java
Compatibilidad con Java 8, seguridad y ordenamiento de serialización
- La información de descarga y uso de Clojure 1.12.0 está disponible en la página de Downloads
- La base de Java 8 se mantiene también en esta release
- Clojure 1.12 genera bytecode de Java 8, igual que Clojure 1.10 y 1.11
- Las siguientes releases planean mover el bytecode y la compatibilidad mínima con Java a una versión Java LTS más reciente
- Se mitiga el problema de fijación de hilos virtuales en JDK 21
- Antes de 1.12,
lazy-seqydelayejecutaban código del usuario dentro de un bloquesynchronizedpara garantizar un comportamiento de ejecución única - En JDK 21,
synchronizedtodavía no participa en el bloqueo cooperativo, por lo que si ese código realiza I/O bloqueante puede fijar un hilo real - Al usar
-Djdk.tracePinnedThreads=full, JDK 21 puede emitir advertencias sobre esta situación - En 1.12,
lazy-seqydelayusan locks en lugar de bloquessynchronized
- Antes de 1.12,
- La corrección de seguridad incorpora CVE-2024-22871, y la advertencia relacionada está en GHSA-vr64-r9qj-h27f
- Se configuran explícitamente los serialVersionUID de las clases relacionadas con serialización de Java
- Los tipos de datos de Clojure han implementado la interfaz de serialización de Java desde Clojure 1.0
- La serialización de Java requiere que, al deserializar, coincida un identificador generado a partir del nombre de clase, la jerarquía de tipos y los campos serializables
- Clojure no garantiza consistencia de serialización entre versiones, pero aplica este cambio para tener más control en el futuro sin romper compatibilidad más de lo necesario
- También se actualizan dependencias
spec.alphase actualiza a 0.5.238core.specs.alphase actualiza a 0.4.74
Funciones para manejar bibliotecas y herramientas en el REPL
- Durante el desarrollo hay casos en los que se necesita agregar bibliotecas sin reiniciar la JVM
- evaluación experimental
- agregar dependencias conocidas al proyecto
- agregar bibliotecas para tareas específicas
- Clojure 1.12 ofrece nuevas funciones para agregar bibliotecas sin perder el estado del REPL
add-lib: descarga una lib que no está en el classpath y la agrega al classloader- no actualiza libs que ya están en el classpath
- si no hay coordenadas, puede inferir la versión más reciente de Maven o, si puede inferir el nombre del repositorio git, usar la versión o etiqueta git más reciente
add-libs: resuelve varias bibliotecas nuevas y sus versiones en conjuntosync-deps: llama aadd-libspara libs que están endeps.ednpero todavía no en el classpath
- Estas funciones están pensadas solo para uso del REPL durante el desarrollo
- la forma correcta de construir y mantener código de producción sigue siendo usar
deps.edn - las tres funciones verifican que
*repl*esté enlazado a true clojure.main/replenlaza esta bandera automáticamente- en el REPL de
clojure.main, las nuevas funciones se hacenreferautomáticamente en el namespaceuser - en otros REPL puede ser necesario
(require '[clojure.repl.deps :refer :all])
- la forma correcta de construir y mantener código de producción sigue siendo usar
- La resolución y descarga de bibliotecas queda a cargo de tools.deps
- para evitar meter
tools.depsy sus dependencias en el classpath del proyecto durante el desarrollo, también se agrega una nueva API para invocar funciones mediante Clojure CLI en un proceso separado
- para evitar meter
clojure.tools.deps.interop/invoke-toolinvoca funciones de herramientas en un proceso separado- el classpath de la herramienta se define en
deps.edn - no hace falta agregar dependencias de herramientas al classpath del proyecto
- la función
add-libfue construida usandoinvoke-tool, y también puede usarse para construir o invocar herramientas de usuario de forma interactiva - el protocolo de ejecución de funciones puede consultarse en la CLI reference
- el classpath de la herramienta se define en
API para ejecutar procesos externos
- Además del namespace existente
clojure.java.shell, ahora hay una vía que aprovecha la nueva API de Java para información, control y redirección de I/O de procesos - Clojure 1.12 agrega el nuevo namespace
clojure.java.process- aprovecha la nueva API de procesos de Java
- está diseñado para ser más fácil de usar que el enfoque anterior
- Las funciones principales son las siguientes
Ampliación de la interoperabilidad con Java
- Se agregan valores de método, lo que permite usar métodos de Java de forma más directa en funciones de orden superior
- antes, para pasar un método Java a
mapu otras funciones, había que envolverlo manualmente en una función - ese wrapping manual era verboso, podía requerir hints para distinguir sobrecargas y podía introducir reflexión o boxing innecesarios
- ahora los qualified methods pueden usarse en posición de valor como funciones normales, y el compilador genera automáticamente una función envoltorio
- si un qualified method no puede resolverse por sobrecarga, el compilador genera una llamada reflexiva
- los desarrolladores pueden indicar la firma deseada con metadatos
:param-tags
- antes, para pasar un método Java a
- La sintaxis de qualified method especifica clase y método
Classname/method: valor de función de Clojure que invoca un método estáticoClassname/.method: valor de función de Clojure que invoca un método de instanciaClassname/new: valor de función de Clojure que invoca un constructor- para distinguir métodos estáticos de métodos de instancia, debe usarse la sintaxis
Classname/methodyClassname/.method
- Los metadatos
:param-tagsse usan para resolver métodos sobrecargados- un qualified method usado como valor solo proporciona la clase y el nombre del método, por lo que no puede resolver métodos sobrecargados
:param-tagses un vector con forma[tag …], donde cada tag corresponde a un parámetro de la firma deseada- se puede usar el placeholder
_para parámetros de tipos no sobrecargados - al proporcionar
:param-tags, el compilador debe poder resolverlo a un único método en tiempo de compilación - la nueva sintaxis de lector de metadatos
^[tag …]adjunta metadatos:param-tagsa un símbolo de miembro
- Se agrega sintaxis de clases de arreglos
- Clojure ya soportaba símbolos con nombres de clase como valores de objeto Class y como type hints, pero no ofrecía sintaxis de clase de arreglo fuera de strings
- ahora se puede referenciar una clase de arreglo con un símbolo de la forma
ComponentClass/#dimensions - ejemplos:
String/1,java.lang.String/1,long/2 - la clase componente puede ser un nombre de clase completo, una clase importada o un primitive
- la sintaxis de clase de arreglo puede usarse tanto como type hint como valor
- Mejora la interoperabilidad con interfaces funcionales de Java
- una interfaz funcional de Java tiene
@FunctionalInterfacey un solo método - una función de Clojure puede pasarse a una llamada de método Java que recibe una interfaz funcional si coincide la aridad
- el compilador de Clojure crea un adaptador lambda para convertir implícitamente la función de Clojure en la interfaz funcional requerida
- para evitar crear adaptadores repetidamente dentro de un loop, se puede forzar explícitamente agregando un hint al nombre enlazado con
let
- una interfaz funcional de Java tiene
- También mejora la interoperabilidad con
Supplier- antes, para llamar métodos que reciben un
Supplierde valores, había que escribir un adaptador conreify - las implementaciones de
IDerefde Clojure, comodelay,futureyatom, ahora implementan directamente la interfazSupplier
- antes, para llamar métodos que reciben un
Mejoras en procesamiento de Stream y rendimiento de colecciones
- Se agregan funciones para consumir al estilo Clojure los
Streamque las APIs de Java devuelven cada vez más- junto con el soporte para interfaces funcionales de Clojure 1.12, se ofrecen funciones de interoperabilidad con Stream
(stream-seq! stream) ⇒ seq(stream-reduce! f [init-val] stream) ⇒ val(stream-transduce! xf f [init-val] stream) ⇒ val(stream-into! to-coll [xf] stream) ⇒ to-coll- todas son operaciones terminales sobre streams y consumen el stream
PersistentVectorahora ofrece directamente un spliterator usado en la implementación de stream de colecciones Java- un spliterator es un iterador divisible para recorridos paralelos más rápidos
- el nuevo spliterator personalizado de
PersistentVectorsoporta paralelismo y mejora mucho el rendimiento
- Mejora la eficiencia de
drop,nthrest,nthnexty del procesamiento de particiones- CLJ-2713 agrega la interfaz interna
IDrop, que indica que una colección puede hacerdropde forma más eficiente que con recorrido secuencial - esta interfaz se implementa en colecciones persistentes y colecciones algorítmicas como
rangeyrepeat - las nuevas funciones
partitionv,partitionv-allysplitv-atson más eficientes que sus equivalentes existentes y generan particiones en vector en lugar de particiones de seq materializadas
- CLJ-2713 agrega la interfaz interna
Política más estricta de interning de Vars
- Hacer interning de un var en un namespace, a diferencia del aliasing, crea una referencia estable para que todas las referencias obtengan el mismo objeto
- Antes había algunos casos en que un var internado podía ser reemplazado, y en 1.12.0-alpha1 la política se volvió más estricta
- cuando esto ocurre, aparece una advertencia con la forma
"REJECTED: attempt to replace interned var #'some-ns/foo with #'other-ns/foo in some-ns, you must ns-unmap first"
- cuando esto ocurre, aparece una advertencia con la forma
- Esta política aborda la causa raíz de un problema que se hizo visible cuando Clojure 1.11.0 agregó nuevas funciones a
clojure.core, en particularabs
Lista completa de cambios
- La lista completa de cambios de Clojure 1.12.0 está en el changelog oficial
1 comentarios
Opiniones de Hacker News
Es realmente un lanzamiento grande y trae muchas funciones nuevas geniales.
Personalmente, lo que más me gusta es add-libs. Ahora se pueden crear demos de un solo archivo o ejemplos mínimos para reproducir issues, lo que reduce mucho la barrera para compartir pequeños fragmentos de código ejecutables.
También se pueden hacer demos de librerías Java sin el boilerplate de Java. Después de probar cosas en el REPL, uno puede pegar el código en lugares como un comentario de HN, y cualquiera puede reproducir y ejecutar exactamente la misma “configuración”. Ni siquiera hace falta clonar un repositorio.
Pensé que iban a postergar este lanzamiento hasta Clojure/conj 2024. No tenía ninguna base concreta, pero Clojure 1.10 salió cerca de Clojure/conj 2021 y el anuncio de que Datomic pasaba a ser gratuito también fue a comienzos de Clojure/conj 2023.
De todos modos, todavía estoy esperando spec2. Por ahora estoy esquivando la rigidez de spec con Malli, pero no es un ciudadano de primera clase en Clojure. Principalmente porque no se pueden inspeccionar macros, y eso es una parte intencional del diseño del compilador de Clojure. Aun así, si se manipulan los esquemas de Malli como datos, se puede imitar la idea de schema/select.
Gracias al cambio en las interfaces funcionales, ahora se puede pasar una función directamente sin tener que mantener macros utilitarias como
(defmacro ->Consumer [f] ...).De hecho, Clojure core también inspecciona llamadas a macros con spec, y a veces se puede ver ese rastro en el stack trace cuando se las llama mal. Me pregunto si se refería a otra cosa.
Me encanta que, aun con un montón de funciones nuevas, el código existente siga funcionando igual. Se nota el esfuerzo constante por evitar romper la compatibilidad.
Si quieren conocer más sobre Clojure, vale la pena ver la conferencia Clojure/conj, que se realizará del 23 al 25 de octubre en Alexandria, Virginia: https://2024.clojure-conj.org
Me alegra que hayan llegado add-libs y sync-deps. Ahora parece que casi no hay, o directamente no hay, motivos para tener que cerrar una sesión.
Este lanzamiento parece bastante distinto en alcance respecto de los anteriores, y es interesante por la cantidad de cosas que incluye. Eso sí, espero que por la mayor velocidad no termine convirtiéndose en una bola enredada dentro de algunos lanzamientos.
El cambio de interfaces funcionales es enorme. Clojure está en su mejor versión cuando se mantiene cerca de Java mediante una interoperabilidad cuidadosa, y este cambio cubre un hueco importante.
Me pregunto qué pasó con spec. ¿Lo abandonaron? Me gustaría saber si hay alguna noticia para esperar.
Parece un lanzamiento bastante sólido, y me alegra que Clojure siga andando bien.
No intento trolear. Quiero elegirlo, y desde el punto de vista de ingeniería parece una buena decisión. Pero si su popularidad y sus contribuidores están cayendo fuerte, eso podría convertirse en un obstáculo en el futuro cercano.
[0] https://www.clara-rules.org/
Convertir desarrolladores existentes en desarrolladores Clojure es más fácil que nunca.
El gran problema inicial[0] es leer el código, y las IA como ChatGPT o Claude son muy buenas explicando código Clojure existente. Como resultado, el onboarding de desarrolladores puede ser mucho más rápido.
[0] Después de unas semanas, leer Clojure se vuelve natural, y hasta uno se olvida de que antes no podía leerlo.
Muchas mejoras geniales. Normalmente es el principal lenguaje de la familia Lisp al que recurro.