Blog

ReasonML

Les 17 et 18 mai derniers s’est tenu la conférence React-Europe. Deux jours de conférence au cours desquels ont été présentées les nouveautés de React.js, avec la sortie l’automne dernier de la version 16, mais également de React Native. L’occasion aussi de rencontrer quelques geeks, qui ne jurent que par la liberté dont dispose un développeur React quand on parle d’architecture d’application. Ici on aime pas les frameworks ! L’un des speakers a quand même tenté de se présenter portant un magnifique t-shirt marqué Angular, mais il s’est rapidement fait chambrer, lui rappelant qu’ici, on ne veut pas des contraintes et du cadre rigide d’un vrai framework. Ici, on veut rester libre de tout faire, même les utilisations les plus farfelues.
Les gens de Dice, eux, l’ont bien compris en nous présentant leur implémentation de la totalité des menus du jeu Battlefield 1 réalisé en React, même s’il leur a fallu pour ça réécrire un JS Engine complet en C++, embarqué dans le jeu.
(Pour ceux qui ne connaissent pas, Dice c’est un studio de développement basé à Stockholm et Los Angeles et qui est juste ce qui se fait de mieux ou presque à l’heure actuelle dans le monde vidéo ludique avec des titres comme Battlefield ou Star Wars Battlefront).

Mais revenons-en au sujet. Les conférences se sont enchaînées toute la journée et je dois dire qu’un sujet en particulier s’est démarqué, présent dans quasiment un talk sur deux : ReasonML.

Qu’est-ce que c’est ?

ReasonML c’est tout simplement le nouveau langage (pas si nouveau que ça en fait, mais j’y reviendrais plus tard), développé par Facebook, pour écrire des applications React.

Encore un nouveau langage ?! Mais comment en est-on arrivé là…

Faisons un petit retour en arrière…
Nous sommes en 2011. Facebook, sorti depuis 2004, connait une popularité toujours croissante. L’application grandit, tout comme l’équipe de développement derrière. Mais voilà que certains problèmes commencent à se faire sentir, notamment au niveau de la partie publicité. Pour info, le marché de la pub dans la monde c’est 650 milliards de dollars par an et on estime que Facebook et Google pèsent pour environ 150 milliards à eux deux. Autant dire que chez Facebook on ne plaisante pas avec ça.
Le problème est que l’application Facebook, basée sur un MVC coté client, devient difficile à maintenir en raison du nombre croissant de fonctionnalités et de développeurs travaillant sur le projet. Un simple changement dans le Model entraîne un ré-affichage complet de toute la page, c’est l’effet cascade, et en termes de performances c’est très gênant. De plus, au fil du temps, les ingénieurs chez Facebook ne parviennent plus à gérer cet effet cascade de manière fluide et l’application commence à devenir imprévisible. Décision est donc prise de chercher un autre moyen de gérer une application client.

Facebook n’en est pas à son coup d’essai : en 2010, ils avaient déjà introduit XHP, une extension PHP open-source qui améliore la syntaxe du langage et permet de rendre le code client plus compréhensible mais également de le protéger contre le cross-site scripting.

function xss_protection(string $danger): void {
  // La chaîne $danger va automatiquement être échappée
  // Pas besoin d'utiliser htmlspecialchars()
  echo
    <html>
      <head/>
      <body>{$danger}</body>
    </html>;
}

Example de code XHP

Cette fois-ci c’est Jordan Walke qui s’attèle à la tâche et qui crée un premier prototype de ce qui deviendra React par la suite : FaxJS.
Pour l’anecdote, les premières versions du prototype ne sont pas écrites en JavaScript comme on pourrait s’y attendre mais en SML, un cousin de OCaml, langage de programmation fonctionnel apparu dans les années 90. (Vous allez comprendre par la suite pourquoi ça a de l’importance).
En 2012, React est utilisé par Facebook en interne. Dans l’objectif de faciliter l’adoption, le SML a été abandonné pour le JavaScript, permettant ainsi de profiter de la communauté JavaScript déjà bien établie alors que celle de SML est… disons plus restreinte. Avec l’acquisition d’Instagram, très intéressé pour utiliser cette nouvelle technologie, un effort est fait pour découpler React de l’application Facebook, et en 2013 React est présenté au monde et devient Open source.

