PF: L'immutabilité
tags: programmation fonctionnelle
22/08/2017 (c'était un mardi)
Rappel du sommaire
- Les fonctions en tant que paramètres
- L’immutabilité <--- vous êtes ici
- Les fonctions pures
- L’application partielle
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
etsort
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.