Pourquoi l'immuabilité est-elle si importante (ou nécessaire) dans JavaScript?

206

Je travaille actuellement sur les frameworks React JS et React Native . À mi-chemin, je suis tombé sur Immutability ou la bibliothèque Immutable-JS , lorsque je lisais sur l'implémentation de Flux et Redux de Facebook.

La question est, pourquoi l'immuabilité est-elle si importante? Qu'est-ce qui ne va pas dans la mutation d'objets? Cela ne simplifie-t-il pas les choses?

Pour donner un exemple, considérons une application de lecture de nouvelles simple, l'écran d'ouverture étant une vue de liste des titres de l'actualité.

Si je mets en place un tableau d'objets avec une valeur au départ, je ne peux pas le manipuler. C'est ce que dit le principe d'immuabilité, non? (Corrigez-moi si je me trompe.) Mais, que faire si j'ai un nouvel objet News qui doit être mis à jour? Dans le cas habituel, j'aurais pu simplement ajouter l'objet au tableau. Comment puis-je réaliser dans ce cas? Supprimer le magasin et le recréer? L'ajout d'un objet au tableau n'est-il pas une opération moins coûteuse?

bozzmob
la source
2
Une structure de données immuable et une fonction pure conduisent à une transparence référentielle, ce qui facilite beaucoup la réflexion sur le comportement de votre programme. Vous bénéficiez également d'un retour en arrière gratuit lorsque vous utilisez la structure de données fonctionnelle.
WorBlux
J'ai fourni un point de vue Redux @bozzmob.
prosti
1
Il peut être utile de se renseigner sur l'immurabilité en général en tant que concept de paradigme fonctionnel au lieu d'essayer de penser que JS a quelque chose à voir avec cela. React est écrit par des fans de programmation fonctionnelle. Vous devez savoir ce qu'ils savent pour les comprendre.
Gherman
Ce n'est pas nécessaire, mais cela offre de bons compromis. Mutable State est au logiciel comme les pièces mobiles au matériel
Kristian Dupont

Réponses:

196

J'ai récemment effectué des recherches sur le même sujet. Je ferai de mon mieux pour répondre à vos questions et essayer de partager ce que j'ai appris jusqu'à présent.

La question est, pourquoi l'immuabilité est-elle si importante? Qu'est-ce qui ne va pas dans la mutation d'objets? Cela ne simplifie-t-il pas les choses?

Fondamentalement, cela revient au fait que l'immuabilité augmente la prévisibilité, les performances (indirectement) et permet le suivi des mutations.

Prévisibilité

Les mutations masquent les changements, qui créent des effets secondaires (inattendus), qui peuvent provoquer des bugs désagréables. Lorsque vous appliquez l'immuabilité, vous pouvez garder votre architecture d'application et votre modèle mental simples, ce qui facilite le raisonnement sur votre application.

Performance

Même si l'ajout de valeurs à un objet immuable signifie qu'une nouvelle instance doit être créée là où les valeurs existantes doivent être copiées et de nouvelles valeurs doivent être ajoutées au nouvel objet qui coûtent de la mémoire, les objets immuables peuvent utiliser le partage structurel pour réduire la mémoire aérien.

Toutes les mises à jour renvoient de nouvelles valeurs, mais les structures internes sont partagées pour réduire considérablement l'utilisation de la mémoire (et le thrashing GC). Cela signifie que si vous ajoutez un vecteur à 1 000 éléments, il ne crée pas réellement un nouveau vecteur de 1001 éléments. Très probablement, en interne, seuls quelques petits objets sont alloués.

Vous pouvez en savoir plus à ce sujet ici .

Suivi des mutations

Outre l'utilisation réduite de la mémoire, l'immuabilité vous permet d'optimiser votre application en utilisant l'égalité de référence et de valeur. Cela permet de voir très facilement si quelque chose a changé. Par exemple, un changement d'état dans un composant React. Vous pouvez utiliser shouldComponentUpdatepour vérifier si l'état est identique en comparant les objets d'état et éviter le rendu inutile. Vous pouvez en savoir plus à ce sujet ici .

Ressources supplémentaires:

Si je mets en place un tableau d'objets avec une valeur au départ. Je ne peux pas le manipuler. C'est ce que dit le principe d'immuabilité, n'est-ce pas? (Corrigez-moi si je me trompe). Mais que se passe-t-il si j'ai un nouvel objet News à mettre à jour? Dans le cas habituel, j'aurais pu simplement ajouter l'objet au tableau. Comment puis-je réaliser dans ce cas? Supprimer le magasin et le recréer? L'ajout d'un objet au tableau n'est-il pas une opération moins coûteuse?

Oui c'est correct. Si vous ne savez pas comment implémenter cela dans votre application, je vous recommanderais de voir comment redux fait cela pour vous familiariser avec les concepts de base, cela m'a beaucoup aidé.

J'aime utiliser Redux comme exemple car il englobe l'immuabilité. Il a un seul arbre d'état immuable (appelé store) où tous les changements d'état sont explicites en envoyant des actions qui sont traitées par un réducteur qui accepte l'état précédent avec lesdites actions (une à la fois) et renvoie l'état suivant de votre application . Vous pouvez en savoir plus sur ses principes fondamentaux ici .

Il y a un excellent cours de redux sur egghead.ioDan Abramov , l'auteur de redux, explique ces principes comme suit (j'ai modifié un peu le code pour mieux l'adapter au scénario):

import React from 'react';
import ReactDOM from 'react-dom';