Pour expliquer rapidement le principe, l’idée derrière React (ou Angular d’ailleurs) est de découper la page en blocs réutilisables au maximum. Les blocs peuvent contenir d’autres blocs qui eux-mêmes peuvent en contenir d’autres et ainsi de suite. On obtient ainsi toute une arborescence allant des blocs les plus globaux jusqu’aux plus spécifiques représentant par exemple un simple bouton ou une div. Ces blocs sont ce qu’on appelle les composants :

class Welcome extends React.Component {
    render() {
        return <h1>Hello World!</h1>;
    }
}

Example de composant en React

Pour l’anecdote : React est assez mal accueilli. Les développeurs se montrant très sceptique : « Quoi ? Du HTML intégré directement dans le code JavaScript ?! Mais on revient en arrière là (JSP si tu nous regarde…) !! Ça ne marchera jamais ! »

2014 est l’année de l’expansion, de plus en plus de personnes s’intéressent à la librairie pour son côté novateur et modulaire et en 2015 React atteint la stabilité et est même utilisé par des grosses sociétés comme Netflix ou AirBnB. C’est également en 2015 qu’apparaissent Redux et React Native.
Aujourd’hui React est un incontournable dans la multitude de frameworks et bibliothèques frontend avec quasiment 100 000 stars sur Github, loin devant Angular et ses 38 000.
Il est utilisé sur un grand nombre de projets, de plus en plus de développeurs commencent à avoir une grande expérience sur le sujet et c’est justement ce qui permet de dresser un bilan des limites de l’utilisation de JavaScript en React.

Les raisons du changement

JavaScript est un langage qui a beaucoup de qualités et que personnellement j’aime beaucoup (Aïe ! Non ! Pas sur la tête !). Très facile à apprendre et à prendre en main, très permissif avec son typage dynamique, il possède de plus une vaste communauté et les ressources en ligne sont nombreuses ce qui en fait un excellent langage pour débuter dans la programmation.
Seulement voilà, le typage dynamique c’est génial quand on débute, on ne comprend pas comment on a pu perdre tout ce temps en Java à typer nos variables avec des noms de classes plus long que le bras, mais à un moment on se rend compte que finalement, tout n’est pas si rose. Sur des gros projets, ce n’est vraiment pas idéal pour la maintenabilité du code et le travail en équipe. JavaScript est certes très facile à utiliser mais il n’en est pas moins très faible du point de vue de la sureté.
Pour vous donner une image, c’est comme prendre la mer avec un bateau dont la coque est percée… On peut naviguer mais on prend quand même des risques.
Des projets comme Flow ou TypeScript, sur lequel d’ailleurs se base Angular, ont donc vu le jour, apportant une surcouche de typage statique à JavaScript pour essayer de colmater ces brèches.

C’est là que les ingénieurs chez Facebook se sont réunis :  »
– Il faut qu’on trouve un moyen d’améliorer la sureté du code de nos applications React.
– On pourrait utiliser un langage fonctionnel avec un typage statique.
– Pourquoi pas… Mais à mon avis, il y a deux aspects qu’il faut bien garder à l’esprit si on veut que les devs s’intéressent à un nouveau langage : la facilité de maintenabilité, de ce côté là on devrait être tranquille avec un langage fonctionnel, mais aussi la facilité d’adoption. Après tout, nos principaux adeptes, ça reste des devs JavaScript… faut pas trop leur en demander non plus.
– On pourrait utiliser OCaml. C’est un langage éprouvé, performant et fiable. En plus c’est un cousin de SML avec lequel React a été développé à la base… »

