🇫🇷 Ce que j'aime en Elm

L'arrivée en 2012 de Elm sur la scène des langages de programmation s'est faite en toute discrétion. Ce langage dédié aux interfaces et applications web et compilant en JavaScript trouve progressivement ses utilisateurs, sans pourtant être autant médiatisé que les alternatives JavaScript telles que React, AngularJS et Vue.js (qui ont bien souvent la puissance marketing d'un GAFAM derrière eux).

Développeur JavaScript depuis de nombreuses années lorsque j'ai découvert Elm, j'ai immédiatement été séduit par le langage mais également par son écosystème. Je vais essayer dans cet article de vous partager certains de ces points.

TL;DR : Parcourez les titres des différentes parties 😉

Logo Elm

La simplicité du langage

Elm est simple. Le langage fait une seule chose et le fait bien : des interfaces et applications web. Très spécialisé, il contient donc le minimum d'éléments pour accomplir à bien sa mission et rien d'autre. Les dernières versions du langage ont d'ailleurs supprimé certains éléments jugés redondants, inutiles ou trop complexes.

Les concepteurs n'hésitent pas à réécrire d'importantes parts du langage si de nouvelles manières de faire semblent plus intuitives et plus logiques. Cela provoque évidemment d'importants breaking changes entre les versions, mais cela n'est pas vraiment pénalisant lors des montées de version puisque des scripts automatisant la plus grosse partie du processus sont fournis.

L'absence de rupture de compatibilité est l'une des grandes forces de JavaScript : un site web qui fonctionne aujourd'hui est censé toujours fonctionner correctement dans 15 ans ! C'est également malheureusement sa plus grande faiblesse : dès qu'une fonctionnalité est introduite dans le langage, elle l'est ad vitam aeternam ! Conçu à l'époque pour un web très différent du web que nous connaissons aujourd'hui, le langage comporte aujourd'hui de nombreux défauts (très facilement utilisés par les détracteurs du langage) qui sont malheureusement voués à y rester.

Mais Elm est compilé, et c'est là toute sa force ! Un code qui fonctionne aujourd'hui fonctionnera toujours dans 15 ans si on le compile avec la même version, puisqu'elle produira le même code JavaScript ! Et cela n'empêche pas les versions suivantes de corriger leurs erreurs ! On peut donc facilement retirer des éléments du langage, gardant sa taille minimale.

Un des effets indirects est qu'il y a en général une seule façon d'effectuer quelque chose et le développeur n'a donc pas à peser les pour et les contre de chacune des alternatives. En JavaScript au contraire, on a souvent plusieurs façons d’atteindre le même but, par exemple pour la gestion de l'asynchrone (callbacks, promises, observables, async / await, ...).

Selon mon expérience, un développeur n'ayant jamais fait de Elm est plus rapidement productif sur une codebase Elm qu'un développeur JavaScript sur un nouveau framework.

La simplicité des outils

Encore une fois, l'accent est mis sur la facilité pour les nouveaux arrivants. En installant Elm, l'utilisateur bénéficie de toute une gamme d'outils :

  • un starter de projet pour initialiser rapidement un nouveau projet Elm

  • un REPL (une console interactive) pour exécuter du code Elm directement dans un terminal

  • un gestionnaire de paquets pour installer des modules et les publier

  • un environnement de développement complet capable de compiler le code à la volée

  • le compilateur du langage

Et chacun de ces outils est vraiment intuitif et facile à utiliser, notamment parce qu'ils ne nécessitent aucune configuration et fonctionnent ensemble. Ils sont d'ailleurs pensés pour les humains ; voyons par exemple ce que retourne un elm init:

➜ project elm init
Hello! Elm projects always start with an elm.json file. I can create them!

Now you may be wondering, what will be in this file? How do I add Elm files to my project? How do I see it in the browser? How will my code grow? Do I need more directories? What about tests? Etc.

Check out <https://elm-lang.org/0.19.0/init> for all the answers!

Knowing all that, would you like me to create an elm.json file now? [Y/n]: y
Okay, I created it. Now read that link!

C'est souvent ce genre de message qu'on va trouver dans les outils Elm : une volonté d'expliquer ce qu'il se passe et de fournir tous les éléments nécessaires à quelqu'un débutant dans le langage pour apprendre.

Les erreurs du compilateur sont d'ailleurs très souvent citées pour leur côté simple et didactique :

-- TYPE MISMATCH ----------------------------------- Main.elm

I cannot do addition with String values like this one:

4| "Hello " + "World"
   ^^^^^^^^

The (+) operator only works with Int and Float values.

Hint: Switch to the (++) operator to append strings!

L'erreur vous indique l'endroit du code concerné, essaye de vous expliquer le plus précisément et humainement l'erreur rencontrée et va souvent vous proposer des pistes pour la corriger.

Même quand l'erreur est complètement inconnue du compilateur (ce qui vous arrivera extrêmement rarement), l'idée est tout de même d'aider le développeur :

Compiling ...elm-0.19.1-beta-1-linux: ./Data/Vector/Generic/Mutable.hs:703 (modify): index out of bounds (3,3) 
CallStack (from HasCallStack): 
  error, called at ./Data/Vector/Internal/Check.hs:87:5 in vector-0.12.0.3- 028020653d7b6942874e2f105e314b1f8fd65010ca5f2ea9562b9a08a2bc03f9:Data.Vector.Internal.Check 

-- ERROR ---------------------------------------------

I ran into something that bypassed the normal error reporting process! I extracted whatever information I could from the internal error: 

> thread blocked indefinitely in an MVar operation 

These errors are usually pretty confusing, so start by asking around on one of forums listed at https://elm-lang.org/community to see if anyone can get you unstuck quickly. 

-- REQUEST -------------------------------------------

If you are feeling up to it, please try to get your code down to the smallest version that still triggers this message. Ideally in a single Main.elm and elm.json file. 

From there open a NEW issue at https://github.com/elm/compiler/issues with your reduced example pasted in directly. (Not a link to a repo or gist!) Do not worry about if someone else saw something similar. More examples is better! 

This kind of error is usually tied up in larger architectural choices that are hard to change, so even when we have a couple good examples, it can take some time to resolve in a solid way.

Elm est donc parfaitement pensé pour accompagner les débutants, mais a également réussi à rester un langage adapté aux non-débutants, puisque ces outils sont tout aussi utiles à ceux-ci.

La simplicité de l'écosystème

Là où npm s'impose comme le gestionnaire de paquets contenant le plus de modules, les packages Elm se comptent uniquement en centaines. Il y a souvent un module standard pour ce que l'on cherche à faire et le choix est donc rapide. Cela s’explique par ce que nous avons vu plus tôt : le langage offre souvent une unique façon de résoudre un problème et les solutions convergent donc sur des choix identiques.

Je n'ai personnellement jamais été bloqué par l'indisponibilité d'un package, notamment parce qu'il est en général facile d'utiliser également des modules JavaScript à travers les ports en Elm.

Et une catégorie se détache plus particulièrement, celle des packages liés à la gestion d'état ! En React il y a toujours un stade où il va falloir choisir entre Redux, MobX, gestion maison ou autre solution de gestion d'état plus ou moins dérivée de Flux. En Elm c'est plus simple : le langage gère directement l'état de l'application à travers la Elm Architecture, très proche de Redux. Et pour cause ! Le créateur de Redux a en effet créé celui-ci en s'inspirant de Elm. Étant intégrée directement dans le langage, son utilisation en Elm est d'ailleurs très intuitive.

On se pose donc au final très peu de questions en début de projet : on utilise ce langage framework et des packages utilisés par quasiment tous les projets Elm, sans devoir choisir entre de nombreuses solutions différentes. C'est pour moi l'un des inconvénients de React pour des équipes peu expérimentées : comme la bibliothèque couvre en elle-même peu de domaines, il faut choisir entre pas mal de solutions des choses comme la gestion d'état, les requêtes HTTP, etc.

La simplicité de refactoring

Comme dit plus tôt, l'une des grandes forces de Elm est son compilateur. La seconde grande force de Elm est son absence d'erreur au runtime, et elle est directement liée à la première. Oui, vous avez bien lu ! En Elm, on considère qu'un code qui compile ne va pas planter en production, et c'est effectivement ce qui se produit en réalité. Voyez par exemple ce graphique, montrant les erreurs en production sur le site de NoRedInk sur 3 ans groupées selon qu'elles proviennent de leur code Elm (200 000 lignes de code) ou de leur code JavaScript (17 000 lignes, soit 11 fois moins !) :

Côté Elm, même si on ne voit pas la courbe, il y a environ une dizaine d'erreurs. En effet, dans les versions précédentes du langage, il était possible pour un cas jugé impossible de demander à l'application de se crasher. Et un jour un développeur s'est trompé... Cela n'est aujourd'hui même plus possible avec les nouvelles versions du langage qui vous forcent à gérer tous les cas possibles !

Pour en revenir à cette conclusion toute simple : en Elm, quand ça compile, ça marche ! Du coup les refactoring deviennent très agréables : on commence par faire un petit changement dans le code, puis le compilateur va nous guider pour savoir ce que l'on doit changer. Rappelez-vous : ses erreurs sont très facilement compréhensibles. Et une fois que cela compile, notre refactoring est terminé !

Il devient alors très facile de modifier du code Elm, à tel point qu'on peut repousser les décisions à plus tard. Par exemple, nul besoin de choisir immédiatement l'architecture de vos fichiers : vous pouvez commencer avec un seul fichier, et le jour où ça devient trop pénible le split est facile (c'est souvent une simple histoire de copier/coller, comme en Elm tout est pur !).

De même, pas besoin de se lancer dans des abstractions hasardeuses, on peut décider d'attendre d'avoir plus de connaissance du métier / projet avant de réécrire certaines parties du code, avec l'assurance qu'on ne va rien oublier.

Cette sérénité qu'apporte le langage retire toute peur de livrer en production un bug caché parce qu'on a oublié de penser à certains cas. A de nombreuses occasions, cela m'a même permis de mettre en évidences des cas auquel le métier lui-même n'avait pas réfléchi !

La simplicité des dépendances

Régulièrement, on peut voir passer des vulnérabilités dans les packages publiés sur npm. D'où que viennent ces failles (npm, erreur humaine, faille technique, ...), le fait est qu'il apparaît dangereux d'ajouter une dépendance à son projet sans vérifier très attentivement sa fiabilité. Et pour cela, il n'existe pas de métrique précise : doit-on regarder le nombre de personnes l'utilisant, la fréquence des mises à jour, la célébrité de l'auteur, le code source directement ?

Et une dépendance innocente peut en tirer une autre qui, elle, peut l'être moins ! La philosophie "publiez et importez des centaines de petit packages spécialisés pour éviter de refaire la roue" de npm encourage malheureusement les arbres de dépendance profonds et il est très compliqué de pouvoir contrôler exactement ce qu'on rajoute à son projet.

Elm est différent dans le sens où les dépendances présentent bien moins de risques. La raison est simple : le langage est plus strict dans sa syntaxe, notamment dans sa gestion des effets secondaires. Par effet secondaire, je parle ici de tout ce qui peut impacter l'extérieur d'un programme : les requêtes HTTP, l'accès aux cookies, le DOM, etc. Et c'est justement via ces effets secondaires que proviennent la plupart des vulnérabilités !

En Elm, si un package installé désire utiliser une de ces fonctionnalités, cela est forcément explicite et le développeur ne peut pas l'ignorer. On sait donc quand il faut se méfier et où regarder précisément. De même, l'arbre de dépendances reste relativement plat et compréhensible.

Un autre aspect qui peut rebuter concerne la taille des dépendances. Un des conseils les plus donnés est de ne "pas ajouter toute une bibliothèque si on n'en utilise qu'une fonction ou deux". En effet, la nature dynamique de JavaScript rend compliqué le tree-shaking (la suppression automatique du code non utilisé lors du build), même si cet aspect a été quelque peu amélioré avec les modules ES6 qui peut maintenant le faire à l'échelle du fichier. Elm – lui – bénéficie de sa nature statique et permet d'effectuer du tree-shaking à l'échelle de la fonction. On n'hésite donc pas à rajouter une dépendance lorsqu'on n'en n'utilise qu'une petite partie.

La charge cognitive réduite

Comme on le voit dans les sections précédentes, la simplicité est le maître mot de Elm. Grâce à ça, les choix incombant au développeur sont réduits : le tooling est standard et bien conçu, pas besoin de mettre en place une chaîne de build complexe manuellement, le choix des bibliothèques à utiliser est relativement simple et la simplicité du langage fait qu'il y a en général une seule façon d'atteindre un but.

De plus, le compilateur assiste le développeur tout au long du développement. En général, l'ajout d'une fonctionnalité se fait en deux temps : on commence par modifier une partie du code, puis les erreurs du compilateur vont permettre de cibler les autres modifications nécessaires. La charge cognitive s'en trouve donc fortement réduite pour le développeur qui peut donc se concentrer sur les changements en eux-mêmes plutôt que sur le fait de réfléchir à tout ce qui doit être changé.

Et c'est pareil pour le refactoring ! Ce qui fait que les décisions prises en début de projet peuvent être modifiées plus tard en toute confiance (et c'est là le second maître mot de Elm). On peut donc partir sur les choix les plus simples en début de projet et revenir dessus lorsqu'on possède davantage d'informations.

Le compilateur est donc une source de feedback sur la justesse de notre code. Rappelez-vous : si ça compile, ça fonctionne ! Ce feedback est instantané dans nos IDE, un développeur Elm peut donc se concentrer sur le développement et se contenter d'ouvrir le navigateur seulement lorsque le code compile, évitant ainsi beaucoup de temps perdu entre le refresh de la page, la navigation jusqu'à la fonctionnalité à tester, etc.

La philosophie de l'écosystème

J'aime beaucoup l'écosystème Elm dans sa façon de communiquer. Tout d'abord, tout est fait en pensant au développeur en tant qu'humain. Les outils et la documentation ont alors pour but d'accompagner celui-ci dans la découverte et l'utilisation du langage.

Le langage a un but précis : permettre de réaliser des interfaces et applications web. Cette volonté est présente dans toutes les décisions pour tendre au mieux vers cet objectif. Dans d'autres langages, lors de la release d'une version les annonces se concentrent généralement sur l'aspect technique : quelles sont les nouveautés, les commits, etc. En Elm, les annonces se concentrent sur ce qu'elles apportent en terme de valeur. Voici quelques exemples :

La communauté est également bienveillante et prompte à aider via Slack ou le Discourse et l'auteur du langage, Evan Czaplicki a donné de nombreux talks centrés non pas sur le langage lui-même, mais sur ses réflexions sur l'animation et les interactions des communautés en ligne.

Le mot de la fin

Elm est un langage qui m'a rapidement séduit et les aspects mentionnés dans cet article sont le fruit d'une longue réflexion sur ce qui me fait apprécier le langage. Malgré mes comparaisons peu flatteuses, je tiens également à préciser que JavaScript est un langage que j'adore et que je pratique toujours aujourd'hui. Elm est cependant devenu mon langage de prédilection pour le développement front-end tant les garanties qu'il offre et l'expérience développeur sont satisfaisantes.