// Reducer.
const news = (state=[], action) => {
  switch(action.type) {
    case 'ADD_NEWS_ITEM': {
      return [ ...state, action.newsItem ];
    }
    default: {
        return state;
    }
  }
};

// Store.
const createStore = (reducer) => {
  let state;
  let listeners = [];

  const subscribe = (listener) => {
    listeners.push(listener);

    return () => {
      listeners = listeners.filter(cb => cb !== listener);
    };
  };

  const getState = () => state;

  const dispatch = (action) => {
    state = reducer(state, action);
    listeners.forEach( cb => cb() );
  };

  dispatch({});

  return { subscribe, getState, dispatch };
};

// Initialize store with reducer.
const store = createStore(news);

// Component.
const News = React.createClass({
  onAddNewsItem() {
    const { newsTitle } = this.refs;

    store.dispatch({
      type: 'ADD_NEWS_ITEM',
      newsItem: { title: newsTitle.value }
    });
  },

  render() {
    const { news } = this.props;

    return (
      <div>
        <input ref="newsTitle" />
        <button onClick={ this.onAddNewsItem }>add</button>
        <ul>
          { news.map( ({ title }) => <li>{ title }</li>) }
        </ul>
      </div>
    );
  }
});

// Handler that will execute when the store dispatches.
const render = () => {
  ReactDOM.render(
    <News news={ store.getState() } />,
    document.getElementById('news')
  );
};

// Entry point.
store.subscribe(render);
render();

De plus, ces vidéos montrent plus en détail comment atteindre l'immuabilité pour:

danillouz
la source
1
@naomik merci pour la rétroaction! Mon intention était d'illustrer le concept et de montrer explicitement que les objets ne sont pas mutés et pas nécessairement de montrer comment l'implémenter complètement. Cependant, mon exemple peut être un peu déroutant, je le mettrai à jour dans un peu.
danillouz
2
@bozzmob vous êtes les bienvenus! Non, ce n'est pas correct, vous devez imposer vous-même l'immuabilité dans le réducteur. Cela signifie que vous pouvez suivre des stratégies comme illustré dans les vidéos ou utiliser une bibliothèque comme immutablejs. Vous pouvez trouver plus d'informations ici et ici .
danillouz
1
@naomik ES6 constn'est pas une question d'immuabilité. Mathias Bynens a écrit un excellent article de blog à ce sujet.
Lea Rosema
1
@terabaud merci d'avoir partagé le lien. Je suis d'accord que c'est une distinction importante. ^ _ ^
Merci
4
Veuillez expliquer ceci "Les mutations masquent les changements, qui créent des effets secondaires (inattendus), qui peuvent provoquer des bugs désagréables. Lorsque vous imposez l'immuabilité, vous pouvez garder votre architecture d'application et votre modèle mental simples, ce qui facilite le raisonnement de votre application." Parce que ce n'est pas vrai du tout dans le contexte de JavaScript.
Pavle Lekic
143

Une vision contraire de l'immuabilité

TL / DR: l'immuabilité est plus une tendance de la mode qu'une nécessité en JavaScript. Si vous utilisez React, il fournit une solution de contournement soignée à certains choix de conception confus dans la gestion des états. Cependant, dans la plupart des autres situations, il n'ajoutera pas suffisamment de valeur à la complexité qu'il introduit, servant davantage à remplir un CV qu'à répondre à un besoin réel du client.

Réponse longue: lire ci-dessous.

Pourquoi l'immuabilité est-elle si importante (ou nécessaire) en javascript?

Eh bien, je suis content que vous ayez demandé!

Il y a quelque temps, un gars très talentueux appelé Dan Abramov a écrit une bibliothèque de gestion d'état javascript appelée Redux qui utilise des fonctions pures et l'immuabilité. Il a également fait des vidéos vraiment cool qui ont rendu l'idée très facile à comprendre (et à vendre).

Le timing était parfait. La nouveauté d' Angular était en train de disparaître, et le monde JavaScript était prêt à se concentrer sur la dernière chose qui avait le bon degré de fraîcheur, et cette bibliothèque était non seulement innovante mais parfaitement intégrée à React qui était colporté par une autre centrale électrique de la Silicon Valley .

Aussi triste que cela puisse être, la mode règne dans le monde de JavaScript. Maintenant, Abramov est salué comme un demi-dieu et nous, tous les simples mortels, devons nous soumettre au Dao de l'immuabilité ... Que cela ait un sens ou non.

Qu'est-ce qui ne va pas dans la mutation d'objets?

Rien!

En fait, les programmeurs mutent des objets depuis heu ... tant qu'il y a des objets à muter. Autrement dit, plus de 50 ans de développement d'applications.

Et pourquoi compliquer les choses? Lorsque vous avez un objet catet qu'il meurt, avez-vous vraiment besoin d'une seconde catpour suivre le changement? La plupart des gens diraient cat.isDead = trueet en finiraient avec.

(Les objets en mutation) ne simplifient-ils pas les choses?

OUI! .. Bien sûr que oui!

Spécialement en JavaScript, qui en pratique est le plus utile pour rendre une vue d'un état qui est maintenu ailleurs (comme dans une base de données).

Que faire si j'ai un nouvel objet News à mettre à jour? ... Comment puis-je réaliser dans ce cas? Supprimer le magasin et le recréer? L'ajout d'un objet au tableau n'est-il pas une opération moins coûteuse?

Eh bien, vous pouvez adopter l'approche traditionnelle et mettre à jour l' Newsobjet, de sorte que votre représentation en mémoire de cet objet change (et la vue affichée pour l'utilisateur, ou du moins on pourrait l'espérer) ...