Vous comprenez maintenant où je voulais en venir avec mon historique sur React. React à la base n’étant pas écrit en JavaScript mais bien en SML. Certaines bonnes pratiques de développement en React (et par bonne pratique j’entends quasiment obligatoire) comme l’immutabilité pour la gestion du « state » paraissent du coup beaucoup plus naturelles. JavaScript ayant besoin de son coté de recourir à des librairies tierces comme Immutable.js, ce n’est pas le cas en programmation fonctionnelle où l’immutabilité est un des fondements du paradigme.
OCaml de son côté est un langage de programmation fonctionnelle donc gérant l’immutabilité et qui possède un typage statique.
Pour reprendre l’image précédente, cette fois, on prend la mer avec un bateau dont la coque est blindée.

Retour à la réunion chez Facebook :  »
– OCaml ? Je connais pas. Ça ressemble à quoi ?

# let rec sort = function
    | [] -> []
    | x :: l -> insert x (sort l)
and insert elem = function
    | [] -> [elem]
    | x :: l -> if elem < x then elem :: x :: l
        else x :: insert elem l;;

– T’es sûr que t’as écouté la partie sur la facilité d’adoption ? Si on présente ça à un dev JavaScript, il va nous faire une syncope. Pour un peu, il pourrait presque se mettre au Java !! Non, il nous faut quelque chose de plus simple.
– Rien ne nous empêche d’implémenter une surcouche de OCaml qui rende le tout un peu plus « user friendly »
– C’est pas une mauvaise idée ça. Appelons le ReasonML ! »

ReasonML est né ! Vive ReasonML !

Voilà donc ce qui nous amène aujourd’hui à parler de ce « nouveau » langage. Mais voyons ça un peu plus en détail…

Pour résumer ce que j’ai dit précédemment, ReasonML est une surcouche de OCaml visant à le rendre un peu plus « sexy » et compréhensible pour un développeur JavaScript. L’idée est de conserver la sureté d’un langage comme OCaml tout en apportant un peu de confort syntaxique au développeur pour qu’il ait simplement l’impression d’écrire du JavaScript avec quelques règles supplémentaires.
De plus, ReasonML maintient une très forte interopérabilité avec JavaScript grâce au compilateur BuckleScript qui permet de compiler le code Reason en code JavaScript lisible. Cela permet d’adopter ReasonML dans un projet React de manière incrémentale en transformant non pas toute l’application d’un coup mais progressivement, composant après composant.

Les principaux apports de ReasonML

- Le typage statique

Habituellement un dev JavaScript, il va coder son composant puis se rendre sur sa page web pour voir là où ça a crasher, modifier le code en fonction, relancer,…
Qui n’a jamais eu de messages comme undefined is not a function ou encore null is not an object ?
ReasonML, lui, cherche à éliminer un maximum de ces « runtime error ». Pour cela, plus de variables qu’on assigne comme on veut. On déclare le type de chacune de ses variables de la même manière qu’on le ferait en Java par exemple. C’est le principe du typage statique.

Pour déclarer une variable on utilise la syntaxe :
let score = 10;

  • Il n’est pas nécessaire de déclarer le type d’une variable systématiquement. OCaml et donc par extension ReasonML possède un système d’inférence de type et va donc déterminer le type d’une variable chaque fois qu’il le peut. Ici le compilateur détermine tout seul qu’il s’agit d’une variable de type int.
  • Les variables en Reason ont une portée de bloc. Ce n’est pas le cas en JavaScript (si on utilise le mot-clé var, ES6 ayant corrigé cela avec les mots-clés const et let)

Quelques-uns des différents types disponibles :

– String : let greeting: string = "Hello world!";
– Char : let firstLetterOfAlphabet: char = 'a';
– Boolean : let isJSTheBest: boolean = true;
– Int : let theAnswer: int = 42;
– Float : let unBateauQui: float = 1.0;
– Tuple : let ageAndName: (int, string) = (24, "Lil' Reason");. Le tuple peut contenir des variables de types différents.
– Record : type person = {age: int, name: string};. Le record est l’équivalent du plain-object JavaScript avec toutefois une structure fixée.
– List : let myList: list(int) = [1, 2, 3];. Les listes sont immutables en Reason.
– Array : let myArray: array(string) = [|"hello", "world", "how are you"|];. Les arrays sont mutables mais ont une taille fixée à la création.
– Variant : structure particulière qui permet de définir une variable qui peut être ça ou ça (de la même manière qu’une énumération Java)

