2 points par GN⁺ 2024-09-06 | 1 commentaires | Partager sur WhatsApp
  • 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-seq et delay utilisent désormais des verrous au lieu de synchronized, 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-deps sans 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, de Supplier et de fonctions de traitement des Stream Java
  • Côté performances et compatibilité, la version inclut un spliterator pour PersistentVector, un traitement plus efficace de drop et 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-seq et delay exécutaient le code utilisateur à l’intérieur d’un bloc synchronized pour garantir un comportement n’ayant lieu qu’une seule fois
    • Avec le JDK 21, synchronized ne 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-seq et delay utilisent des verrous à la place des blocs synchronized
  • 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.alpha passe à 0.5.238
    • core.specs.alpha passe à 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 fois
    • sync-deps : appelle add-libs pour les libs présentes dans deps.edn mais 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/repl lie automatiquement ce drapeau
    • dans le REPL clojure.main, les nouvelles fonctions sont automatiquement référencées dans l’espace de noms user
    • dans d’autres REPL, il peut être nécessaire d’utiliser (require '[clojure.repl.deps :refer :all])
  • La résolution et le téléchargement des bibliothèques sont pris en charge par tools.deps
    • pour éviter d’ajouter tools.deps et 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é
  • clojure.tools.deps.interop/invoke-tool appelle 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-lib a été construite avec invoke-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

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
    • start : permet un contrôle complet des flux et l’accès aux objets Java sous-jacents pour les usages avancés
    • exec : couvre le cas courant où l’on exécute un processus externe et récupère stdout à la fin

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 à map ou é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
  • La syntaxe des qualified methods explicite la classe et la méthode
    • Classname/method : une valeur de fonction Clojure qui appelle une méthode statique
    • Classname/.method : une valeur de fonction Clojure qui appelle une méthode d’instance
    • Classname/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/method et Classname/.method
  • Les métadonnées :param-tags servent à 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-tags est 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-tags est 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 @FunctionalInterface et 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
  • L’interopérabilité avec Supplier est aussi améliorée
    • auparavant, pour appeler une méthode recevant un Supplier fournissant une valeur, il fallait écrire un adaptateur via reify
    • les implémentations Clojure de IDeref, comme delay, future et atom, implémentent désormais directement l’interface Supplier

Traitement des Stream et amélioration des performances des collections

  • Des fonctions ont été ajoutées pour consommer à la manière Clojure les Stream de 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
  • PersistentVector fournit 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 PersistentVector prend en charge le parallélisme et améliore nettement les performances
  • L’efficacité de drop, nthrest, nthnext et du traitement des partitions est améliorée
    • CLJ-2713 ajoute une interface interne IDrop indiquant 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 range et repeat
    • les nouvelles fonctions partitionv, partitionv-all, splitv-at sont plus efficaces que leurs équivalents existants et produisent des partitions vectorielles au lieu de partitions de séquences réalisées

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
  • Cette politique traite la cause racine d’un problème apparu avec l’ajout de nouvelles fonctions à clojure.core dans Clojure 1.11.0, en particulier abs
    • 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

Liste complète des changements

  • La liste complète des changements de Clojure 1.12.0 est disponible dans le changelog officiel

1 commentaires

 
GN⁺ 2024-09-06
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 ne sais pas s’il reste des gens qui se souviennent de Groovy. Groovy dispose d’une annotation @Grab, qui fait pratiquement la même chose que add-libs tel que décrit, et qui est très pratique pour écrire des scripts
    • Java dispose désormais aussi d’un REPL et d’une prise en charge du scripting, mais une fonctionnalité comme add-libs ne fait pas encore partie des méta-commandes disponibles
  • 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 voyant “maybe not”, j’ai eu vraiment besoin de schema/select, donc j’ai créé une bibliothèque pour Malli : https://github.com/eval/malli-select
    • Je me demande ce que veut dire « on ne peut pas vérifier les macros ». Avec spec, on peut associer s/fdef à une macro, et les appels sont vérifiés à la compilation
      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

    • Rich Hickey et la Clojure Team sont des concepteurs très prudents, donc je pense qu’il n’y a pas trop de quoi s’inquiéter
    • Pure spéculation, mais comme c’est la première version depuis que Rich Hickey a quitté nubank, il a peut-être pu y consacrer davantage d’attention
  • 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

    • spec existe toujours et est utilisé. Beaucoup de travail de suivi a également été fait, mais c’est en pause pendant qu’ils examinent quoi faire sur plusieurs sujets
  • Cela ressemble à une version plutôt solide, et je suis content de voir que Clojure se porte toujours bien

    • Je me demande si ça se porte vraiment bien. Je suis en train de l’évaluer pour un nouveau projet, notamment avec Clara[0]. Cela dit, ce n’est plus aussi mainstream qu’avant, et l’écosystème me paraît plus clairsemé qu’auparavant
      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