Ou bien...

Vous pouvez essayer l'approche sexy FP / Immuabilité et ajouter vos modifications à l' Newsobjet à un tableau suivant chaque changement historique afin que vous puissiez ensuite parcourir le tableau et déterminer quelle devrait être la représentation d'état correcte (ouf!).

J'essaie d'apprendre ce qui est juste ici. Veuillez m'éclairer :)

Les modes vont et viennent copain. Il existe de nombreuses façons de peiner un chat.

Je suis désolé que vous ayez à supporter la confusion d'un ensemble de paradigmes de programmation en constante évolution. Mais bon, BIENVENUE AU CLUB !!

Maintenant, quelques points importants à retenir en ce qui concerne l'immuabilité, et vous les recevrez avec une intensité fébrile que seule la naïveté peut rassembler.

1) L'immuabilité est géniale pour éviter les conditions de course dans des environnements multi-threads .

Les environnements multithreads (comme C ++, Java et C #) sont coupables de la pratique du verrouillage des objets lorsque plusieurs threads veulent les changer. C'est mauvais pour les performances, mais meilleur que l'alternative de la corruption de données. Et pourtant pas aussi bon que de rendre tout immuable (Seigneur loue Haskell!).

MAIS HÉLAS! En JavaScript, vous opérez toujours sur un seul thread . Même les travailleurs Web (chacun s'exécute dans un contexte distinct ). Donc, comme vous ne pouvez pas avoir de condition de concurrence liée au thread dans votre contexte d'exécution (toutes ces belles variables globales et fermetures), le point principal en faveur de l'immuabilité sort par la fenêtre.

(Cela dit, il y a un avantage à utiliser des fonctions pures dans les travailleurs Web, c'est que vous n'aurez aucune attente à jouer avec des objets sur le thread principal.)

2) L'immuabilité peut (en quelque sorte) éviter les conditions de concurrence dans l'état de votre application.

Et voici le véritable nœud de la question, la plupart des développeurs (React) vous diront que l'immuabilité et la FP peuvent en quelque sorte opérer cette magie qui permet à l'état de votre application de devenir prévisible.

Bien sûr, cela ne signifie pas que vous pouvez éviter les conditions de concurrence dans la base de données , pour retirer celle-ci, vous devrez coordonner tous les utilisateurs dans tous les navigateurs , et pour cela, vous aurez besoin d'une technologie de back-end push comme WebSockets ( plus d'informations ci-dessous) qui diffusera les modifications à tous ceux qui exécutent l'application.

Cela ne signifie pas non plus qu'il existe un problème inhérent à JavaScript où l'état de votre application a besoin d'immutabilité pour devenir prévisible, tout développeur qui a codé des applications frontales avant React vous le dirait.

Cette affirmation plutôt confuse signifie simplement qu'avec React, votre état d'application deviendra plus sujet aux conditions de concurrence , mais cette immuabilité vous permet de supprimer cette douleur. Pourquoi? Parce que React est spécial. Il a été conçu comme une bibliothèque de rendu hautement optimisée avec une gestion d'état cohérente en deuxième position, et donc l'état des composants est géré via une chaîne d'événements asynchrone (alias "liaison de données à sens unique") que vous n'avez aucun contrôle et comptez sur vous pour ne pas muter directement l'état ...

Dans ce contexte, il est facile de voir comment le besoin d'immuabilité a peu à voir avec JavaScript et beaucoup à voir avec les conditions de concurrence dans React: si vous avez un tas de changements interdépendants dans votre application et aucun moyen facile de comprendre ce que votre état est actuellement, vous allez être confus , et il est donc parfaitement logique d'utiliser l'immuabilité pour suivre chaque changement historique .

3) Les conditions de course sont catégoriquement mauvaises.

Eh bien, ils pourraient l'être si vous utilisez React. Mais ils sont rares si vous choisissez un cadre différent.

En plus, vous avez normalement problèmes bien plus importants à régler… Des problèmes comme l'enfer de la dépendance. Comme une base de code gonflée. Comme votre CSS ne se charge pas. Comme un processus de construction lent ou être coincé dans un back-end monolithique qui rend l'itération presque impossible. Comme des développeurs inexpérimentés qui ne comprennent pas ce qui se passe et qui font des dégâts.

Tu sais. Réalité. Mais bon, qui s'en soucie?

4) L'immuabilité utilise des types de référence pour réduire l'impact sur les performances du suivi de chaque changement d'état.

Parce que sérieusement, si vous allez copier des choses à chaque fois que votre état change, vous feriez mieux de vous assurer que vous êtes intelligent à ce sujet.

5) L'immuabilité vous permet d'annuler des choses .

Parce que euh… c'est la fonctionnalité numéro un que votre chef de projet va demander, non?

6) L'état immuable a beaucoup de potentiel cool en combinaison avec WebSockets

Enfin, l'accumulation de deltas d'état constitue un cas assez convaincant en combinaison avec WebSockets, ce qui permet une consommation facile de l' état en tant que flux d'événements immuables ...