type myResponseVariant =
  | Yes
  | No
  | PrettyMuch;
let areYouCrushingIt: myResponseVariant = Yes;

L’intérêt des types est que cette fois, en cas d’erreur, c’est à l’étape de compilation que l’erreur est signalée plutôt qu’au runtime. Les types des paramètres attendus par une fonction sont également connus, ce qui permet d’avoir un code plus facilement maintenable et plus lisible. Pour reprendre une citation sur Twitter :

Unit tests cover whatever you can think of. Types cover the things you forgot.

Les tests unitaires couvrent tout ce à quoi vous avez pensé. Les types couvrent tout ce que vous avez oublié.

Alors bien sûr c’est un peu plus long d’écrire le code, il faut faire plus attention mais au final on gagne beaucoup de temps sur la phase de test « en live » sur la page Web, la plupart des erreurs ayant été anticipées.

- L’immutabilité naturellement prise en charge

Comme je le disais plus haut, l’immutabilité est un des fondements de la programmation fonctionnelle et ReasonML n’échappe donc pas à la règle. Si vous avez déjà codé une application React, vous connaissez l’importance de toujours manipuler son « state » de manière immutable, sans quoi on génère des effets de bord qui peuvent avoir des comportements inattendus.
Redux fonctionne d’ailleurs sur ce principe en se basant uniquement sur des fonctions pures, à savoir qui ne modifient jamais les paramètres passés en entrée.
Reason offre cependant la possibilité de faire une entorse à la règle. C’est certes plus compliqué au niveau syntaxique mais ça reste réalisable pour certains algos qui fonctionnent mieux en utilisant un mode de programmation impératif classique.

 
- Multi-plateforme

JavaScript l’est également avec le Web, Node.js, Electron ou encore React Native. Mais JavaScript peut être lent. De son côté, grâce à OCaml, ReasonML peut compiler en natif (ou bytecode ou JavaScript) vers toutes les plateformes (Mac/Windows/Linux/Raspberry/…) et qui dit natif dit forcément des performances accrues. Et de ce côté-là, le meilleur reste à venir avec les fonctionnalités de compilation native vers iOS ou Android qui sont en cours de développement.

En quoi est-ce important ?
Imaginons une application React Native sur un smartphone. A l’heure actuelle, le code natif écrit en Objective-C ou Java tourne dans un thread et la VM JavaScript qui exécute l’application React Native dans un autre thread. Les deux communiquent à coup de sérialisation/désérialisation. Forcément cela a un coût et ça se traduit par des performances en berne. Certaines fonctionnalités particulièrement gourmandes nécessitent même d’être réécrites en code natif car le coût de fonctionnement est trop élevé en React Native.
ReasonML, lui, pourra donc théoriquement compiler directement en code natif iOS ou Android. Pas de sérialisation, pas de threads séparés, l’application React pour le coup serait vraiment native (ou Really Native React Native \o/) et aura donc des performances maximales.
C’est certainement LA fonctionnalité qui génère le plus de « hype » au niveau de ce langage.

Quelques autres caractéristiques du langage

- Les fonctions

Une fonction se déclare de la manière suivante :

let sayHi = (name: string): string => {
    "Hello " ++ name;
};

On peut noter l’utilisation du « ++ » pour la concaténation de string

Caractéristique intéressante : les fonctions sont « auto-curried », ce qui signifie qu’une fonction prenant n paramètres peut en fait être écrite comme n fonctions prenant 1 seul paramètre. Lors d’un appel à une fonction avec un paramètre manquant, on aura donc en retour une nouvelle fonction n’attendant que ce paramètre :

let add = (x: int, y: int): int => x + y;
let addFive = add(5); /* retourne une function */
let eleven = addFive(6);
let twelve = addFive(7);

