Sortie de Clojure 1.12.0
(clojure.org)- Clojure 1.12.0 conserve le bytecode Java 8, tout en étant annoncé comme la dernière version basée sur Java 8 avant que les prochaines versions ne déplacent la compatibilité Java minimale et la cible de bytecode vers un Java LTS plus récent
- Dans l’environnement de threads virtuels du JDK 21,
lazy-seqetdelayutilisent désormais des verrous au lieu desynchronized, ce qui réduit les cas où des E/S bloquantes immobilisent un thread réel - Dans le REPL, il est possible d’ajouter des bibliothèques avec
add-lib,add-libs,sync-depssans redémarrer la JVM, mais cette fonctionnalité est limitée à un usage interactif en développement - L’interopérabilité Java s’élargit avec l’ajout des valeurs de méthode, de
:param-tags, de la syntaxe des classes de tableaux, de la conversion vers les interfaces fonctionnelles, deSupplieret de fonctions de traitement desStreamJava - Côté performances et compatibilité, la version inclut un spliterator pour
PersistentVector, un traitement plus efficace dedropet des partitions, un durcissement de la politique d’interning des Var, la correction de CVE-2024-22871 et une mise en ordre des identifiants de sérialisation Java
Compatibilité Java 8, sécurité et remise en ordre de la sérialisation
- Les informations de téléchargement et d’utilisation de Clojure 1.12.0 sont disponibles sur la page Downloads
- La base Java 8 est maintenue dans cette version
- Clojure 1.12 génère du bytecode Java 8, comme Clojure 1.10 et 1.11
- Les versions suivantes devraient déplacer le bytecode et la compatibilité Java minimale vers une version Java LTS plus récente
- Le problème d’épinglage des threads virtuels dans le JDK 21 est atténué
- Avant la 1.12,
lazy-seqetdelayexécutaient le code utilisateur à l’intérieur d’un blocsynchronizedpour garantir un comportement n’ayant lieu qu’une seule fois - Avec le JDK 21,
synchronizedne participe pas encore au blocage coopératif, donc si ce code effectue des E/S bloquantes, il peut immobiliser un thread réel - Avec
-Djdk.tracePinnedThreads=full, le JDK 21 peut émettre un avertissement dans cette situation - En 1.12,
lazy-seqetdelayutilisent des verrous à la place des blocssynchronized
- Avant la 1.12,
- La correction de sécurité CVE-2024-22871 est intégrée, avec une alerte associée disponible sur GHSA-vr64-r9qj-h27f
- Le serialVersionUID des classes liées à la sérialisation Java est désormais défini explicitement
- Les types de données Clojure implémentent l’interface de sérialisation Java depuis Clojure 1.0
- La sérialisation Java fonctionne si l’identifiant généré à partir du nom de classe, de la hiérarchie de types et des champs sérialisés correspond lors de la désérialisation
- Clojure ne garantit pas la cohérence de la sérialisation entre versions, mais applique ce changement pour mieux contrôler la compatibilité à l’avenir sans la casser plus que nécessaire
- Les dépendances ont aussi été mises à jour
spec.alphapasse à 0.5.238core.specs.alphapasse à 0.4.74
Fonctions pour gérer bibliothèques et outils dans le REPL
- En développement, il arrive qu’il faille ajouter des bibliothèques sans redémarrer la JVM
- évaluation expérimentale
- ajout de dépendances connues au projet
- ajout d’une bibliothèque pour une tâche précise
- Clojure 1.12 fournit de nouvelles fonctions pour ajouter des bibliothèques sans perdre l’état du REPL
add-lib: télécharge une lib absente du classpath et l’ajoute au classloader- une lib déjà présente dans le classpath n’est pas mise à jour
- si aucune coordonnée n’est fournie, la dernière version Maven ou, si le nom d’un dépôt git peut être déduit, la dernière version ou le dernier tag git est utilisé
add-libs: résout plusieurs nouvelles bibliothèques et versions en une foissync-deps: appelleadd-libspour les libs présentes dansdeps.ednmais pas encore dans le classpath
- Ces fonctions sont conçues uniquement pour un usage du REPL en développement
- la bonne manière de construire et maintenir du code de production reste l’usage de
deps.edn - les trois fonctions vérifient toutes que
*repl*est lié à true clojure.main/repllie automatiquement ce drapeau- dans le REPL
clojure.main, les nouvelles fonctions sont automatiquement référencées dans l’espace de nomsuser - dans d’autres REPL, il peut être nécessaire d’utiliser
(require '[clojure.repl.deps :refer :all])
- la bonne manière de construire et maintenir du code de production reste l’usage de
- La résolution et le téléchargement des bibliothèques sont pris en charge par tools.deps
- pour éviter d’ajouter
tools.depset ses dépendances au classpath du projet pendant le développement, une nouvelle API a aussi été ajoutée afin d’appeler les fonctions via le CLI Clojure dans un processus séparé
- pour éviter d’ajouter
clojure.tools.deps.interop/invoke-toolappelle une fonction d’outil dans un processus séparé- le classpath de l’outil est défini dans
deps.edn - il n’est pas nécessaire d’ajouter les dépendances de l’outil au classpath du projet
- la fonctionnalité
add-liba été construite avecinvoke-tool, et cela peut aussi servir à construire ou invoquer des outils utilisateur de manière interactive - le protocole d’exécution des fonctions est documenté dans la référence CLI
- le classpath de l’outil est défini dans
API d’exécution de processus externes
- En plus de l’espace de noms existant
clojure.java.shell, une nouvelle voie apparaît pour exploiter les API Java récentes d’information, de contrôle des processus et de redirection d’E/S - Clojure 1.12 ajoute le nouvel espace de noms
clojure.java.process- il exploite les nouvelles API Java liées aux processus
- il a été conçu pour être plus simple à utiliser que l’approche existante
- Les fonctions principales sont les suivantes
Extension de l’interopérabilité Java
- Les valeurs de méthode sont ajoutées, ce qui permet d’utiliser plus directement les méthodes Java dans les fonctions d’ordre supérieur
- auparavant, il fallait envelopper manuellement une méthode Java pour la passer à
mapou équivalent - cet emballage manuel était verbeux et pouvait nécessiter des indications pour distinguer les surcharges, ou introduire de la réflexion et du boxing en plus
- désormais, les qualified methods peuvent être utilisées en position de valeur comme des fonctions ordinaires, et le compilateur génère automatiquement une fonction d’enrobage
- si une qualified method ne peut pas être résolue à cause des surcharges, le compilateur génère un appel par réflexion
- les développeurs peuvent indiquer la signature voulue via les métadonnées
:param-tags
- auparavant, il fallait envelopper manuellement une méthode Java pour la passer à
- La syntaxe des qualified methods explicite la classe et la méthode
Classname/method: une valeur de fonction Clojure qui appelle une méthode statiqueClassname/.method: une valeur de fonction Clojure qui appelle une méthode d’instanceClassname/new: une valeur de fonction Clojure qui appelle un constructeur- pour distinguer méthode statique et méthode d’instance, il faut utiliser les syntaxes
Classname/methodetClassname/.method
- Les métadonnées
:param-tagsservent à résoudre les méthodes surchargées- une qualified method utilisée comme valeur ne fournit que la classe et le nom de méthode, ce qui ne permet pas de résoudre une surcharge
:param-tagsest un vecteur de la forme[tag …], où chaque tag correspond à un paramètre de la signature voulue- le placeholder
_peut être utilisé pour les paramètres de types non surchargés - lorsque
:param-tagsest fourni, le compilateur doit pouvoir résoudre une méthode unique à la compilation - la nouvelle syntaxe de lecteur de métadonnées
^[tag …]ajoute des métadonnées:param-tagsà un symbole de membre
- Une syntaxe de classe de tableau est ajoutée
- Clojure prenait déjà en charge les symboles de nom de classe comme valeurs d’objet classe et comme indications de type, mais ne fournissait pas de syntaxe pour les classes de tableaux en dehors des chaînes de caractères
- il est désormais possible de référencer une classe de tableau via un symbole de la forme
ComponentClass/#dimensions - exemples :
String/1,java.lang.String/1,long/2 - la classe composant peut être un nom de classe complet, une classe importée ou un type primitif
- cette syntaxe de classe de tableau peut être utilisée à la fois comme indication de type et comme valeur
- L’interopérabilité avec les interfaces fonctionnelles Java est améliorée
- une interface fonctionnelle Java est annotée
@FunctionalInterfaceet possède une seule méthode - une fonction Clojure peut être transmise à une méthode Java qui attend une interface fonctionnelle si l’arité correspond
- le compilateur Clojure crée un adaptateur lambda pour convertir implicitement la fonction Clojure vers l’interface fonctionnelle nécessaire
- pour éviter de recréer cet adaptateur de manière répétée dans une boucle, on peut forcer explicitement la conversion en ajoutant une indication sur un nom de liaison
let
- une interface fonctionnelle Java est annotée
- L’interopérabilité avec
Supplierest aussi améliorée- auparavant, pour appeler une méthode recevant un
Supplierfournissant une valeur, il fallait écrire un adaptateur viareify - les implémentations Clojure de
IDeref, commedelay,futureetatom, implémentent désormais directement l’interfaceSupplier
- auparavant, pour appeler une méthode recevant un
Traitement des Stream et amélioration des performances des collections
- Des fonctions ont été ajoutées pour consommer à la manière Clojure les
Streamde plus en plus souvent renvoyés par les API Java- avec la prise en charge des interfaces fonctionnelles dans Clojure 1.12, des fonctions d’interopérabilité Stream sont fournies
(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- toutes ces fonctions sont des opérations terminales sur le stream et le consomment
PersistentVectorfournit directement un spliterator utilisé par l’implémentation stream des collections Java- un spliterator est un itérateur fractionnable pour accélérer le parcours parallèle
- le nouveau spliterator personnalisé de
PersistentVectorprend en charge le parallélisme et améliore nettement les performances
- L’efficacité de
drop,nthrest,nthnextet du traitement des partitions est améliorée- CLJ-2713 ajoute une interface interne
IDropindiquant qu’une collection peut effectuer un drop plus efficacement qu’avec une simple itération séquentielle - cette interface est implémentée sur les collections persistantes et sur des collections algorithmiques comme
rangeetrepeat - les nouvelles fonctions
partitionv,partitionv-all,splitv-atsont plus efficaces que leurs équivalents existants et produisent des partitions vectorielles au lieu de partitions de séquences réalisées
- CLJ-2713 ajoute une interface interne
Durcissement de la politique d’interning des Var
- L’interning d’une var dans un espace de noms, contrairement à l’aliasing, consiste à créer une référence stable garantissant que toutes les références obtiennent le même objet
- Il existait auparavant certains cas où une var internée pouvait être remplacée, et la politique a été durcie dans la 1.12.0-alpha1
- lorsqu’une telle situation se produit, un avertissement du type
"REJECTED: attempt to replace interned var #'some-ns/foo with #'other-ns/foo in some-ns, you must ns-unmap first"apparaît
- lorsqu’une telle situation se produit, un avertissement du type
- Cette politique traite la cause racine d’un problème apparu avec l’ajout de nouvelles fonctions à
clojure.coredans Clojure 1.11.0, en particulierabs- si du code compilé avec une version antérieure de Clojure possédait un nom de var identique à celui d’une fonction nouvellement ajoutée à
clojure.core, cette var pouvait devenir non liée lors du chargement sur un runtime 1.11.0 - en plus de CLJ-2711, une correction antérieure dans ce domaine, CLJ-1604, a été annulée
- si du code compilé avec une version antérieure de Clojure possédait un nom de var identique à celui d’une fonction nouvellement ajoutée à
Liste complète des changements
- La liste complète des changements de Clojure 1.12.0 est disponible dans le changelog officiel
1 commentaires
Commentaires sur Hacker News
C’est vraiment une version majeure, avec beaucoup de nouvelles fonctionnalités intéressantes
Personnellement, c’est add-libs que je préfère. On peut désormais créer des démos en un seul fichier ou des exemples minimaux pour reproduire un problème, ce qui réduit fortement la barrière au partage de petits extraits de code exécutables
On peut aussi faire la démonstration de bibliothèques Java sans boilerplate Java. Après avoir expérimenté dans le REPL, il suffit de coller le code quelque part, par exemple dans un commentaire HN, et n’importe qui peut reproduire exactement la même « configuration » et l’exécuter. Pas besoin non plus de cloner un dépôt
Je pensais qu’ils repousseraient cette version jusqu’à Clojure/conj 2024. Je n’avais pas vraiment de raison solide, mais Clojure 1.10 était sorti vers Clojure/conj 2021, et l’annonce de la gratuité de Datomic avait aussi eu lieu au début de Clojure/conj 2023
Cela dit, j’attends toujours spec2. Pour l’instant, je contourne la rigidité de spec avec Malli, mais ce n’est pas un citoyen de première classe dans Clojure. Principalement parce qu’on ne peut pas vérifier les macros, et c’est intentionnel dans la conception du compilateur Clojure. En revanche, en manipulant les schémas Malli comme des données, on peut imiter l’idée de schema/select
Grâce au changement sur les interfaces fonctionnelles, on peut maintenant passer directement des fonctions, sans avoir à maintenir des macros utilitaires comme
(defmacro ->Consumer [f] ...)En fait, Clojure core vérifie aussi les appels de macros avec spec, et quand on les appelle mal, on en voit parfois la trace dans la stack trace. Je me demande si tu voulais dire autre chose
J’apprécie vraiment que le code existant continue de fonctionner tel quel alors qu’il y a tout un tas de nouvelles fonctionnalités. L’effort constant pour éviter les ruptures de compatibilité se voit
Si vous voulez en savoir plus sur Clojure, la conférence Clojure/conj aura lieu du 23 au 25 octobre à Alexandria, en Virginie : https://2024.clojure-conj.org
Je suis content de voir arriver add-libs et sync-deps. Désormais, il semble qu’il n’y ait presque plus, voire plus du tout, de raison de fermer une session
Cette version paraît assez différente des précédentes par son périmètre, et elle est intéressante parce qu’elle contient beaucoup de choses. J’espère simplement qu’avec l’accélération du rythme, cela ne deviendra pas dans quelques versions un amas de fonctionnalités emmêlées
Le changement sur les interfaces fonctionnelles est énorme. Clojure est à son meilleur quand il reste proche de Java grâce à une interopérabilité réfléchie, et ce changement comble une grosse lacune
Je me demande ce qu’il est advenu de spec. Est-ce abandonné ? Je me demande s’il y a des nouvelles à attendre
Cela ressemble à une version plutôt solide, et je suis content de voir que Clojure se porte toujours bien
Je ne cherche pas à troller. J’ai envie de le choisir, et cela semble être une bonne décision d’ingénierie. Mais si la popularité et le nombre de contributeurs sont en forte baisse, cela pourrait devenir un frein dans un avenir proche
[0] https://www.clara-rules.org/
Il n’a jamais été aussi facile de convertir des développeurs existants en développeurs Clojure
Le gros problème au début[0], c’est la lecture du code, et les IA comme ChatGPT ou Claude sont très douées pour expliquer du code Clojure existant. Résultat : l’onboarding des développeurs peut être beaucoup plus rapide
[0] Au bout de quelques semaines, lire du Clojure devient naturel, et on en vient même à oublier qu’on n’arrivait pas à le lire avant
Beaucoup de belles améliorations. C’est généralement le principal langage de la famille Lisp vers lequel je me tourne