Une fois que le sou tombe sur ce concept (l'état étant un flux d'événements - plutôt qu'un ensemble brut de documents représentant la dernière vue), le monde immuable devient un endroit magique à habiter. Une terre d' émerveillement événementiel et de possibilité qui transcende le temps lui-même . Et quand bien fait cela peut certainement faire en temps réel applications EASI er à accomplir, vous venez de diffuser le flux d'événements à toutes les personnes intéressées afin qu'ils puissent construire leur propre représentation du présent et écrire de nouveau leurs propres changements dans le flux communal.

Mais à un moment donné, vous vous réveillez et réalisez que toute cette merveille et cette magie ne viennent pas gratuitement. Contrairement à vos collègues passionnés, vos parties prenantes (oui, les gens qui vous paient) se soucient peu de la philosophie ou de la mode et beaucoup de l'argent qu'ils paient pour construire un produit qu'ils peuvent vendre. Et l'essentiel est qu'il est plus difficile d'écrire du code immuable et plus facile à le casser, et il n'y a pas grand intérêt à avoir un front-end immuable si vous n'avez pas de back-end pour le prendre en charge. Lorsque (et si!) Vous finissez par convaincre vos parties prenantes que vous devez publier et consommer des événements via une technologie push comme WebSockets, vous découvrez à quel point il est difficile de faire évoluer la production .


Maintenant, pour quelques conseils, si vous choisissez de l'accepter.

Le choix d'écrire du JavaScript à l'aide de FP / Immutability est également un choix pour rendre la base de code de votre application plus grande, plus complexe et plus difficile à gérer. Je plaiderais fortement pour limiter cette approche à vos réducteurs Redux, à moins que vous ne sachiez ce que vous faites ... Et SI vous allez continuer et utiliser l'immuabilité malgré tout, puis appliquer un état immuable à l'ensemble de votre pile d'applications , et pas seulement le côté client, car vous en manquez la valeur réelle autrement.

Maintenant, si vous avez la chance de pouvoir faire des choix dans votre travail, essayez d'utiliser votre sagesse (ou non) et faites ce qui est bien pour la personne qui vous paie . Vous pouvez baser cela sur votre expérience, sur votre instinct ou sur ce qui se passe autour de vous (certes, si tout le monde utilise React / Redux, il existe un argument valable selon lequel il sera plus facile de trouver une ressource pour continuer votre travail). Alternativement, vous pouvez essayer les approches Resume Driven Development ou Hype Driven Development . Ils pourraient être plus votre genre de chose.

En bref, la chose à dire pour immuabilité est qu'il va vous faire la mode avec vos pairs, au moins jusqu'à la prochaine folie arrive, par quel point vous serez heureux de passer à autre chose.


Maintenant, après cette session d'auto-thérapie, je voudrais souligner que j'ai ajouté cela en tant qu'article dans mon blog => Immuabilité en JavaScript: une vue opposée . N'hésitez pas à y répondre si vous avez des sensations fortes que vous aimeriez aussi retirer de votre poitrine;).

Steven de Salas
la source
10
Bonjour Steven, oui. J'ai eu tous ces doutes quand j'ai considéré immutable.js et redux. Mais, votre réponse est incroyable! Cela ajoute beaucoup de valeur et merci d'avoir abordé chaque point sur lequel j'avais des doutes. C'est tellement plus clair / meilleur maintenant même après avoir travaillé pendant des mois sur des objets immuables.
bozzmob
5
J'utilise React avec Flux / Redux depuis plus de deux ans et je ne pourrais pas être plus d'accord avec vous, excellente réponse!
Pavle Lekic
6
Je soupçonne fortement que les vues sur l'immuabilité sont assez bien corrélées aux tailles d'équipe et de base de code, et je ne pense pas que ce soit une coïncidence si le principal promoteur est un géant de la Silicon Valley. Cela étant dit, je suis en désaccord avec respect: l'immuabilité est une discipline utile comme ne pas utiliser goto est une discipline utile. Ou des tests unitaires. Ou TDD. Ou analyse de type statique. Cela ne signifie pas que vous les faites tout le temps, à chaque fois (bien que certains le fassent). Je dirais également que l'utilité est orthogonale au battage médiatique: dans une matrice utile / superflue et sexy / ennuyeuse, il y a beaucoup d'exemples de chacun. "hyped" !== "bad"
Jared Smith
4
Salut @ftor, bon point, aller trop loin dans l'autre sens. Cependant, comme il y a une telle profusion d'articles et d'arguments «pro-immuabilité en javascript», j'ai senti que je devais équilibrer les choses. Les débutants ont donc un point de vue opposé pour les aider à porter un jugement de toute façon.
Steven de Salas
4
Informatif et titré avec brio. Jusqu'à ce que je trouve cette réponse, je pensais que j'étais le seul à avoir une opinion similaire. Je reconnais la valeur de l'immuabilité, mais ce qui me dérange, c'est qu'il est devenu un tel dogme oppressant pour toutes les autres techniques (par exemple au détriment de la liaison bidirectionnelle qui est incroyablement utile pour le formatage d'entrée tel qu'implémenté dans KnockoutJS par exemple).
Tyblitz
53

La question est, pourquoi l'immuabilité est-elle si importante? Qu'est-ce qui ne va pas dans la mutation d'objets? Cela ne simplifie-t-il pas les choses?

En fait, le contraire est vrai: la mutabilité rend les choses plus compliquées, du moins à long terme. Oui, cela facilite votre codage initial car vous pouvez simplement changer les choses où vous voulez, mais lorsque votre programme s'agrandit, cela devient un problème - si une valeur a changé, qu'est-ce qui l'a changé?

Lorsque vous rendez tout immuable, cela signifie que les données ne peuvent plus être modifiées par surprise. Vous savez avec certitude que si vous passez une valeur dans une fonction, elle ne peut pas être modifiée dans cette fonction.

