Pourquoi nous avons choisi le langage de programmation Clojure pour Penpot


  • FrançaisFrançais


  • « Pourquoi Clojure ? » est probablement la question qui nous a été le plus posée à Penpot. Nous avons une vague explication sur notre page FAQ, donc avec cet article, je vais vous expliquer les motivations et l’esprit derrière notre décision.

    Tout a commencé dans un PIWEEK. Bien sûr!

    Lors d’un de nos Semaines de l’Innovation Personnelle (PIWEEK) en 2015, une petite équipe a eu l’idée de créer un outil de prototypage open source. Ils ont immédiatement commencé à travailler et ont pu sortir un prototype fonctionnel après une semaine de travail acharné (et beaucoup de plaisir). Il s’agissait du premier prototype statique, sans backend.

    Je ne faisais pas partie de l’équipe initiale, mais il y avait de nombreuses raisons de choisir ClojureScript à l’époque. Construire un prototype en seulement une semaine de hackathon n’est pas facile et ClojureScript a certainement aidé à cela, mais je pense que la raison la plus importante pour laquelle il a été choisi était parce que c’est amusant. Il offrait un paradigme fonctionnel (dans l’équipe, il y avait beaucoup d’intérêt pour les langages fonctionnels).

    Il a également fourni un environnement de développement entièrement interactif. Je ne parle pas de l’actualisation automatique du navigateur après la compilation. Je veux dire rafraîchir le code lors de l’exécution sans faire de rafraichissement de page et sans perdre d’état ! Techniquement, vous pourriez développer un jeu et modifier le comportement du jeu pendant que vous y jouez encore, simplement en touchant quelques lignes dans votre éditeur. Avec ClojureScript (et Clojure), vous n’avez besoin de rien d’extraordinaire. Les constructions de langage sont déjà conçues avec le rechargement à chaud à l’esprit dès le départ.

    (Andrey Antukh, CC BY-SA 4.0)

    Je sais qu’aujourd’hui (en 2022), vous pouvez également avoir quelque chose de similaire avec React sur JavaScript brut, et probablement avec d’autres frameworks que je ne connais pas. Là encore, il est également probable que ce support de capacité soit limité et fragile, en raison des limitations intrinsèques du langage, des modules scellés, de l’absence d’un REPL approprié.

    À propos de REPL

    Dans d’autres langages dynamiques, comme JavaScript, Python, Groovy (et ainsi de suite), les fonctionnalités REPL sont ajoutées après coup. En conséquence, ils ont souvent des problèmes avec le rechargement à chaud. Les modèles de langage conviennent au code réel, mais ils ne conviennent pas au REPL (par exemple, un const en JavaScript évalue le même code deux fois dans un REPL).

    Ces REPL sont généralement utilisés pour tester des extraits de code créés pour le REPL. En revanche, l’utilisation de REPL dans Clojure implique rarement de taper ou de copier directement dans le REPL, et il est beaucoup plus courant d’évaluer de petits extraits de code à partir des fichiers source réels. Ceux-ci sont souvent laissés dans la base de code sous forme de blocs de commentaires, vous pouvez donc réutiliser les extraits dans le REPL lorsque vous modifiez le code à l’avenir.

    Dans le Clojure REPL, vous pouvez développer une application entière sans aucune limitation. Le Clojure REPL ne se comporte pas différemment du compilateur lui-même. Vous pouvez effectuer toutes sortes d’introspections d’exécution et de remplacement à chaud de fonctions spécifiques dans n’importe quel espace de noms d’une application déjà en cours d’exécution. En fait, il n’est pas rare de trouver des applications backend dans un environnement de production exposant REPL sur un socket local pour pouvoir inspecter le runtime et, si nécessaire, corriger des fonctions spécifiques sans même avoir à redémarrer le service.

    Programmation et développement

    Du prototype à l’application utilisable

    Après PIWEEK en 2015, Juan de la Cruz (designer chez Penpot et auteur original de l’idée du projet) et moi avons commencé à travailler sur le projet pendant notre temps libre. Nous avons réécrit l’ensemble du projet en utilisant toutes les leçons tirées du premier prototype. Début 2017, nous avons sorti en interne ce que l’on pourrait appeler le deuxième prototype fonctionnel, cette fois avec un backend. Et le fait est que nous utilisions toujours Clojure et ClojureScript !

    Les raisons initiales étaient toujours valables et pertinentes, mais la motivation d’un investissement en temps aussi important révèle d’autres raisons. C’est une très longue liste, mais je pense que les caractéristiques les plus importantes de toutes étaient : la stabilité, la rétrocompatibilité et l’abstraction syntaxique (sous la forme de macros).

    (Andrey Antukh, CC BY-SA 4.0)

    Stabilité et rétrocompatibilité

    La stabilité et la rétrocompatibilité sont l’un des objectifs les plus importants du langage Clojure. Il n’y a généralement pas beaucoup de précipitation pour inclure tous les trucs à la mode dans la langue sans avoir testé sa réelle utilité. Il n’est pas rare de voir des personnes exécuter la production sur une version alpha du compilateur Clojure, car il est rare d’avoir des problèmes d’instabilité même sur les versions alpha.

    Dans Clojure ou ClojureScript, si une bibliothèque n’a pas de commits pendant un certain temps, c’est très probablement bien tel quel. Il n’a pas besoin d’être développé davantage. Cela fonctionne parfaitement, et il ne sert à rien de changer quelque chose qui fonctionne comme prévu. Au contraire, dans le monde JavaScript, lorsque vous voyez une bibliothèque qui n’a pas eu de commits depuis plusieurs mois, vous avez tendance à avoir l’impression que la bibliothèque est abandonnée ou non maintenue.

    Il y a de nombreuses fois où j’ai téléchargé un projet JavaScript qui n’a pas été touché depuis 6 mois pour constater que plus de la moitié du code est déjà obsolète et non maintenu. A d’autres occasions, il ne compile même pas car certaines dépendances n’ont pas respecté le versioning sémantique.

    C’est pourquoi chaque dépendance de Penpot est soigneusement choisi, dans un souci de continuité, de stabilité et de rétrocompatibilité. Beaucoup d’entre eux ont été développés en interne. Nous déléguons à des bibliothèques tierces uniquement lorsqu’elles ont prouvé qu’elles avaient les mêmes propriétés, ou lorsque le rapport effort/temps de le faire en interne n’en valait pas la peine.

    Je pense qu’un bon résumé est que nous essayons d’avoir le minimum de dépendances externes nécessaires. React est probablement un bon exemple d’une grande dépendance externe. Au fil du temps, il a montré qu’ils avaient un réel souci de rétrocompatibilité. Chaque version majeure intègre les changements progressivement et avec un chemin clair pour la migration, permettant à l’ancien et au nouveau code de coexister.

    Abstractions syntaxiques

    Une autre raison pour laquelle j’aime Clojure est ses abstractions syntaxiques claires (macros). C’est une de ces caractéristiques qui, en règle générale, peut être une arme à double tranchant. Vous devez l’utiliser avec précaution et ne pas en abuser. Mais avec la complexité de Penpot en tant que projet, avoir la possibilité d’extraire certaines constructions communes ou verbeuses nous a aidés à simplifier le code. Ces déclarations ne peuvent pas être généralisées et la valeur éventuelle qu’elles apportent doit être considérée au cas par cas. Voici quelques exemples importants qui ont fait une différence significative pour Penpot :

    • Lorsque nous avons commencé à construire Penpot, React n’avait que des composants en tant que classe. Mais ces composants ont été modélisés comme des fonctions et des décorateurs dans un rumext bibliothèque. Lorsque React a publié des versions avec des hooks qui amélioraient considérablement les composants fonctionnels, nous n’avions qu’à changer l’implémentation de la macro et 90% des composants de Penpot pouvaient rester inchangés. Par la suite, nous sommes progressivement passés des décorateurs aux crochets sans avoir besoin d’une migration laborieuse. Cela renforce la même idée des paragraphes précédents : stabilité et rétrocompatibilité.
    • Le deuxième cas le plus important est la facilité d’utilisation des constructions de langage natif (vecteurs et cartes) pour définir la structure du DOM virtuel, au lieu d’utiliser un DSL personnalisé de type JSX. L’utilisation de ces constructions de langage natif ferait qu’une macro finirait par générer les appels correspondants à React.createElement au moment de la compilation, laissant encore de la place pour des optimisations supplémentaires. De toute évidence, le fait que la langue soit orientée vers l’expression la rend d’autant plus idiomatique.

    Voici un exemple simple en JavaScript, basé sur des exemples de la documentation React :

    function MyComponent({isAuth, options}) {
        let button;
        if (isAuth) {
            button = <LogoutButton />;
        } else {
            button = <LoginButton />;
        }

        return (
            <div>
              {button}
              <ul>
                {Array(props.total).fill(1).map((el, i) =>
                  <li key={i}>{{item + i}}</li>
                )}
              </ul>
            </div>
        );
    }

    Voici l’équivalent en ClojureScript :

    (defn my-component [{:keys [auth? options]}]
      [:div
       (if auth?
         [:& logout-button {}]
         [:& login-button {}])
       [:ul
        (for [[i item] (map-indexed vector options)]
          [:li {:key i} item])]])

    Toutes ces structures de données utilisées pour représenter le DOM virtuel sont converties en React.createElement appels au moment de la compilation.

    Le fait que Clojure soit tellement orienté données a fait de l’utilisation des mêmes structures de données natives du langage pour représenter le DOM virtuel un processus naturel et logique. Clojure est un dialecte de LISP, où la syntaxe et l’AST du langage utilisent les mêmes structures de données et peuvent être traités avec les mêmes mécanismes.

    Pour moi, travailler avec React via ClojureScript semble plus naturel que de travailler avec JavaScript. Tous les outils supplémentaires ajoutés à React pour l’utiliser confortablement, tels que JSX, les structures de données immuables ou les outils pour travailler avec les transformations de données et la gestion des états, ne sont qu’une partie du langage ClojureScript.

    Langue de l’invité

    Enfin, l’un des fondamentaux de Clojure et ClojureScript est qu’ils ont été construits comme Langues invitées. Autrement dit, ils fonctionnent au-dessus d’une plate-forme ou d’un environnement d’exécution existant. Dans ce cas, Clojure est construit au-dessus de la JVM et ClojureScript au-dessus de JavaScript, ce qui signifie que l’interopérabilité entre le langage et le runtime est très efficace. Cela nous a permis de tirer parti de tout l’écosystème de Clojure ainsi que de tout ce qui se fait en Java (il en va de même pour ClojureScript et JavaScript).

    Il existe également des morceaux de code qui sont plus faciles à écrire lorsqu’ils sont écrits dans des langages impératifs, comme Java ou JavaScript. Clojure peut coexister avec eux dans la même base de code sans aucun problème.

    Il existe également une facilité de partage de code entre le frontend et le backend, même si chacun peut s’exécuter dans un environnement d’exécution complètement différent (JavaScript et JVM). Pour Penpot, presque toute la logique la plus importante pour gérer les données d’un fichier est écrite en code et exécutée à la fois dans le frontend et dans le backend.

    Peut-être pourriez-vous dire que nous avons choisi ce que certains appellent une technologie “ennuyeuse”, mais sans que ce soit vraiment ennuyeux du tout.

    Compromis

    De toute évidence, chaque décision a des compromis. Le choix d’utiliser Clojure et ClojureScript n’est pas une exception. D’un point de vue commercial, le choix de Clojure pourrait être considéré comme risqué car ce n’est pas un langage courant, il a une communauté relativement petite par rapport à Java ou JavaScript, et trouver des développeurs est intrinsèquement plus compliqué.

    Mais à mon avis, la courbe d’apprentissage est beaucoup plus faible qu’il n’y paraît à première vue. Une fois que vous vous êtes débarrassé du c’est différent peur (ou comme je l’appelle en plaisantant : peur des parenthèses), vous commencez à maîtriser la langue très rapidement. Il existe des tonnes de ressources d’apprentissage, y compris des livres et des cours de formation.

    Le véritable obstacle que j’ai remarqué est le changement de paradigme, plutôt que la langue elle-même. Avec Penpot, la complexité nécessaire et inhérente au projet fait du langage de programmation le cadet de nos soucis face au développement : construire une plateforme de conception n’est pas une mince affaire.


    Cet article est initialement paru sur le Blog de Kaleidos et a été republié avec autorisation.

    Source

    N'oubliez pas de voter pour cet article !
    1 Star2 Stars3 Stars4 Stars5 Stars (No Ratings Yet)
    Loading...

    La Rédaction

    L'équipe rédactionnnelle du site

    Pour contacter personnellement le taulier :

    Laisser un commentaire

    Votre adresse e-mail ne sera pas publiée.