Il est également possible d’attribuer un alias aux paramètres :

let add = (~x: int, ~y: int): int => {
   x + y;
};
add(~x=5, ~y=6);

 

- Relation avec JavaScript

ReasonML vise une interopérabilité maximale avec JavaScript notamment via BuckleScript qui permet de compiler vers JavaScript. Sans surprise également, il migre du package manager opam d’OCaml vers celui de node : npm.

 

- Les modules :

Par défaut, chaque fichier Reason, portant l’extension « .re », est un module. Pour utiliser une fonction déclarée dans un fichier Test il suffit de l’appeler en utilisant Test.func(). Plus besoin d’importer explicitement les fichiers avec import { func } from './Test' et ça c’est que du bonheur.

 

- La valeur null

Il n’y a pas d’équivalent à la valeur null de JavaScript. A la place, Reason utilise un type option :
option(int) = None | Some(int);
Ici on simule un nullable int qui peut donc prendre les valeurs « None » ou « Some(int) ».

 

Il y a encore beaucoup à dire concernant ce langage mais l’objectif de ce blog n’étant pas d’être exhaustif, je vais m’arrêter là.
Si cela vous intéresse ou que vous êtes curieux, je vous invite à consulter la documentation en ligne.

Et un composant React en Reason ça ressemble à quoi ?

Le but de ReasonML étant à terme de remplacer ou, tout du moins, proposer une alternative à JavaScript pour écrire une application React, il existe bien évidemment une librairie ReasonReact.
Voici à quoi ressemble un composant simple en Reason :

/* file: Welcome.re */

let component = ReasonReact.statelessComponent("Welcome");

let make = (~name, _children) => {
  ...component,
  render: (_self) => <h1> {ReasonReact.string("Hello " ++ name ++ "!")} </h1>
};

En détail:

  • let component = ReasonReact.statelessComponent("Welcome");

    Crée le composant. A noter que la chaîne « Welcome » passée à la fonction n’a qu’une utilité de debugging, elle ne définit pas le nom du composant.

  • let make = ...

    On ne définit plus une classe comme en React classique mais une fonction make, appelée automatiquement par ReasonReact.

  • (~name, _children)

    Les paramètres passés à la fonction make. C’est l’équivalent des « props » en React. Le dernier paramètre doit être _children (les variables commençant par « _ » ne génèrent pas d’avertissement à la compilation si elles ne sont pas utilisées).

  • render: (_self) => ...

    Comme en React, un composant doit avoir une fonction render. Le concept de this n’existe pas en ReasonReact, il est remplacé par self qui est un record (cf. types) contenant différentes propriétés comme le state que l’on peut ainsi passer entre les composants.

Et pour ajouter notre composant dans le DOM (en supposant qu’il y ait une div avec l’id welcome dans la page) :

/* file: Index.re */

ReactDOMRe.renderToElementWithId(<Welcome name="World" />, "welcome");

De ce côté là, pas de grands changements par rapport à JavaScript qui utilise le même principe avec ReactDOM.render( ... );

Pour conclure

Alors bien sûr le projet est encore jeune et l’utiliser aujourd’hui pour des applications en production est à réaliser, pour reprendre les termes des devs, « avec précaution ». La stabilité n’est pas encore parfaite, nombre d’outils de l’écosystème JavaScript sont encore à adapter, la doc n’est pas exhaustive, loin de là et la communauté est assez réduite.
Toutefois cela semble assez prometteur. En tout cas le « marketing » est en place, les devs travaillent pour adapter les outils, les conférenciers sillonnent le monde, tout ça pour essayer de fédérer une communauté autour de ce langage et d’avoir l’appui de la très large communauté JS. Trop tôt pour dire si cela va fonctionner mais personnellement je trouve le concept séduisant, notamment grâce au lien très fort que ReasonML entretient avec JavaScript tout en résolvant les principaux écueils de ce dernier. Maintenant à vous de tester !!

Ludovic Cadet

Written by

Web Developer at Oxiane