En termes simples: si vous utilisez des valeurs immuables, il est très facile de raisonner sur votre code: tout le monde obtient une copie unique * de vos données, il ne peut donc pas les utiliser et casser d'autres parties de votre code. Imaginez à quel point cela facilite le travail dans un environnement multithread!

Remarque 1: il y a un coût potentiel de performance à l'immuabilité en fonction de ce que vous faites, mais des choses comme Immutable.js optimisent du mieux qu'elles peuvent.

Remarque 2: dans le cas improbable où vous n'êtes pas sûr, Immutable.js et ES6 constsignifient des choses très différentes.

Dans le cas habituel, j'aurais pu simplement ajouter l'objet au tableau. Comment puis-je réaliser dans ce cas? Supprimer le magasin et le recréer? L'ajout d'un objet au tableau n'est-il pas une opération moins coûteuse? PS: Si l'exemple n'est pas la bonne façon d'expliquer l'immuabilité, veuillez me faire savoir quel est le bon exemple pratique.

Oui, votre exemple d'actualité est parfaitement bon, et votre raisonnement est tout à fait juste: vous ne pouvez pas simplement modifier votre liste existante, vous devez donc en créer une nouvelle:

var originalItems = Immutable.List.of(1, 2, 3);
var newItems = originalItems.push(4, 5, 6);
TwoStraws
la source
1
Je ne suis pas en désaccord avec cette réponse, mais elle ne répond pas à sa partie "Je voudrais apprendre d'un exemple pratique" de la question. On pourrait faire valoir qu'une seule référence à la liste des en-têtes de nouvelles utilisés dans plusieurs domaines est une bonne chose. "Je n'ai qu'à mettre à jour la liste une seule fois et tout ce qui fait référence à la liste des actualités est mis à jour gratuitement" - je pense qu'une meilleure réponse prendrait un problème commun comme il l'a présenté, et montrerait une alternative valable qui utilise l'immuabilité.
Merci
1
Je suis content que la réponse ait été utile! En ce qui concerne votre nouvelle question: n'essayez pas de deviner le système :) Dans ce cas précis, quelque chose appelé «partage structurel» réduit considérablement le thrashing du GC - si vous avez 10 000 éléments dans une liste et en ajoutez 10 de plus, je crois immuable. js essaiera de réutiliser la structure précédente du mieux qu'il peut. Laissez Immutable.js s'inquiéter de la mémoire et il y a de fortes chances que vous la trouviez meilleure.
TwoStraws
6
Imagine how much easier this makes working in a multi-threaded environment!-> Ok pour les autres langues mais ce n'est pas un avantage en JavaScript mono-thread.
Steven de Salas
1
@StevendeSalas note que JavaScript est principalement asynchrone et piloté par les événements. Il n'est pas du tout à l'abri des conditions de course.
Jared Smith
1
@JaredSmith reste mon point. FP et Immutability sont de puissants paradigmes utiles pour éviter la corruption des données et / ou les verrous de ressources dans les environnements multithreads, mais pas en JavaScript car son unique thread. À moins que je ne manque une sainte pépite de sagesse, le principal compromis est de savoir si vous êtes prêt à vous rendre le code plus complexe (et plus lent) dans une quête pour éviter les conditions de course ... qui sont beaucoup moins problématiques que la plupart des gens pense.
Steven de Salas
37

Bien que les autres réponses soient correctes, pour répondre à votre question sur un cas d'utilisation pratique (à partir des commentaires sur les autres réponses), laissez sortir votre code de course pendant une minute et regardez la réponse omniprésente sous votre nez: git . Que se passerait-il si chaque fois que vous poussiez un commit, vous écrasiez les données dans le référentiel?

Maintenant, nous sommes confrontés à l'un des problèmes auxquels les collections immuables sont confrontées: le gonflement de la mémoire. Git est assez intelligent pour ne pas simplement faire de nouvelles copies de fichiers à chaque fois que vous effectuez une modification, il garde simplement une trace des différences .

Bien que je ne sache pas grand-chose sur le fonctionnement interne de git, je peux seulement supposer qu'il utilise une stratégie similaire à celle des bibliothèques que vous référencez: le partage structurel. Sous le capot, les bibliothèques utilisent des essais ou d'autres arbres pour suivre uniquement les nœuds différents.

Cette stratégie est également raisonnablement performante pour les structures de données en mémoire car il existe des algorithmes d'opération d'arbre bien connus qui fonctionnent en temps logarithmique.

Autre cas d'utilisation: dites que vous voulez un bouton Annuler sur votre webapp. Avec des représentations immuables de vos données, leur mise en œuvre est relativement triviale. Mais si vous comptez sur la mutation, cela signifie que vous devez vous soucier de la mise en cache de l'état du monde et des mises à jour atomiques.

En bref, il y a un prix à payer pour l'immuabilité dans les performances d'exécution et la courbe d'apprentissage. Mais tout programmeur expérimenté vous dira que le temps de débogage l'emporte sur le temps d'écriture de code d'un ordre de grandeur. Et le léger impact sur les performances d'exécution est probablement compensé par les bogues liés à l'état que vos utilisateurs n'ont pas à supporter.

