L'immutabilité est un concept très fortement exploité dans les langages de programmation fonctionnelle, où les valeurs des variables ne changent pas une fois attribuées. Cela a le mérite de simplifier la gestion de l'état application en réduisant le nombre de variables partagées. Comme nous allons le voir, des objets immutables rendent plus performants des frameworks MVC tels que React et AngularJS. C’est également pour ça que le mot immutabilité s'entend de plus en plus souvent dans la communauté Javascript.
Dans cet article, nous verrons les avantages et les inconvénients de l'utilisation de structures de données immutables dans du code Javascript.
Des objets immutables pour simplifier la gestion de l’état partagé
Dans l'exemple suivant, nous trouvons deux façons différentes d'implémenter une fonction multiply en réutilisant une fonction sum donnée. Dans le premier exemple, les deux fonctions partagent un accumulateur acc où les états intermédiaires du calcul sont estoqués. Dans le deuxième exemple, les fonctions ne partagent pas d'état, la variable acc est également utilisée par la fonction multiply, mais elle est privée.
L'exemple peut paraître simpliste, mais nous pouvons ainsi comparer les avantages et les inconvénients d’une approche sans état partagé (2) avec une approche avec état partagé (1)
Avantages :
Chaque fonction est plus facile à comprendre, car elle n'utilise que d'autres fonctions et des variables privées.
Le code est plus facile à paralléliser. Par exemple, des appels multiply peuvent être exécutés en parallèle, sans risque d'interférences entre threads différents
Inconvénients :
Il faut plus de mémoire. Chaque fonction utilise son accumulateur acc, la version fonctionnelle requiert donc deux fois plus de mémoire que la version impérative.
Un exemple pratique de ce genre d'approche est dans l'utilisation des frameworks de gestion d'état, tels que redux.org et Flux.
L'état de l'application est estoqué dans un seul objet, store, qui ne peut pas être modifié directement.
Pour modifier l'état de l'application, il faut une fonction, reducer, qui prend en entrée un état et une action (un objet qui décrit déclarativement une modification) et renvoie en retour l'état suivant.
Des objets immutables pour réagir à des changements d’état plus rapidement
Actuellement, la majorité des applications web complexes sont développées avec l'aide de frameworks MVC tels que React et AngularJS. Indépendamment du .. choisi, un framework MVC impose aux développeurs de séparer clairement le modèle, la view et le contrôleur de l'application. Le modèle représente l'état de UI, normalement sur forme d'un POJO . La view représente l'affichage graphique du modèle, en général sous forme de CSS et HTML. Finalement, le contrôleur représente le comportement de l'application, en général sous forme de code Javascript.
Le framework est responsable de la mise à jour en continu de la view à partir des changements provoqués par le contrôleur. L'approche naïve consiste à régénérer la view après chaque changement, ce qui est très coûteux. Pour être plus efficace, il faut être capable de mettre à jour uniquement les parties de la view qui ont changé : il faut donc être capable de détecter les modifications dans le modèle.
Pour une view mutable, pour détecter les modifications, il y a deux approches possibles :
Dirty checking, i. e. comparer récursivement tous les attributs de deux états - facile à implémenter, mais n'est pas scalable si l'état grossit.
Reactive programming / observer pattern, i. e. écouter les modifications sur chaque élément et mettre à jour la partie de la page associé, ce qui rends le comportement du framework plus difficile à comprendre.
Dans le cas d'une view immutable, l’approche la plus importante s’appelle structural sharing. On va l’illustrer avec le code suivant :
Dans cet exemple, dans l'état de l'application, on représente un nom et une adresse. La figure suivante illustre les états S0 et S1, avant et après l’appel à changeName. Pour un changement de nom, on implémente une fonction qui, au lieu de modifier un état donné, construit une nouvelle version de l'état avec le nouveau nom. Si on part du principe que l'état est un objet immutable, on peut comparer ses attributs par référence (i.e. avec ===) et donc exécuter beaucoup moins d’opérations par rapport à une comparaison récursive classique.
Pour aller plus loin
Dans cet article, nous n'avons donné qu'une vision générale de l'immutabilité, ses avantages et inconvénients. Dans cette section, vous trouverez des articles pour vous aider à approfondir vos connaissances sur le sujet et des outils, soit présents dans le langage, soit fournis par des librairies pour créer et manipuler des objets immutables.
De la lecture sur l'immutabilité
Functional Programming should be your #1 priority for 2015 - la programmation fonctionnelle, ses avantages et inconvénients
Redux Prior Art - présente d'autres approches que redux pour le management de l'état
ImmutableJS et Structural sharing - présente ImmutableJS, la librairie de structures de donnés immutables en Javascript la plus connue, et les stratégies de structural sharing en détail.
Immutable Update Patterns - quelques recommandations pour la mise à jour d'un état redux (immutable).
Boîte à outils
Support à l'immutabilité dans le langage
Support API: classe Object
assign - copier des valeurs des attributs d'un objet en changeant sélectivement les valeurs de certains
freeze, seal preventExtensions - interdire des changements de certaines propriétés des objets
Syntaxe - l'opérateur de décomposition - des opérateurs pour copier et modifier facilement le contenu d'objets.
Outils pour manipuler des POJOs et les rendre immutables
deep-freeze - recursively Object.freeze() on objects and functions.
ImmutableAssign - Lightweight immutable helper that allows you to continue working with POJO (Plain Old JavaScript Object).
immu - A TINY, fail-fast, lazy, immutable Javascript objects library.
icedam - Just-in-time immutability.
freezer - A tree data structure that emits events on updates, even if the modification is triggered by one of the leaves, making it easier to think in a reactive way.
Librairies de structures de données:
ImmutableJS - Immutable collections for JavaScript
mori - A library for using ClojureScript's persistent data structures and supporting API from the comfort of vanilla JavaScript.
icepick - Utilities for treating frozen JavaScript objects as persistent immutable collections.
typed-immutable - Immutable and structurally typed data.