"Elm"
est un langage fonctionnel inspiré de Haskell et destiné à l'écriture d'applications s'exécutant dans un navigateur. Le compilateur d'Elm est un transpileur vers du JavaScript.
Elm est donc donc dédié à la programmation fonctionnelle réactive (FRP, "Functional Reactive Programming") qui consiste à modéliser des interactions avec un utilisateur sous forme de pures fonctions. Nous verrons plus loin pourquoi cela est souhaitable, et comment c'est possible.
Elm est le premier outil qui me donne envie de développer des applications Web. Je considère le reste (hors FRP) comme un monceau d'aberrations technologiques. Un petit mot de consolation pour ceux qui prendraient soudainement conscience d'avoir gâché une partie de leur vie à faire fonctionner une chaîne d'outils ridicule : ne pleurez pas ! S'il n'y avait pas eu autant d'applications Web -- ridicules ou non -- pour créer l'appel d'air, Elm n'aurait jamais vu le jour !
Après un tel assaut de gentillesse il faut argumenter un peu.
== Ridicule, pourquoi ?
D'une façon générale, un langage de programmation nous aide à écrire autre chose que ce qu'on croit de deux façons :
- En autorisant des effets de bord incontrôlés.
- Par absence de typage.
Le JavaScript cumule judicieusement les deux.
À côté de ça les applications natives offrent deux façons de dessiner efficacement un objet graphique :
- Envoyer des commandes de dessin 2D concernant une surface donnée.
- Envoyer des commandes à la carte graphique pour déplacer des polygones texturés.
La première approche a été popularisée par `Windows 3` vers la fin du précédent millénaire. C'est également celle de Swing qui dessine ses composants avec `Java 2D`.
La deuxième approche a été popularisée par Mac OS X à sa sortie dans les années 2000. Elle tire parti de l'accélération matérielle pour améliorer le rendu des applications, notamment grâce à des animations jusqu'ici réservées aux jeux. JavaFX fournit les primitives pour bénéficier de ce type d'accélération que Windows a également fini par intégrer.
Les deux approches se combinent. Par exemple, les cartes graphiques modernes accélèrent le rendu graphique d'un texte sur un canevas, indépendemment de la 3D.
Où est le problème ? Les navigateurs Web sont optimisés pour tirer parti de la meilleure méthode d'affichage disponible. Oui mais ça c'est une fois que le document HTML a été transformé en DOM ("Document Object Model", modèle objet de document). Si du code JavaScript veut agir sur l'affichage en cours, il doit passer par le DOM. Le navigateur, qui écoute les modifications sur le DOM, sait alors qu'il doit recalculer la disposition des éléments sur la page.
Bien sûr il y a le tag ``<canvas>`` pour dessiner directement sur une surface avec des primitives 2D mais c'est réservé à des cas marginaux. Si on veut des composants graphiques (boutons, champs de saisie...) l'approche la plus naturelle reste le DOM. Les applications Web optimisées utilisent par exemple des caches de fragments de DOM. Elles ne sont pas aidées par le fait que l'accès au DOM ne puisse avoir lieu que dans le fil d'exécution réservé à l'affichage -- au moins en Swing tu peux tripoter un composant dans n'importe quel fil d'exécution tant que tu ne l'affiches pas-_.
Techniquement c'est n'importe quoi. Si on veut changer la couleur d'un machin ça devrait suffire d'invalider la zone pour le repeindre avec la bonne couleur. Mais non, il faut passer par un modèle objet qui représente un document HTML qui n'a probablement jamais existé, puis le modifier afin de provoquer des notifications en cascade.
Écrire des applications Web c'est faire la cuisine avec des instruments qui ont tous un manche de `3 m` de long. Toutes les semaines des mecs malins trouvent une nouvelle façon de tenir le manche pour que ça gêne moins. Et je n'insiste pas sur les vertus pousse-au-crime du JavaScript.
Au milieu de tout ce que j'ai dit précédemment quelqu'un perçoit-il une lueur d'espoir ?
== Lueur d'espoir
Nous avons mentionné les approches "efficaces" pour afficher quelque chose (primitives 2D ou 3D). Elles font furieusement penser à des effets de bord non-contrôlés. Par opposition le passage par le DOM donne l'impression qu'il y a moyen d'exercer plus de contrôle pour détecter plus vite si on fait des bêtises. Mais avec le JavaScript c'est un peu mort, non ?
Peut-être que c'est le moment de détailler ce que signifie un "effet de bord contrôlé". D'une façon générale, un langage qui ne produit pas d'effet de bord n'a aucune utilité. On peut dire aussi que l'effet de bord est la cause de tous les dysfonctionnements et considérer alors qu'un programme qui fonctionne est un cas spécial de dysfonctionnement mais ça ne nous avance pas beaucoup.
L'important c'est que les langages fonctionnels ont résolu tout ça depuis longtemps. Tu as de pures fonctions (donc pas d'effet de bord) et après c'est du code propre à l'environnement d'exécution (donc écrit par des gens compétents et testé plein de fois) qui effectue l'effet de bord.
Autrement dit tu peux modéliser un changement d'état dans un monde purement fonctionnel :
<<<
ma_pure_fonction( état_1, paramètres ) -> état_2
>>>
Donc si tu dis que ton état c'est le DOM, et si tu considères qu'il y a moyen de faire une copie totale ou partielle du DOM, alors cette grosse idiotie obèse de DOM te fournit le début d'une solution pour maîtriser les effets de bord. L'exemple précédent devient :
<<<
mon_comportement_applicatif( mon_dom_1, paramètres ) -> mon_dom_2
>>>
Mais ça ne suffit pas. Il faut réconcilier le fragment de DOM avec le "vrai" DOM affiché. Est-ce que ça ne va pas générer un coût insupportable ?
Bizarrement, non. La création d'objets dans le DOM et sa mise à jour coûtent tellement cher que le DOM virtuel ("virtual DOM") est une technique d'optimisation reconnue -- preuve que le mécanisme de base est déficient-_. D'ailleurs la documentation d'Elm mentionne React et Om, pionniers du DOM virtuel, comme sources d'inspiration.
Avec le DOM virtuel, un socle technique fournit au code applicatif une grappe d'objets qui correspond à un DOM allégé, sans référence directe vers l'original. Le code applicatif fait ses modifications, y compris des trucs non-optimaux genre ajouter un composant pour l'enlever aussitôt. Quand c'est fini le socle technique calcule la différence avec l'original et n'effectue sur le vrai DOM que le minimum de modifications. On peut voir le DOM virtuel comme un cas extrême du cache de DOM.
Donc là il se trouve que par une espèce de hasard idiot mais sympathique il y ait moyen de transformer l'inaptitude fondamentale des navigateurs Web à exécuter des applications en quelque chose de vertueux. Évidemment cela nécessite d'encadrer très sévèrement les accès au DOM. Et c'est là qu'on se remet à parler d'Elm.
== À quoi ça ressemble
De loin Elm ressemble furieusement à Haskell :
- C'est un langage fonctionnel sans variables apparentes et sans effets de bord.
- Le typage est très restrictif.
- La syntaxe d'Elm a plus ou moins la même gueule que celle de Haskell.
Elm est un langage compilé. Le compilateur d'Elm est écrit en Haskell. Il fonctionne sur Mac, Windows et Linux. Une version du compilateur tournant dans `Node.js` a l'air de fonctionner plus ou moins.
Elm ne supporte pas certains niveaux d'abstraction offerts par Haskell. Ça veut dire que quand on utilise certaines structures qu'on voudrait génériques, il faut écrire du code-glue. Oui c'est horrible mais le monde est plein de trucs encore plus horribles.
Elm interopère avec du Javascript en échangeant des chaînes de caractères qui représentent des objets sérialisés ("ports" dans la terminologie Elm). Si la désérialisation n'a pas craché d'erreur, l'objet Elm résultant est garanti valide. On peut aussi embarquer un programme Elm comme composant d'une page HTML.
Elm fournit des bibliothèques standard pour accéder aux fonctionnalités du navigateur : composants HTML, CSS, WebSockets, requêtes HTTP, etc. Il y a également un référentiel de "bibliothèques tierces"
.
Je ne vais pas recopier la doc. Je conseille juste de regarder l'application-exemple "Todo", le code est d'une concision hallucinante.
=== Et côté serveur ?
Elm est conçu pour la FRP dans un navigateur Web et rien de plus. Mais il doit y avoir moyen d'exécuter le JavaScript généré côté serveur, dans `Node.js` ou dans une JVM. Non ce n'est pas un langage pensé pour être utilisé de bout en bout.
== Outillage
Elm fournit un peu plus que le minimum syndical. La documentation mentionne :
- Des extensions pour différents éditeurs de texte.
- Quelques outils pour essayer du code interactivement.
- Un outil de téléchargement de bibliothèques tierces.
- Un débogueur, ressuscité dans la version `0.18`.
=== Débogueur
Le débogueur d'Elm tire parti de la nature fonctionnelle du langage. Comme les structures de données sont immuables, chaque entrée ou sortie est clairement identifiée. L'état du programme à un moment donné correspond à la séquence des événements en entrée jusqu'à ce moment. Il n'y a pas d'état caché puisque le programme est fait de pures fonctions. Donc l'état de l'application est externalisable et rejouable par la suite.
Dit autrement : tu peux déboguer la prod à distance par mail sans rien casser, en marche avant et en marche arrière aussi.
Par mail ? En marche arrière ? Oui.
C'est simple : quelqu'un constate un fonctionnement non-souhaité. Il exporte l'historique des événements et tu le rejoues sur ta machine. Tu peux voir le détail de tous les changement de toutes les structures de données à chaque étape.
Là je propose deux minutes de pause pour pleurer pour tout le temps perdu à reproduire les "Mais non je te jure j'ai rien fait" qui ont introduit des changements cachés qui ont foutu la grouille plus tard.
Le billet "The Perfect Bug Report"
explique tout ça et fournit une démo en ligne, ainsi qu'une courte vidéo.
Quand dans le billet sur Sketch-n-Sketch je parlais d'un débogueur de folie rendus possible par un langage fonctionnel, c'est celui-là que j'avais en tête.
C'est une "vieille version"
du débogueur d'Elm qui a inspiré celui de Swift. Avec Swift on a un effet plus visuel, quand on bouge le curseur de temps les variables sont mises à jour dans l'environnement de développement, et s'il y a une animation correctement programmée elle s'actualise en fonction du temps choisi avec Mario qui saute en marche arrière. Comme Elm dispose de 100 fois moins de budget l'auteur s'est concentré sur des fonctionnalités vraiment utiles et le résultat est 10 fois meilleur.
== Performances
Pour les mises à jour du DOM, Elm "annonce"
des performances nettement supérieures à React, Angular et Ember.
Le document sur les performances mentionne un point intéressant : optimiser une application en Elm ne nécessite pas de bricolages susceptibles d'introduire des dysfonctionnements, comme c'est le cas pour les autres socles techniques en JavaScript.
Concernant la taille, les fichiers ``.js`` chargés par Chrome pour "Sketch-n-Sketch"
totalisent 490 kB, mais le code n'est pas minifié. De plus il faut tenir compte d'une éventuelle compression gzip.
Donc niveau performances Elm semble être dans le peloton de tête.
== Qui utilise Elm ?
"NoRedInk"
annonce 80k lignes d'Elm et pas un seul plantage en 1 an.
"Sketch-n-Sketch"
compte d'après ses auteurs 13k lignes d'Elm.
=== Un retour d'expérience perso ?
Eh bien non je n'ai pas encore écrit une seule ligne en Elm. Cela dit la réécriture de Szó, mon outil d'apprentissage de vocabulaire, figure en bonne place dans mes futurs projets de loisir.
Cela dit je voulais parler d'Elm pour introduire cette notion de reproductibilité des événements en entrée, en vue d'un prochain article.
== Produits concurrents
Elm n'est pas le seul socle technique dédié à la programmation réactive fonctionnelle. Voici ce que j'ai trouvé de plus abouti dans le domaine.
"PureScript"
est très proche de Haskell et il y a beaucoup d'outillage.
"Reflex"
est une bibliothèque Haskell pour la FRP, à utiliser de pair avec "GHCJS"
, le transpileur de Haskell vers JavaScript.
"Fable"
est mentionné ici juste pour faire plaisir aux adeptes de `F#`.
Je ne parle pas de la programmation réactive en JavaScript, vu que ce langage n'est pas pur d'un point de vue fonctionnel.
Il est sûrement possible de compiler du PureScript pour le faire tourner dans un serveur `Node.js`. Mais ça ne veut pas dire que le développement d'un serveur en PureScript soit prévu.
Parmi les innombrables bibliothèques Haskell, il y en a forcément une pour faire tourner un serveur Web, donc là on aurait du code complètement partageable entre client et serveur, sans rien renier du paradigme FRP. Mais Haskell c'est velu.
Je n'ai vu dans aucun autre produit l'équivalent du débogueur d'Elm.
== Conclusion
On peut s'interroger sur la pérennité d'Elm, dont les développements tournent au ralenti. Le développeur principal, Evan Czaplicki, n'a visiblement plus l'occasion d'y travailler à plein temps.
On pourrait souhaiter plus de doc et d'outillage. Notamment, il manque des outils de refactorisation. Mais le débogueur est une tuerie absolue.
J'ai bien lu le billet "Elm Is Wrong"
et l'auteur, programmeur expérimenté en Haskell, nous explique à quel point le système de types d'Elm est loupé. Des "commentaires sur Reddit"
relativisent le point de vue. D'autres billets confirment qu'Elm fournit un niveau d'abstraction très limité par rapport à Haskell. Un mec a essayé PureScript (très proche de Haskell) et Elm, il donne Elm gagnant : "In depth overview of Elm and Purescript. Lessons learned porting a game from Purescript to Elm."
L'important c'est que la FRP (programmation fonctionnelle réactive) rend obsolètes les autres approches impératives, autant pour des raisons de performances que de sûreté d'exécution.
Elm offre la solution la moins traumatisante pour se lancer dans la FRP.
Quelqu'un a déjà essayé ?