Jared Smith
la source
1
Un brillant exemple que je dis. Ma compréhension de l'immuabilité est plus claire maintenant. Merci Jared. En fait, l'une des implémentations est le bouton UNDO: D Et vous avez rendu les choses assez simples pour moi.
bozzmob
3
Ce n'est pas parce qu'un motif fait sens dans git que la même chose a du sens partout. Dans git, vous vous souciez réellement de toute l'histoire stockée et vous voulez pouvoir fusionner différentes branches. En frontend, vous ne vous souciez pas de la majeure partie de l'histoire de l'État et vous n'avez pas besoin de toute cette complexité.
Ski
2
@Ski c'est seulement complexe car ce n'est pas la valeur par défaut. Je n'utilise généralement pas mori ou immutable.js dans mes projets: j'hésite toujours à accepter des deps tiers. Mais si c'était la valeur par défaut (a la clojurescript) ou avait au moins une option native opt-in, je l'emploierais tout le temps, parce que lorsque je programme par exemple en clojure, je ne bourre pas tout immédiatement en atomes.
Jared Smith
Joe Armstrong dirait ne vous inquiétez pas de la performance, attendez quelques années et la loi de Moore s'en chargera pour vous.
ximo
1
@JaredSmith Vous avez raison, les choses ne font que devenir plus petites et plus limitées en ressources. Je ne sais pas si ce sera le facteur limitant pour JavaScript. Nous continuons à trouver de nouvelles façons d'améliorer les performances (Svelte par exemple). Soit dit en passant, je suis entièrement d'accord avec votre autre commentaire. La complexité ou la difficulté d'utiliser des structures de données immuables se résume souvent au fait que le langage n'a pas de support intégré pour le concept. Clojure rend l'immuabilité simple car il est intégré dans la langue, toute la langue a été conçue autour de l'idée.
ximo
8

La question est, pourquoi l'immuabilité est-elle si importante? Qu'est-ce qui ne va pas dans la mutation d'objets? Cela ne simplifie-t-il pas les choses?

À propos de la mutabilité

Rien de mal à la mutabilité du point de vue technique. C'est rapide, c'est la réutilisation de la mémoire. Les développeurs y sont habitués depuis le début (si je me souviens bien). Il existe un problème dans l'utilisation de la mutabilité et des problèmes que cette utilisation peut entraîner.

Si l'objet n'est partagé avec rien, par exemple existe dans le champ d'application de la fonction et n'est pas exposé à l'extérieur, il est difficile de voir les avantages de l'immuabilité. Vraiment dans ce cas, cela n'a aucun sens d'être immuable. Le sentiment d'immuabilité commence quand quelque chose est partagé.

Maux de tête de mutabilité

Une structure partagée mutable peut facilement créer de nombreux pièges. Tout changement dans n'importe quelle partie du code avec accès à la référence a un impact sur d'autres parties avec visibilité de cette référence. Un tel impact relie toutes les pièces entre elles, même lorsqu'elles ne doivent pas être conscientes des différents modules. La mutation dans une fonction peut planter une partie totalement différente de l'application. Une telle chose est un mauvais effet secondaire.

Ensuite, souvent, le problème de mutation est l'état corrompu. Un état corrompu peut se produire lorsque la procédure de mutation échoue au milieu et que certains champs ont été modifiés et d'autres non.

De plus, avec la mutation, il est difficile de suivre le changement. Une simple vérification des références ne montrera pas la différence, pour savoir ce qui a changé, une vérification approfondie doit être effectuée. Pour surveiller le changement, un modèle observable doit également être introduit.

Enfin, la mutation est à l'origine du déficit de confiance. Comment vous pouvez être sûr qu'une certaine structure a voulu une valeur, si elle peut être mutée.

const car = { brand: 'Ferrari' };
doSomething(car);
console.log(car); // { brand: 'Fiat' }

Comme le montre l'exemple ci-dessus, le passage d'une structure mutable peut toujours se terminer par une structure différente. La fonction doSomething mute l'attribut donné de l'extérieur. Aucune confiance pour le code, vous ne savez vraiment pas ce que vous avez et ce que vous aurez. Tous ces problèmes surviennent parce que: Les structures mutables représentent des pointeurs vers la mémoire.

L'immuabilité est une question de valeurs

L'immuabilité signifie que le changement ne se fait pas sur le même objet, la même structure, mais le changement est représenté dans un nouveau. Et c'est parce que la référence représente la valeur non seulement le pointeur de mémoire. Chaque changement crée une nouvelle valeur et ne touche pas l'ancien. De telles règles claires redonnent confiance et prévisibilité du code. Les fonctions sont sûres à utiliser car au lieu de la mutation, elles traitent de leurs propres versions avec leurs propres valeurs.

L'utilisation de valeurs au lieu de conteneurs de mémoire donne la certitude que chaque objet représente une valeur immuable spécifique et il est sûr de l'utiliser.

Les structures immuables représentent des valeurs.

Je plonge encore plus dans le sujet dans un article moyen - https://medium.com/@macsikora/the-state-of-immutability-169d2cd11310

Maciej Sikora
la source
6

Pourquoi l'immuabilité est-elle si importante (ou nécessaire) dans JavaScript?

L'immuabilité peut être suivie dans différents contextes, mais le plus important serait de la suivre par rapport à l'état de l'application et à l'interface utilisateur de l'application.

Je considérerai le modèle JavaScript Redux comme une approche très tendance et moderne et parce que vous l'avez mentionné.

Pour l'interface utilisateur, nous devons la rendre prévisible . Il sera prévisible si UI = f(application state).

Les applications (en JavaScript) changent l'état via des actions implémentées à l'aide de la fonction de réduction .

La fonction de réduction prend simplement l'action et l'ancien état et renvoie le nouvel état, en gardant l'ancien état intact.

new state  = r(current state, action)

entrez la description de l'image ici

L'avantage est: vous voyagez dans le temps les états puisque tous les objets d'état sont enregistrés, et vous pouvez rendre l'application dans n'importe quel état depuis UI = f(state)

