logo le blog invivoo blanc

Immutabilité et Javascript

21 septembre 2017 | Front-End, Java

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.

let acc = 0;

function multiply_1(a, b) {
    for(var i=0;i<a;++i) {
        sum(acc, b);
     }
}

function multiply_2(a, b) {
    let acc = 0;
    for(var i=0;i<a;++i) {
        acc = sum(acc, b);
    }
    return acc;
}


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 :

var S0 = {
   name: 'John',
   address: {
      street: 'rue de Rivoli',
      city: 'Paris'
   }
};

changeName(state, newName) {
   return {
      name: newName,
      address: state.address
   };
}
S1 = changeName(S0, 'Mary');

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é

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.