Coderstand

PF: L'immutabilité

tags: programmation fonctionnelle

22/08/2017 (c'était un mardi)

Rappel du sommaire

L’immutabilité, pourquoi être figé?

L’immutabilité est un concept générique, non lié à la programmation fonctionnelle, mais très utilisé avec celle-ci.

Un objet immutable est un objet qui ne peut être modifié après sa création. Cela permet:

  • d’être thread-safe par défaut, car 2 processus ne peuvent plus altérer l’objet simultanément,
  • une optimisation plus poussée au runtime, car on peut juste comparer les pointeurs mémoires pour savoir si deux objets sont différents, il n’y a plus besoin d’aller comparer en détail les 2 objets, donc on gagne en temps d’exécution,
  • une rejouabilité totale du code, mais nous verrons cet avantage dans l’article sur les fonctions pures.

Plus d’accès direct, et c’est réglé

Non. Il y a d’autres moyens de faire muter un objet.

let objet = {
    tableau: ["a","b"]
};
objet.tableau.push("c");
// je fais muter mon tableau, sans changer son pointer en mémoire

let personne = {
  nom: 'Brice',
  age: 32
}
let autrePersonne = personne;
autrePersonne.age = 30
// personne.age = 30
console.log(personne.age) // age: 30
// j'ai fait muter personne

let tableau2 = ['Jean', 'Michel', 'ChanteMal']
let tableauTrie = tableau2.sort()
console.log(tableau2) // ["ChanteMal", "Jean", "Michel"]

Pour éviter cela, il existe plusieurs choses à faire:

  • ne pas utiliser push et sort pour les tableaux
  • créer un nouveau objet à chaque modification de propriété. Cela peut sembler lourd à écrire, mais avec la puissance d’ES2017, on peut faire

    let objet = {
    a: 'AH',
    b: 42
    }
    let nouvelObjet = { ...objet };
    nouvelObjet.b = 31;
    console.log(objet); // {a: "AH", b: 42}
    console.log(nouvelObjet); // {a: "AH", b: 31}

    et on obtient un nouvel objet, qui est la copie de l’autre. Attention cependant, cela ne fonctionne pas pour les objets en profondeur

    let objet = {
    a: {
        z: "Général Zed"
    },
    b: 42
    }
    let nouvelObjet = { ...objet };
    nouvelObjet.a.z = "Docteur Zoidberg";
    console.log(nouvelObjet); // {a: {z: "Docteur Zoidberg"}, b: 42}
    console.log(objet); // {a: {z: "Docteur Zoidberg"}, b: 42} On a changé z ici aussi

La solution est de ne plus jamais utiliser la syntaxe a.b = X pour modifier une valeur, mais de créer un nouvel objet à chaque fois

let objet = {
    a: {
        z: "Général Zed",
        x: "Génération X"
    },
    b: 42
}
// Au lieu de faire nouvelObjet.a.z = "Docteur Zoidberg";
// On crée un objet en modifiant les propriétés à la création
let nouvelObjet = {
    ...objet, // je recopie objet
    b: 31, // Et je remets une clé b qui vaut 31,
          // qui écrase le b: 42 qu'on a copié de l'objet

    a: { // j'écrase aussi a
        ...objet.a, // mais je recopie objet.a
        d: 'Dédééééé' // et je rajoute une propriété d
    }
};
console.log(JSON.stringify(nouvelObjet)); // {"a":{"z":"Général Zed","x":"Génération X","d":"Dédééééé"},"b":31}

On peut aussi faire pareil avec les tableaux

let tab = [1, 2, 3];
let tab2 = [ ...tab, 4];
// tab = [1, 2, 3];
// tab2 = [1, 2, 3, 4];

Conclusion

La syntaxe peut paraitre perturbante au début, mais on s’y fait très vite, et le code résultant est très explicite, car on voit tout de suite ce qui est rajouté/modifié lors de la recopie d’un objet. De plus, les gains d’exécution et de rejouabilité vont beaucoup nous apporter lors de l’utilisation des fonctions pures, ce dont on parlera la prochaine fois.


Brice Coquereau

Bonjour! Je suis Brice Coquereau, parisien, développeur, flexitarien, et amateur de chats. Je vais à des meetups assez souvent. Venez me parler sur Mastodon ou Twitter (mais Mastodon c'est mieux)

TwitterFacebookLinkedin