Pourquoi Jepsen est écrit en Clojure
(aphyr.com)- Jepsen est un outil destiné à vérifier des systèmes concurrents comme les bases de données ; le modèle de concurrence du langage d’implémentation et sa prise en charge réelle des threads étaient donc des critères de choix importants
- Les structures de données immuables et persistantes de Clojure, ainsi que les outils de concurrence de la JVM, réduisent le risque d’erreurs dans le code de test et s’accordent bien avec les clients de bases de données basés sur Java
- Le travail sur Jepsen relève davantage de l’expérimentation et de l’exploration que du développement produit cadré ; des fonctionnalités de Clojure comme le REPL, les macros, les threading macros et EDN sont avantageuses pour traiter des données complexes
- Les performances ne sont pas parmi les meilleures, mais du Clojure idiomatique est généralement plus lent que Java d’un facteur inférieur à un ou deux ordres de grandeur ; les points critiques peuvent être traités par l’optimisation et les outils de profiling de la JVM
- Clojure a des faiblesses, comme une petite communauté, l’absence d’un système de typage statique largement adopté et éprouvé, une gestion peu pratique des primitives et des messages d’erreur médiocres, mais pour Jepsen, maintenu et utilisé par 1 à 3 personnes, c’était un compromis acceptable
Les critères d’un langage adapté à un outil de test de concurrence
- Jepsen a été créé pour tester des systèmes concurrents, dont la plupart sont des bases de données
- Clojure répond bien aux conditions de base nécessaires pour écrire du code de test de concurrence
- Les structures de données immuables et persistantes réduisent le risque d’erreurs dans le code qui manipule un état partagé
- Il permet d’utiliser de vrais threads, des promise, future, atom, lock, queue, cyclic barrier,
java.util.concurrent, etc. - Des langages comme Haskell, au contrôle des effets de bord plus strict, ont aussi été envisagés, mais l’approche moins dogmatique de Clojure convenait mieux à Jepsen
- Les tests de bases de données nécessitent de se connecter à de nombreux clients différents
- Presque toutes les bases de données fournissent généralement un client JVM écrit en Java
- Clojure offre une bonne interopérabilité avec Java, ce qui facilite l’utilisation de ces clients
Des fonctionnalités de Clojure favorables à l’expérimentation et à l’exploration de données
- Les tests Jepsen étant un travail expérimental, il fallait un langage concis, facile à modifier et adapté au prototypage
- La syntaxe concise de Clojure et son système de macros correspondent bien à cette manière de travailler
- Les threading macros rendent les transformations en chaîne plus lisibles
- Les macros facilitent l’implémentation d’une gestion des erreurs réutilisable et du contrôle de la portée des ressources
- Le REPL de Clojure est utile pour explorer à la volée les données produites par les exécutions de tests
- Pendant les tests, il faut représenter, transformer et inspecter des structures de données complexes et imbriquées
- Les structures de données de Clojure et les fonctions de sa bibliothèque standard sont particulièrement adaptées à ce travail
- Lorsqu’il faut produire beaucoup de données structurées dans la console et dans des fichiers, la syntaxe de données EDN convient bien
Compromis entre performances et maintenance à long terme
- Jepsen n’est pas un très grand système, mais il manipule une quantité raisonnable de données et a donc besoin de performances « suffisamment bonnes »
- Clojure n’est pas le langage le plus rapide
- Le Clojure idiomatique reste généralement dans un écart de performance inférieur à un ou deux ordres de grandeur par rapport à Java
- Sur les goulets d’étranglement importants, cet écart peut être réduit
- Les outils de profiling de la JVM fonctionnent bien avec Clojure
- Jepsen étant un projet vieux d’environ 10 ans, la maturité du noyau et la stabilité étaient importantes
- Clojure est stable, tant du point de vue de sa cible JVM que du langage lui-même
- Ses bibliothèques ne « pourrissent » pas aussi vite que celles de Scala ou Ruby
Les faiblesses de Clojure à accepter
- Clojure présente des inconvénients gérables à l’échelle de Jepsen, mais qui pourraient peser sur des équipes plus grandes
- Sa communauté d’ingénierie est réduite
- Il n’existe pas de système de typage statique largement accepté et réussi
- Comme Jepsen n’est maintenu et utilisé que par 1 à 3 personnes à la fois, cette contrainte pèse moins lourd
- La manipulation des primitives JVM peut être frustrante si l’on ne redescend pas en Java, et Java est effectivement utilisé de temps à autre
- Certaines parties du système de polymorphisme sont insuffisantes, mais on peut les contourner avec des bibliothèques ; les messages d’erreur sont également mauvais
- Jepsen a été prototypé dans plusieurs langages avant que Clojure soit retenu, et dix ans plus tard, ce choix reste un assez bon compromis
1 commentaires
Avis sur Hacker News
Ayant exploité un SaaS en Clojure/ClojureScript pendant une dizaine d’années, je suis entièrement d’accord avec l’article de Kyle
Ce qui a aussi eu beaucoup de valeur, c’est de pouvoir placer la majeure partie du code métier dans des fichiers cljc compilables à la fois côté serveur et côté client ; le fait que les transducers et les pipelines de transducers soient sous-estimés en matière de performance, de composabilité et de réutilisabilité ; et, comme on peut s’y attendre d’un langage conçu par Rich Hickey, son excellente stabilité et sa compatibilité à long terme
La rétrocompatibilité est très importante : elle permet de se concentrer sur l’application plutôt que de subir des outils qui changent sans cesse. Et je ne pense pas que ce soit un hasard si le programmeur Clojure moyen a plus de 12 ans d’expérience et se situe dans le quartile supérieur de rémunération
Ça casse très rarement et les mises à niveau sont faciles. Grâce au HMR, au REPL et aux fichiers cljc, l’expérience développeur est excellente, et écrire du code Clojure est plaisant
On trouve acceptable de tordre les langages et les systèmes pour éviter de casser ce qui fonctionne, mais je ne comprends pas pourquoi, pour deux langages qui soutiennent une grande partie des logiciels effectivement en production, on attendrait qu’ils rompent avec leur passé glorieux et leur présent
Le plus gros problème de Clojure, c’est clairement son écosystème
Si l’on regarde la répartition des outils accessibles et utilisables, on trouve soit des outils tout à gauche, atroces mais faciles d’accès, soit des outils tout à droite, avec une citation du Seigneur des anneaux dans le README, où tout est des données jusqu’au n-ième degré et qui ne sont accessibles qu’à des dragons capables d’écrire un plugin Emacs les yeux bandés et suspendus la tête en bas
Le langage lui-même est excellent, et l’interopérabilité avec Java est bien meilleure que ce qu’en dit Aphyr, mais s’il existait pour Clojure quelque chose comme Rails ou Django — ou mieux, Phoenix — il serait beaucoup plus facile à défendre et probablement bien plus largement utilisé
En particulier, pour convaincre une équipe venant de langages impératifs de la famille Algol de passer à Lisp, il faut que les gains dépassent les difficultés ; or aujourd’hui, en dehors de briques de base comme le routage HTTP, il faut réinventer la roue dans presque chaque projet
Cela dit, pouvoir utiliser n’importe quel package Maven et compiler en jar est un énorme avantage : partout où un jar peut tourner, Clojure peut tourner aussi
https://calva.io
Clojure existe sous plusieurs formes ; on utilise surtout Clojure et ClojureScript, avec aussi Babashka, Scittle, etc., mais tous dérivent de clj ou de cljs
Les outils de développement dépendent de la plateforme et des préférences d’IDE : Emacs, Neovim, VSCode et IntelliJ ont chacun leurs outils, et le système de build varie aussi selon les besoins
Pour clj, deps.edn est aujourd’hui le plus utilisé, suivi de leiningen ; pour cljs, il existe plusieurs options en plus de shadow-cljs
Cela peut être intimidant pour quelqu’un qui découvre, mais la communauté est bonne et aide toujours. J’ai programmé professionnellement dans plusieurs langages, mais Clojure est de loin celui que je préfère
J’utilise Clojure depuis environ 2013 et aucun outil de ce genre ne me vient à l’esprit. La plupart des bibliothèques populaires sont excellentes et, en matière de stabilité, de rétrocompatibilité et de simplicité, elles sont très en avance sur la plupart des écosystèmes que je connais
Grâce aux fichiers source .cljc partageables entre Clojure et ClojureScript, le même code Clojure peut s’exécuter à la fois sur le front-end et le back-end
Comme le dit Rich Hickey, il n’y a pas que le JVM qui « porte » loin : JavaScript aussi
J’utilise Emacs depuis longtemps et je peux le configurer comme je veux, ou finir par écrire le code elisp dont j’ai besoin, mais je ne prétends absolument pas être bon en elisp
J’admets qu’apprendre simultanément Emacs, elisp et Clojure quand on n’a jamais appris aucun dialecte de Lisp est difficile, et très difficile à vendre
Aujourd’hui, il existe un serveur LSP Clojure, qui fonctionne non seulement avec Emacs mais aussi avec d’autres IDE ; même si beaucoup de développeurs Clojure utilisent Emacs, Emacs n’est donc pas indispensable pour développer en Clojure
J’utilise exclusivement Clojure depuis 7 ans et je travaille activement sur une base de code Clojure + ClojureScript de plus de 500 000 lignes ; ma plus grande frustration est l’absence d’un système de types solide
Je suis d’accord pour dire que Clojure est expressif, permet d’avancer vite et ne se met pas en travers du chemin, mais il autorise aussi des erreurs d’exécution désordonnées, difficiles à reproduire et à déboguer
Si Clojure disposait d’un système de typage statique largement accepté et réussi, ce serait incroyable. En l’état, je le vois comme un très bon choix si l’on aime Ruby ou Python tout en voulant quelque chose de plus robuste, mais je me demande désormais si Go n’est pas la bonne voie
Clojure(Script) est vraiment une bénédiction, et je suis tout à fait d’accord pour dire que c’est un excellent choix pour de nombreux projets, petits comme grands.
Après avoir un peu utilisé C, Python, PowerShell, Java, Bash et SQL pour des devoirs universitaires, de l’administration système ou l’analyse de dumps de trafic, le premier langage dans lequel j’ai fait de la programmation professionnelle a été Clojure.
Nous construisons OrgPad en Clojure+ClojureScript, et même avec plus de 100 000 lignes de code, je pense toujours que c’était une bonne décision.
C’est accessible, et après avoir écrit un peu de code, j’ai assez confiance dans le fait qu’il se comportera comme je l’imagine. Je n’avais pas ce niveau avec Python, PowerShell ou Bash ; j’ai l’impression que c’est d’un tout autre ordre.
Le workflow avec REPL est aussi vraiment excellent, et un collègue a fait une vidéo à ce sujet : https://www.youtube.com/watch?v=4igO7Qbyj9o
Aujourd’hui, même des fonctions, petites tâches ou petits projets pour lesquels on hésiterait à passer de Bash à Python peuvent être traités avec Babashka/nbb.
Quand j’ai essayé autrefois d’écrire quelques milliers de lignes en Clojure et ClojureScript, le plus gros problème était que la plupart des outils avaient des états d’erreur assez brouillons.
Python a un côté similaire : si l’on interrompt la plupart des programmes Python avec Ctrl+C, on obtient une trace de pile mêlée de choses incompréhensibles, mais quand on débute, cela rendait difficile le fait d’avancer.
Je pense que le langage et l’écosystème contiennent beaucoup d’idées innovantes et intéressantes. À tout le moins, cela vaut la peine de lire l’article historique sur Clojure et d’en intérioriser la plupart des leçons.
Cela paraîtra hérétique à tout le monde, mais je pense qu’il existe une petite place dans l’écosystème pour un langage de script aussi sérieux que Clojure sur les performances et l’ergonomie d’écriture du code, mais sans parenthèses.
On pourrait aussi se passer de l’usage consistant à traiter nil de façon spéciale, même s’il y a au moins un certain fondement à cela.
Je sais que
(f x y)est mieux quef(x, y), mais mon problème se situe plutôt dans les cas où les formes let provoquent de l’indentation. Je ne pense pas qu’il faille payer à la fois le coût de l’indentation et celui des parenthèses simplement pour donner un nom à une valeur.Quand on vient d’un langage comme Python, où l’indentation qu’on met de toute façon dans le code porte de l’information, ces choses peuvent parfois ressembler à du bruit.
Dans un style fonctionnel, on n’a pas souvent besoin de nommer les arguments intermédiaires passés entre fonctions.
En général, on passe implicitement les arguments à la fonction suivante avec les macros de threading
->et->>, ou bien on définit la transformation elle-même comme une fonction pure au niveau supérieur.Cela dit, je comprends que certains codes d’interopérabilité aient beaucoup besoin de let. C’est notamment le cas des bibliothèques Java JWT qui modifient les arguments dans des chaînes procédurales.
Par exemple, après avoir longtemps débogué, on se rend compte qu’on n’a pas rechargé ou réinitialisé.
J’ai l’impression que, la plupart du temps, cela finit par s’ajouter à la liste des choses à vérifier dans les situations du type « ce n’est pas possible ». Par exemple : « ajoutons une sortie de debug pour vérifier que mon changement de code est réellement appelé ».
Bien sûr, ce n’est qu’un filet de sécurité, et on essaie surtout d’éviter ce genre de situation avec d’autres habitudes de travail au REPL.
(+ clojure clojurescript).Clojure fait beaucoup de choses correctement, et c’est pour cela que je l’aime beaucoup.
Une chose qui ralentit de vraies équipes lorsqu’elles construisent de vrais projets web en Clojure, selon moi, c’est ce désir presque dogmatique de tout assembler en combinant une multitude de bibliothèques, chacune avec sa propre façon de faire.
Si la communauté disposait, en plus de son approche actuelle, d’un framework web activement maintenu, batteries included et utilisable de manière générale, Clojure serait probablement bien plus populaire et agréable à utiliser. Je me demande si Biff joue ce rôle.
Ce serait bien d’avoir une voie alternative pour les gens qui aiment le langage, mais n’aiment pas assembler eux-mêmes un monstre.
C’est aussi dû au fait que Ruby on Rails m’a habitué à autre chose.
Comme dans la célèbre conférence de Rich Hickey : ne pas complect, mais composer : (https://github.com/matthiasn/talk-transcripts/blob/master/Hi...)
Apprendre Clojure, une base de données graphe et parfois HTMX, cela fait beaucoup. Cela dit, beaucoup de configuration est déjà faite, l’authentification est soignée et simple, et les commentaires des fichiers de configuration serveur sont bien rédigés.
Il y a aussi des scripts de déploiement pour configurer un serveur Linux et se connecter en SSH au REPL de production. Si vous voulez utiliser XTDB, la manière dont Biff configure la validation des documents est vraiment très bonne.
Personnellement, je préfère cette approche aux applications qui tournent avec ClojureScript. Il existe beaucoup de wrappers ClojureScript autour de React, c’est presque devenu un mème, mais en tant que développeur React, aucun ne m’a semblé plus simple que React.
Au contraire, cela me paraît complexe et peu naturel. En revanche, HTMX/Hyperscript s’accorde très bien avec Clojure, car la syntaxe HTML n’est au fond que des tableaux et objets faciles à manipuler.
La maxime répétée par les amateurs de Clojure, « le code est de la donnée », m’a semblé réelle pour la première fois.
Il y a quelque temps, j’ai essayé plusieurs fois de me mettre un peu à OCaml, mais son système de types excessivement strict ne me plaisait pas
Je passais plus de temps à satisfaire le compilateur qu’à écrire du code. En Clojure, Common Lisp et C, j’ai davantage de liberté pour plier le compilateur ou le système de types dans le sens que je veux
Bien sûr, il y a des avantages et des inconvénients, mais je n’ai pas envie d’être interrompu quand j’écris du code. C’est peut-être juste mon style, mais j’ai rencontré beaucoup de gens qui pensent pareil
Je pense que les systèmes de types statiques sont une affaire de goût, et le camp d’en face pense autrement. Cela dit, c’est une bonne chose qu’il y ait des camps opposés, car ils apprennent beaucoup l’un de l’autre
Rich Hickey a dit plusieurs fois que Clojure étant un langage hôte, il n’y a pas de problème à descendre vers Java ou JavaScript quand c’est nécessaire
Personnellement, je vois cela comme l’une des fonctionnalités les plus fortes de l’écosystème Clojure. C’est bien plus pragmatique
J’ai vu beaucoup de tentatives de réimplémenter encore et encore la même chose dans un langage qu’on aime, simplement parce qu’on ne veut pas toucher à « cette bibliothèque écrite dans cet autre langage crasseux ». Les raisons varient : c’est lent, dangereux, pas assez flexible, etc.
Cela s’est souvent produit, et se produit encore, aussi bien en Common Lisp que dans la communauté Rust
SBCL garantit ces déclarations autant que possible, et m’a probablement épargné quelques minutes de prise de tête par projet
Et CL est très pragmatique : par exemple, personne ne sourcille parce que woo s’appuie sur une bibliothèque C
Tous les problèmes d’exploitation étaient liés à de petits détails subtils. Par exemple, une mise à jour du runtime changeait l’ordre des clés JSON et cassait de vieux clients qui désérialisaient le JSON à la main, ou une bibliothèque profondément enfouie dans la pile commençait à renvoyer les codes HTTP sous forme d’entiers plutôt que de chaînes, ce qui cassait l’analyse des erreurs
À notre échelle d’exploitation, même si seule une petite fraction des clients était touchée, cela représentait des centaines de signalements ; chaque panne était donc un gros problème
Aujourd’hui je travaille dans une petite startup, et c’est totalement différent. J’ai choisi un langage statique parce que c’est là que se concentrent l’attention et les effectifs, mais l’époque où je travaillais avec des langages dynamiques me manque
Comme on publie vite, la qualité du code se dégrade vraiment. Par manque de temps, au lieu d’affronter le système de types et de refactorer proprement, on copie plusieurs structures d’objets presque identiques mais légèrement différentes et on les utilise partout
Comme la forme des types rend la généralisation délicate, on copie-colle la même logique à 10 endroits au lieu d’isoler la fonctionnalité et de la référencer à plusieurs endroits
Je pense que la bonne solution dépend du domaine du problème. Si des bugs subtils vous valent des appels au milieu de la nuit, le typage statique strict est probablement le bon choix
Si vous devez itérer vite et qu’un problème peut se résoudre par un e-mail ou un message Slack, le typage dynamique peut être préférable
L’astuce est de ne pas accumuler trop de dette dans le paradigme dynamique, afin d’être prêt le jour où la croissance impose de passer au paradigme statique
Les langages qui ont des fans très enthousiastes ont malheureusement tendance, me semble-t-il, à dériver vers l’attitude « je ne veux pas utiliser de bibliothèques d’autres langages »
Clojure est de loin mon langage préféré, et il me semble naturel
Cela dit, il y a deux choses qui me manquent vraiment dans d’autres langages : des messages d’erreur compréhensibles et des indications de types
J’ai dû refactorer une grosse base de code au travail, et ça a été douloureux. Il est difficile de savoir quelle fonction va lancer une exception ou simplement renvoyer nil, et tout aussi difficile de savoir si une fonction centrale va renvoyer un vecteur, une séquence ou une autre collection
Il existe des projets qui tentent de résoudre cela, comme spec, malli et typed clojure, mais je n’ai pas eu l’impression qu’ils constituaient des substituts proches des types de Python ou de TypeScript
Un langage qui ne peut pas vous dire ce qui entre dans une fonction et ce qui en sort me semble presque sans valeur dans une base de code de plus de quelques fichiers
Essayer d’inférer le type à partir de ce que le code actuel semble pouvoir faire avec un objet, c’est s’infliger de la douleur soi-même
Si vous découvrez Clojure, vous pouvez consulter ce tutoriel interactif : https://tryclojure.org
Un meilleur ensemble d’apprentissage interactif, qui va des niveaux faciles aux niveaux intermédiaires puis difficiles
Clojure est amusant
Rails a dominé le développement web dans les années 2010, et beaucoup de startups extrêmement prospères ont été bâties dessus
Beaucoup de gens se moquent des ingénieurs qui « jouent avec des jouets », mais une bonne partie des belles choses dont nous disposons aujourd’hui ont un jour été des jouets. Linux en est aussi un bon exemple
Ce qu’il y a de délicat avec un langage très « amusant », c’est qu’on finit par supposer qu’on peut tout résoudre avec lui
Au final, on écrit beaucoup de code, alors qu’avec le recul, ne pas écrire de code aurait peut-être été une solution parfaitement acceptable
[1] https://franz.hamburg/writing/clojure-makes-happy.html