Vous pouvez donc annuler / rétablir facilement.


Il se trouve que la création de tous ces états peut encore être efficace en mémoire, une analogie avec Git est excellente, et nous avons l'analogie similaire dans Linux OS avec des liens symboliques (basés sur les inodes).

prosti
la source
5

Un autre avantage de l'immuabilité en Javascript est qu'il réduit le couplage temporel, ce qui présente des avantages substantiels pour la conception en général. Considérez l'interface d'un objet avec deux méthodes:

class Foo {

      baz() {
          // .... 
      }

      bar() {
          // ....
      }

}

const f = new Foo();

Il se peut qu'un appel à baz()soit requis pour que l'objet soit dans un état valide pour qu'un appel bar()fonctionne correctement. Mais comment savez-vous cela?

f.baz();
f.bar(); // this is ok

f.bar();
f.baz(); // this blows up

Pour le comprendre, vous devez examiner les internes de la classe car cela ne ressort pas immédiatement de l'examen de l'interface publique. Ce problème peut exploser dans une grande base de code avec de nombreux états et classes mutables.

Si Fooest immuable, ce n'est plus un problème. Il est sûr de supposer que nous pouvons appeler bazou bardans n'importe quel ordre car l'état interne de la classe ne peut pas changer.

fantômes
la source
4

Il était une fois un problème de synchronisation des données entre les threads. Ce problème était une grande douleur, il y avait 10+ solutions. Certaines personnes ont essayé de le résoudre radicalement. C'était un endroit où la programmation fonctionnelle était née. C'est comme le marxisme. Je ne pouvais pas comprendre comment Dan Abramov a vendu cette idée à JS, car elle est monothread. C'est un génie.

Je peux donner un petit exemple. Il y a un attribut __attribute__((pure))dans gcc. Les compilateurs essaient de déterminer si votre fonction est pure ou non si vous ne la supprimez pas spécialement. Votre fonction peut être pure même si votre état est modifiable. L'immuabilité n'est que l'une des 100+ façons de garantir que votre fonction sera pure. En fait, 95% de vos fonctions seront pures.

