Facebook a récemment annoncé que 50% de leur application Messenger a été réécrite en ReasonML. Ils affirment n’avoir eu que 10 bugs en une année sur ce nouveau code et que le temps nécessaire pour faire des refactoring majeurs est passé de plusieurs jours à quelques minutes !! Si le langage de programmation choisi permet un tel niveau de fiabilité et de flexibilité, imaginez donc l’efficacité de l’équipe de développement !


Qu’est-ce que la programmation fonctionnelle ?

 

La programmation fonctionnelle est une approche centrée sur les fonctions. Au lieu d’architecturer un projet en regroupant des fonctionnalités dans des classes, on va plutôt structurer le projet simplement avec des fonctions. Certaines peuvent recevoir d’autres fonctions en entrée afin d’organiser leur travail. Cela peut paraître vague et complexe mais, en pratique, c’est très simple ! On peut commencer avec un petit script et le faire évoluer facilement en un projet complexe.

Quelques langages fonctionnels et leurs avantages

 

Lisp et Haskell sont parmi les langages fonctionnels les plus connus. Cependant, leurs forces ne reposent pas sur l’aspect fonctionnel à proprement parlé. Concernant Lisp, sa force réside dans le système de macros permettant d’adapter le langage au domaine du logiciel à développer. Quant à Haskell, c’est également un langage efficace pour le développement, puisqu’il propose un système de types forts qui permet au compilateur de produire du code très optimisé.

Ocaml, dont les racines remontent aux années 70, est un langage fortement typé qui est l’ancêtre des langages que nous allons étudier ici (F#, Rust et Reason ML). Ce langage est aussi multi-paradigme et peut servir à faire de la programmation orientée objet.

 

Si vous programmez en .NET, vous pouvez utiliser le langage F# qui est une implémentation de Ocaml pour .NET. Puisque F# et C# fonctionnent tous les deux en .NET, il n’est pas difficile de mélanger les deux langages au sein d’un même projet et ils ont tous les deux accès à toutes les librairies écrites pour .NET.

Par exemple, Kaggle a choisi d’utiliser F# plutôt de C# pour les algorithmes d’analyse. Depuis, ils convertissent de plus en plus leur code C# en F# car ils le trouvent plus court, plus facile à lire et plus facile à remanier. De plus, grâce aux types forts le code contient beaucoup moins de bugs.

 

Des langages à la mode

 

Vous avez sûrement entendu parler de Rust, le langage développé par Mozilla et qui veut remplacer le C, mais vous ignorez peut-être que la première version de Rust a été écrite en Ocaml et qu’il en garde bien des aspects. Rust emprunte aussi la technique RAII (resource acquisition is initialisation) à C++, tout en l’étendant afin de pouvoir se passer de ramasse miettes (garbage collector). Un logiciel en Rust n’a donc jamais besoin de se mettre en pause pour nettoyer la mémoire utilisée. Par conséquence, une performance stable est facile à garantir. Rust oblige à déclarer quelles parties du code peuvent modifier telle valeur et le compilateur vérifie qu’aucune autre partie ne peut accéder à cette valeur pendant sa modification. Cela protège des erreurs quand on écrit du code qui va être exécuté en parallèle. C’est sûrement pour ces raisons que, depuis 2016, Rust gagne chaque année le prix du langage le plus aimé dans les sondages de StackOverflow.

Par exemple, Chucklefish utilise Rust pour développer des jeux afin d’avoir une meilleure performance pour les calculs en parallèle et afin de réduire les plantages. Ils apprécient aussi la gestion des bibliothèques tiers (Cargo) et le compilateur unique pour toutes les plateformes.

Autre exemple, Mozilla est en train de ré-implémenter Firefox en Rust. C’est loin d’être fini mais leur équipe a déjà pu remplacer le code qui applique les styles CSS aux éléments HTML par un code écrit en Rust. Cette tâche peut, en théorie, être faite en parallèle mais c’est seulement avec les garanties fournis par Rust qu’ils ont enfin pu le faire. Le résultat ? Un code bien plus rapide et plus sûr que le code existant écrit en C++.

Nous avons déjà mentionné ReasonML. Il s’agit d’une nouvelle syntaxe pour le langage Ocaml accompagné par un compilateur qui le transforme en javascript ordinaire. De ce fait, un module écrit en ReasonML peut être utilisé par du code écrit en javascript, et à l’inverse ReasonML peut facilement utiliser un module existant écrit en javascript. Le code javascript produit par le compilateur est assez lisible et souvent bien plus performant que du javascript écrit à la main car les types forts permettent certaines optimisations qui seraient difficile à réaliser à la main.

Des types forts pour se protéger des erreurs

 

Une mesure de température est juste un nombre, n’est-ce pas ? Tant que les mesures de température se font toutes en Celsius, c’est une supposition raisonnable. Cependant, dès que l’on doit aussi traiter les températures en Fahrenheit, on introduit la possibilité de se tromper et la plupart des langages de programmation ne peuvent nous protéger de ces erreurs. Voici, comment faire avec des types forts.

Quand on crée une valeur de type Temperature on doit indiquer l’unité de mesure.

La valeur numérique est étiquetée et englobée par son unité de mesure, et on ne peut plus l’utiliser directement. Pour utiliser une valeur de type Temperature on doit traiter tous les cas possibles. Exemple ci-joint.

 

 

La syntaxe “match” fonctionne comme un super “switch case”, qui oblige à traiter tous les cas. Ainsi, le jour où on ajoutera les températures en Kelvin, il suffira d’ajouter l’unité Kelvin au type Temperature et tout de suite le compilateur indiquera tous les endroits où le code devra traiter les températures en Kelvin.

Des règles métiers forment souvent un genre de machine d’état, et si on les implémente avec ce genre de type, on peut être sûr que tous les cas ont été traités. De plus, quand il faudra ajuster les règles métiers, le refactoring sera facile puisque le compilateur apporte son aide.

 

Eviter les NullReferenceException

 

Il faut souvent pouvoir gérer les valeurs manquantes. L’ennuie est qu’il est facile d’oublier de vérifier si la valeur est présente avant de l’utiliser. D’où le fameux NullReferenceException, ou en javascript “undefined is not a function”.

Les types forts peuvent nous aider via le type Option défini ainsi.

type Option<T> =
| Some(T)
| None
Le type Option peut englober n’importe quel autre type pour nous permettre d’avoir une valeur qui peut être manquante. Nous ne pourrons pas accéder à cette valeur sans avoir explicitement traité la possibilité que celle-ci soit manquante.

Conclusion

 

Ces langages puissants et modernes reprennent des idées anciennes pour des résultats impressionnants dans différents domaines. Rust pour la performance brut, F# pour les développements en .NET, et ReasonML pour les applications web.

Pour les objets connectés en particulier, Rust pourra bientôt être un choix solide pour le  développement. D’une part, parce qu’un logiciel écrit en Rust est performant et peu gourmand en mémoire, et d’autre part, parce que ce langage aide à produire un code fiable.