Vous ne devriez pas utiliser de limitations (comme l'immuabilité) si vous n'avez en fait aucune raison sérieuse. Si vous souhaitez "Annuler" un état, vous pouvez créer des transactions. Si vous souhaitez simplifier les communications, vous pouvez envoyer des événements avec des données immuables. C'est comme tu veux.

J'écris ce message de la République post-marxiste. Je suis sûr que la radicalisation d'une idée est une mauvaise façon.

puchu
la source
Le troisième paragraphe est tellement logique. Merci pour ça. 'Si vous voulez "annuler" un état, vous pouvez créer des transactions' !!
bozzmob
La comparaison avec le marxisme peut également être faite pour la POO, soit dit en passant. Rappelez-vous Java? Heck, les petits bouts de Java en JavaScript? Le battage médiatique n'est jamais bon, il provoque la radicalisation et la polarisation. Historiquement, la POO était beaucoup plus médiatisée que celle de Facebook sur Redux. Bien qu'ils aient certainement fait de leur mieux.
ximo
4

Une prise différente ...

Mon autre réponse aborde la question d'un point de vue très pratique et je l'aime toujours. J'ai décidé d'ajouter ceci comme une autre réponse plutôt que comme un addendum à celle-ci parce que c'est une diatribe philosophique ennuyeuse qui, espérons-le, répond également à la question, mais ne correspond pas vraiment à ma réponse actuelle.

TL; DR

Même dans les petits projets, l'immuabilité peut être utile, mais ne partez pas du principe qu'elle existe pour vous.

Réponse beaucoup, beaucoup plus longue

REMARQUE: aux fins de cette réponse, j'utilise le mot «discipline» pour signifier le renoncement à soi-même pour un certain bénéfice.

La forme est similaire à une autre question: "Dois-je utiliser Typescript? Pourquoi les types sont-ils si importants en JavaScript?". Il a également une réponse similaire. Considérez le scénario suivant:

Vous êtes le seul auteur et mainteneur d'une base de code JavaScript / CSS / HTML d'environ 5000 lignes. Votre patron semi-technique lit quelque chose à propos de Typescript-as-the-new-hotness et suggère que nous pouvons vouloir y aller mais vous laisse la décision. Vous lisez donc à ce sujet, jouez avec, etc.

Alors maintenant, vous avez un choix à faire, passez-vous à Typescript?

Typescript présente des avantages convaincants: intellisense, détection précoce des erreurs, spécification initiale de vos API, facilité de correction lors de la refactorisation les casse, moins de tests. Typescript a également certains coûts: certains idiomes JavaScript très naturels et corrects peuvent être difficiles à modéliser dans son système de type pas particulièrement puissant, les annotations augmentent la LoC, le temps et l'effort de réécrire la base de code existante, une étape supplémentaire dans le pipeline de construction, etc. Plus fondamentalement, il découpe un sous - ensemble de programmes JavaScript corrects possibles en échange de la promesse que votre code est plus susceptible d'être correct. C'est arbitrairement restrictif. C'est tout le problème: vous imposez une discipline qui vous limite (avec un peu de chance de vous tirer une balle dans le pied).

Revenons à la question, reformulée dans le contexte du paragraphe ci-dessus: cela en vaut-il la peine ?

Dans le scénario décrit, je dirais que si vous êtes très familier avec une base de code JS petite à moyenne, le choix d'utiliser Typescript est plus esthétique que pratique. Et c'est bien , il n'y a rien de mal à l'esthétique, ils ne sont tout simplement pas nécessairement convaincants.

Scénario B:

Vous changez d'emploi et êtes maintenant programmeur métier chez Foo Corp. Vous travaillez avec une équipe de 10 personnes sur une base de code JavaScript / HTML / CSS 90000 LoC (et en comptant) avec un pipeline de construction assez compliqué impliquant babel, webpack , une suite de polyfills, réagissent avec divers plugins, un système de gestion d'état, ~ 20 bibliothèques tierces, ~ 10 bibliothèques internes, des plugins d'édition comme un linter avec des règles pour un guide de style interne, etc. etc.

À l'époque où vous étiez un gars / fille LoC 5k, cela n'avait pas beaucoup d'importance. Même la documentation n'était pas si grave, même en revenant à une partie particulière du code après 6 mois, vous pouviez le comprendre assez facilement. Mais maintenant, la discipline n'est pas seulement agréable mais nécessaire . Cette discipline ne peut impliquer Tapuscrit, mais va probablement impliquer une certaine forme d'analyse statique ainsi que toutes les autres formes de discipline de codage (documentation, guide de style, de créer des scripts, les tests de régression, CI). La discipline n'est plus un luxe , c'est une nécessité .

Tout cela s'appliquait GOTOen 1978: votre petit jeu de blackjack dinky en C pourrait utiliser la GOTOlogique s et spaghetti et ce n'était pas si grave de choisir votre propre aventure à votre guise, mais à mesure que les programmes devenaient plus grands et plus ambitieux, bien, indisciplinée utilisation de GOTOne pouvait être maintenue. Et tout cela s'applique à l'immuabilité aujourd'hui.

Tout comme les types statiques, si vous ne travaillez pas sur une grande base de code avec une équipe d'ingénieurs qui la maintient / l'étend, le choix d'utiliser l'immuabilité est plus esthétique que pratique: ses avantages sont toujours là mais peuvent ne pas encore l'emporter sur les coûts.

Mais comme pour toutes les disciplines utiles, il arrive un moment où il n'est plus facultatif. Si je veux maintenir un poids santé, la discipline concernant la crème glacée peut être facultative. Mais si je veux être un athlète de compétition, mon choix de manger ou non des glaces est subsumé par mon choix d'objectifs. Si vous voulez changer le monde avec un logiciel, l'immuabilité peut faire partie de ce dont vous avez besoin pour éviter qu'il ne s'effondre sous son propre poids.

Jared Smith
la source
1
+1 J'aime ça. Beaucoup plus sur le point Jared. Et pourtant l'immuabilité ne sauvera pas une équipe de son propre manque de discipline. 😉
Steven de Salas
@StevendeSalas est une forme de discipline. Et en tant que tel, je pense qu'il est en corrélation avec (mais ne remplace pas) les autres formes de discipline du génie logiciel. Il complète plutôt qu'il ne supplante. Mais comme je l'ai dit dans un commentaire sur votre réponse, je ne suis pas surpris du tout que cela soit poussé par un géant de la technologie avec un groupe d'ingénieurs travaillant tous sur la même énorme base de code :) ils ont besoin de toute la discipline qu'ils peuvent obtenir. Pour la plupart, je ne mute pas d'objets, mais je n'utilise aucune forme d'application, car c'est juste moi.
Jared Smith
0

J'ai créé une librairie agnostique open source (MIT) pour un état mutable (ou immuable) qui peut remplacer tous ces stockage immuable comme les libs (redux, vuex etc ...).

Les états immuables étaient laids pour moi car il y avait trop de travail à faire (beaucoup d'actions pour de simples opérations de lecture / écriture), le code était moins lisible et les performances des grands ensembles de données n'étaient pas acceptables (restitution complète des composants: /).

Avec l'observateur d'état profond, je ne peux mettre à jour qu'un seul nœud avec la notation par points et utiliser des caractères génériques. Je peux également créer un historique de l'état (annuler / rétablir / voyager dans le temps) en conservant uniquement les valeurs concrètes qui ont été modifiées {path:value}= moins d'utilisation de la mémoire.

Avec l'observateur d'état profond, je peux affiner les choses et j'ai le contrôle du grain sur le comportement des composants afin que les performances puissent être considérablement améliorées. Le code est plus lisible et le refactoring est beaucoup plus facile - il suffit de rechercher et de remplacer les chaînes de chemin (pas besoin de changer le code / la logique).

neuronet
la source
-1

Je pense que la principale raison pour laquelle les objets immuables sont la conservation de l'état de l'objet est valide.

Supposons que nous ayons un objet appelé arr. Cet objet est valide lorsque tous les éléments sont la même lettre.

// this function will change the letter in all the array
function fillWithZ(arr) {
    for (var i = 0; i < arr.length; ++i) {
        if (i === 4) // rare condition
            return arr; // some error here

        arr[i] = "Z";
    }

    return arr;
}

console.log(fillWithZ(["A","A","A"])) // ok, valid state
console.log(fillWithZ(["A","A","A","A","A","A"])) // bad, invalid state

si vous arrdevenez un objet immuable, alors nous serons sûrs que arr est toujours dans un état valide.

bedorlan
la source
Je pense arrque mute à chaque fois que vous appelezfillWithZ
rodrigo-silveira
si vous utilisez immutable.js, vous obtiendrez une nouvelle copie de l'objet chaque fois que vous le modifiez. afin que l'objet d'origine reste intact
bedorlan