Perché l'immutabilità è così importante (o necessaria) in JavaScript?


206

Attualmente sto lavorando su framework React JS e React Native . A metà strada mi sono imbattuto in Immutability o nella libreria Immutable -JS , mentre leggevo dell'implementazione di Flux e Redux di Facebook.

La domanda è: perché l'immutabilità è così importante? Cosa c'è di sbagliato negli oggetti mutanti? Non semplifica le cose?

Diamo un esempio, consideriamo una semplice app per la lettura di notizie con la schermata di apertura come una visualizzazione elenco dei titoli delle notizie.

Se ho impostato dire una matrice di oggetti con un valore inizialmente non posso manipolarlo. Questo è ciò che dice il principio di immutabilità, giusto? (Correggimi se sbaglio.) Ma cosa succede se ho un nuovo oggetto Notizie che deve essere aggiornato? Nel solito caso, avrei potuto semplicemente aggiungere l'oggetto all'array. Come posso ottenere in questo caso? Eliminare il negozio e ricrearlo? L'aggiunta di un oggetto all'array non è un'operazione meno costosa?



2
La struttura dei dati immutabile e la pura funzione portano alla trasparenza referenziale, rendendo molto più facile ragionare sul comportamento del programma. Si ottiene anche il backtracking gratuito quando si utilizza la struttura dati funzionale.
WorBlux,

Ho fornito un punto di vista Redux @bozzmob.
prosti

1
Potrebbe essere utile conoscere l'immurabilità in generale come concetto di paradigma funzionale invece di cercare di pensare che JS abbia qualcosa a che fare con esso. React è scritto dai fan della programmazione funzionale. Devi sapere cosa sanno per capirli.
Gherman,

Non è necessario, ma offre alcuni buoni compromessi. Lo stato mutevole è al software come le parti mobili all'hardware
Kristian Dupont

Risposte:


196

Di recente ho studiato lo stesso argomento. Farò del mio meglio per rispondere alle tue domande e cercherò di condividere ciò che ho imparato finora.

La domanda è: perché l'immutabilità è così importante? Cosa c'è di sbagliato negli oggetti mutanti? Non semplifica le cose?

Fondamentalmente si riduce al fatto che l'immutabilità aumenta la prevedibilità, le prestazioni (indirettamente) e consente il monitoraggio delle mutazioni.

prevedibilità

La mutazione nasconde il cambiamento, che crea effetti collaterali (imprevisti), che possono causare cattivi bug. Quando si applica l'immutabilità, è possibile mantenere semplice l'architettura dell'applicazione e il modello mentale, il che semplifica il ragionamento sull'applicazione.

Prestazione

Anche se l'aggiunta di valori a un oggetto immutabile significa che è necessario creare una nuova istanza in cui è necessario copiare i valori esistenti e aggiungere nuovi valori al nuovo oggetto che costano memoria, gli oggetti immutabili possono utilizzare la condivisione strutturale per ridurre la memoria spese generali.

Tutti gli aggiornamenti restituiscono nuovi valori, ma le strutture interne sono condivise per ridurre drasticamente l'utilizzo della memoria (e il thrashing GC). Ciò significa che se aggiungi a un vettore con 1000 elementi, in realtà non crea un nuovo vettore lungo 1001 elementi. Molto probabilmente, internamente sono allocati solo alcuni piccoli oggetti.

Puoi leggere di più su questo qui .

Monitoraggio della mutazione

Oltre a ridurre l'utilizzo della memoria, l'immutabilità consente di ottimizzare l'applicazione facendo uso dell'uguaglianza di riferimento e di valore. Questo rende davvero facile vedere se qualcosa è cambiato. Ad esempio un cambio di stato in un componente di reazione. È possibile utilizzare shouldComponentUpdateper verificare se lo stato è identico confrontando gli oggetti stato e impedire il rendering non necessario. Puoi leggere di più su questo qui .

Risorse addizionali:

Se ho impostato dire una matrice di oggetti con un valore inizialmente. Non riesco a manipolarlo. Questo è ciò che dice il principio di immutabilità, giusto? (Correggimi se sbaglio). Ma cosa succede se ho un nuovo oggetto Notizie che deve essere aggiornato? Nel solito caso, avrei potuto semplicemente aggiungere l'oggetto all'array. Come posso ottenere in questo caso? Eliminare il negozio e ricrearlo? L'aggiunta di un oggetto all'array non è un'operazione meno costosa?

Sì questo è corretto. Se sei confuso su come implementarlo nella tua applicazione, ti consiglio di esaminare come Redux lo fa per familiarizzare con i concetti chiave, mi ha aiutato molto.

Mi piace usare Redux come esempio perché abbraccia l'immutabilità. Ha un unico albero di stato immutabile (indicato come store) in cui tutti i cambiamenti di stato sono espliciti inviando azioni che vengono elaborate da un riduttore che accetta lo stato precedente insieme a dette azioni (uno alla volta) e restituisce lo stato successivo dell'applicazione . Puoi leggere di più sui suoi principi fondamentali qui .

Esiste un eccellente corso di redux su egghead.io in cui Dan Abramov , l'autore di redux, spiega questi principi come segue (ho modificato un po 'il codice per adattarlo meglio allo scenario):

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();

Inoltre, questi video dimostrano in dettaglio come ottenere l'immutabilità per:


1
@naomik grazie per il feedback! La mia intenzione era quella di illustrare il concetto e mostrare esplicitamente che gli oggetti non vengono mutati e non necessariamente di mostrare come implementarlo fino in fondo. Tuttavia, il mio esempio potrebbe essere un po 'confuso, lo aggiornerò tra poco.
danillouz,

2
@bozzmob sei il benvenuto! No, ciò non è corretto, è necessario imporre l'immutabilità nel riduttore. Ciò significa che puoi seguire strategie come dimostrate nei video o utilizzare una libreria come immutablejs. Puoi trovare maggiori informazioni qui e qui .
Danillouz,

1
@naomik ES6 constnon riguarda l'immutabilità. Mathias Bynens ha scritto un ottimo articolo di blog al riguardo.
Lea Rosema,

1
@terabaud grazie per aver condiviso il link. Sono d'accordo che è una distinzione importante. ^ _ ^
Grazie

4
Spiega questo "La mutazione nasconde il cambiamento, che crea effetti collaterali (imprevisti), che possono causare cattivi bug. Quando si applica l'immutabilità, è possibile mantenere semplice l'architettura dell'applicazione e il modello mentale, il che semplifica il ragionamento sulla propria applicazione." Perché questo non è affatto vero nel contesto di JavaScript.
Pavle Lekic,

143

Una visione contraria dell'immutabilità

TL / DR: l'immutabilità è più una tendenza della moda che una necessità in JavaScript. Se si utilizza React, fornisce una soluzione accurata ad alcune scelte di progettazione confuse nella gestione dello stato. Tuttavia, nella maggior parte delle altre situazioni, non aggiungerà abbastanza valore rispetto alla complessità che introduce, servendo più per riempire un curriculum che per soddisfare un'esigenza effettiva del cliente.

Risposta lunga: leggi sotto.

Perché l'immutabilità è così importante (o necessaria) in JavaScript?

Bene, sono contento che tu l'abbia chiesto!

Qualche tempo fa un ragazzo di grande talento di nome Dan Abramov ha scritto una libreria di gestione dello stato javascript chiamata Redux che utilizza funzioni pure e immutabilità. Ha anche realizzato alcuni video davvero interessanti che hanno reso l'idea (e la vendita) davvero facile da capire.

Il tempismo è stato perfetto. La novità di Angular stava svanendo e il mondo JavaScript era pronto a fissarsi sull'ultima cosa che aveva il giusto grado di cool, e questa libreria non era solo innovativa, ma si inseriva perfettamente con React che veniva commercializzato da un'altra centrale elettrica della Silicon Valley .

Per quanto triste, le mode dominano nel mondo di JavaScript. Ora Abramov viene accolto come un semidio e tutti noi semplici mortali dobbiamo sottometterci al Dao dell'Immutabilità ... Sia che abbia senso o no.

Cosa c'è di sbagliato negli oggetti mutanti?

Niente!

In effetti i programmatori hanno mutato oggetti per sempre ... purché ci siano stati oggetti da mutare. Più di 50 anni di sviluppo di applicazioni in altre parole.

E perché complicare le cose? Quando hai un oggetto cate muore, hai davvero bisogno di un secondo catper rintracciare il cambiamento? La maggior parte delle persone direbbe cat.isDead = truee finirà.

(Oggetti mutanti) non rende le cose semplici?

SÌ! .. Certo che lo fa!

Specialmente in JavaScript, che in pratica è più utile per il rendering di una vista di alcuni stati mantenuta altrove (come in un database).

Cosa succede se ho un nuovo oggetto Notizie che deve essere aggiornato? ... Come posso ottenere in questo caso? Eliminare il negozio e ricrearlo? L'aggiunta di un oggetto all'array non è un'operazione meno costosa?

Bene, puoi seguire l'approccio tradizionale e aggiornare l' Newsoggetto, quindi la tua rappresentazione in memoria di quell'oggetto cambia (e la vista mostrata all'utente, o almeno così si spera) ...

O in alternativa ...

Puoi provare l'approccio sexy FP / Immutabilità e aggiungere le tue modifiche Newsall'oggetto a un array che tiene traccia di ogni cambiamento storico in modo da poter iterare attraverso l'array e capire quale dovrebbe essere la rappresentazione dello stato corretta (eh!).

Sto cercando di imparare cosa c'è proprio qui. Per favore, illuminami :)

La moda va e viene amico. Esistono molti modi per scuoiare un gatto.

Mi dispiace che tu debba sopportare la confusione di una serie in costante cambiamento di paradigmi di programmazione. Ma ehi, BENVENUTO NEL CLUB !!

Ora un paio di punti importanti da ricordare per quanto riguarda l'Immutabilità, e ti verranno lanciati contro con l'intensità febbrile che solo l'ingenuità può raccogliere.

1) L'immutabilità è eccezionale per evitare le condizioni di gara in ambienti multi-thread .

Gli ambienti multi-thread (come C ++, Java e C #) sono colpevoli della pratica di bloccare gli oggetti quando più di un thread vuole cambiarli. Ciò è negativo per le prestazioni, ma migliore dell'alternativa alla corruzione dei dati. Eppure non è buono come rendere tutto immutabile (lode di Haskell!).

MA AHIMÈ! In JavaScript operi sempre su un singolo thread . Perfino i web worker (ognuno funziona all'interno di un contesto separato ). Quindi, dal momento che non è possibile avere una race condition legata al thread all'interno del proprio contesto di esecuzione (tutte quelle adorabili variabili globali e chiusure), il punto principale a favore dell'Immutabilità va fuori dalla finestra.

(Detto questo, non v'è un vantaggio di utilizzare funzioni pure nei lavoratori web, che è che non avrete aspettative su giocherellare con oggetti sul thread principale.)

2) L'immutabilità può (in qualche modo) evitare le condizioni di gara nello stato della tua app.

Ed ecco il vero punto cruciale della questione, la maggior parte degli sviluppatori (React) ti dirà che Immutabilità e FP possono in qualche modo funzionare questa magia che consente allo stato della tua applicazione di diventare prevedibile.

Ovviamente questo non significa che puoi evitare le condizioni di competizione nel database , per risolverlo dovresti coordinare tutti gli utenti in tutti i browser e per questo avresti bisogno di una tecnologia push back-end come WebSocket ( ulteriori informazioni qui di seguito) che trasmetterà le modifiche a tutti coloro che eseguono l'app.

Né significa che c'è qualche problema inerente a JavaScript in cui lo stato della tua applicazione ha bisogno di immutabilità per diventare prevedibile, qualsiasi sviluppatore che ha codificato applicazioni front-end prima che React te lo dicesse.

Questa affermazione piuttosto confusa significa semplicemente che con React il tuo stato di applicazione diventerà più incline alle condizioni di gara , ma quell'immutabilità ti consente di eliminare quel dolore. Perché? Perché React è speciale .. è stato progettato come una libreria di rendering altamente ottimizzata con una gestione coerente dello stato che occupa il secondo posto, e quindi lo stato dei componenti è gestito tramite una catena di eventi asincrona ( nota anche come "associazione dati a una via") che non hai controllo e fai affidamento sul fatto che ti ricordi di non mutare direttamente lo stato ...

Dato questo contesto, è facile vedere come la necessità di immutabilità abbia poco a che fare con JavaScript e molto a che fare con le condizioni di gara in React: se hai un sacco di cambiamenti interdipendenti nella tua applicazione e nessun modo semplice per capire cosa il tuo stato è attualmente, ti confonderai , e quindi ha perfettamente senso usare l'immutabilità per tracciare ogni cambiamento storico .

3) Le condizioni di gara sono categoricamente cattive.

Bene, potrebbero esserlo se stai usando React. Ma sono rari se scegli un quadro diverso.

Inoltre, normalmente hai problemi molto più grandi da affrontare ... Problemi come l'inferno delle dipendenze. Come una base di codice gonfia. Come se il tuo CSS non venisse caricato. Come un processo di compilazione lento o essere bloccato su un back-end monolitico che rende quasi impossibile l'iterazione. Come gli sviluppatori inesperti che non capiscono cosa sta succedendo e fanno un casino di cose.

Sai. La realtà. Ma hey, a chi importa?

4) L'immutabilità utilizza i tipi di riferimento per ridurre l'impatto delle prestazioni del tracciamento di ogni cambiamento di stato.

Perché seriamente, se hai intenzione di copiare cose ogni volta che il tuo stato cambia, è meglio assicurarsi di essere furbo a riguardo.

5) L'immutabilità ti consente di ANNULLARE le cose .

Perché ehm ... questa è la caratteristica numero uno che il tuo project manager chiederà, giusto?

6) Lo stato immutabile ha un grande potenziale in combinazione con WebSocket

Ultimo ma non meno importante, l'accumulo di delta di stato costituisce un caso piuttosto convincente in combinazione con WebSocket, che consente un facile consumo di stato come flusso di eventi immutabili ...

Una volta che il penny scende su questo concetto (dichiarare che è un flusso di eventi - piuttosto che un insieme rozzo di documenti che rappresentano l'ultima visione), il mondo immutabile diventa un luogo magico in cui abitare. Una terra di meraviglia e possibilità che provengono da eventi che trascendono il tempo stesso . E quando fatto bene questo può sicuramente fare in tempo reale applicazioni easi er da realizzare, basta trasmettere il flusso degli eventi a tutti gli interessati in modo che possano costruire la loro propria rappresentazione del presente e scrivere di nuovo i propri cambiamenti nel flusso comune.

Ma ad un certo punto ti svegli e ti rendi conto che tutta quella meraviglia e magia non vengono gratis. A differenza dei tuoi colleghi desiderosi, i tuoi stakeholder (sì, le persone che ti pagano) si preoccupano poco della filosofia o della moda e molto dei soldi che pagano per costruire un prodotto che possono vendere. E la linea di fondo è che è più difficile scrivere codice immutabile e più facile romperlo, inoltre ha poco senso avere un front-end immutabile se non si ha un back-end per supportarlo. Quando (e se!) Alla fine convinci i tuoi stakeholder che dovresti pubblicare e consumare eventi tramite una tecnologia push come WebSocket, scopri che dolore è scalare nella produzione .


Ora, per un consiglio, dovresti scegliere di accettarlo.

Una scelta per scrivere JavaScript usando FP / Immutability è anche una scelta per rendere la base di codice della tua applicazione più grande, più complessa e più difficile da gestire. Direi fortemente di limitare questo approccio ai tuoi riduttori Redux, a meno che tu non sappia cosa stai facendo ... E SE hai intenzione di andare avanti e usare l'immutabilità a prescindere, quindi applicare lo stato immutabile a tutto il tuo stack di applicazioni , e non solo al lato client, poiché in caso contrario manchi il suo valore reale.

Ora, se sei abbastanza fortunato da poter fare delle scelte nel tuo lavoro, allora prova a usare la tua saggezza (o no) e fai ciò che è giusto per la persona che ti sta pagando . Puoi basarlo sulla tua esperienza, sul tuo intestino o su ciò che accade intorno a te (è vero che se tutti usano React / Redux, allora c'è un valido argomento secondo cui sarà più facile trovare una risorsa per continuare il tuo lavoro) .. In alternativa, puoi provare gli approcci Riprendi Driven Development o Hype Driven Development . Potrebbero essere più il tuo genere di cose.

In breve, la cosa da dire per l'immutabilità è che ti renderà di moda con i tuoi coetanei, almeno fino a quando non arriva la prossima mania, a quel punto sarai felice di andare avanti.


Ora, dopo questa sessione di autoterapia, vorrei sottolineare che l'ho aggiunto come articolo nel mio blog => Immutabilità in JavaScript: una visione contraria . Sentiti libero di rispondere lì se hai sentimenti forti e ti piacerebbe toglierti anche il petto;).


10
Ciao Steven, sì. Avevo tutti questi dubbi quando ho considerato immutable.js e redux. Ma la tua risposta è sorprendente! Aggiunge molto valore e grazie per aver affrontato ogni singolo punto su cui avevo dei dubbi. Ora è molto più chiaro / migliore anche dopo aver lavorato per mesi su oggetti immutabili.
Bozzmob,

5
Uso React con Flux / Redux da oltre due anni e non potrei essere più d'accordo con te, ottima risposta!
Pavle Lekic,

6
Sono fortemente sospettoso che le opinioni sull'immutabilità siano piuttosto ordinatamente correlate alle dimensioni della squadra e della base di codice, e non credo che sia una coincidenza il fatto che il principale sostenitore sia un gigante della Silicon Valley. Detto questo, non sono rispettosamente d'accordo: l'immutabilità è una disciplina utile come non usare goto è una disciplina utile. O test unitari. O TDD. O analisi del tipo statico. Non significa che li fai sempre, ogni volta (anche se alcuni lo fanno). Direi anche che l'utilità è ortogonale all'hype: in una matrice di utile / superfluo e sexy / noioso ci sono molti esempi di ciascuno. "hyped" !== "bad"
Jared Smith,

4
Ciao @ftor, buon punto, andare troppo lontano nella direzione opposta. Tuttavia, poiché esiste una tale profusione di articoli e argomenti sulla "immutabilità negli javascript", ho sentito il bisogno di bilanciare le cose. Quindi i neofiti hanno un punto di vista opposto per aiutarli a dare un giudizio in entrambi i modi.
Steven de Salas,

4
Informativo e brillantemente intitolato. Fino a quando non ho trovato questa risposta, ho pensato di essere il solo ad avere una visione simile. Riconosco il valore dell'immutabilità, ma ciò che mi preoccupa è che è diventato un dogma opprimente per tutte le altre tecniche (ad esempio a scapito dell'associazione a 2 vie che è follemente utile per la formattazione dell'input come implementato in KnockoutJS per esempio).
Tyblitz,

53

La domanda è: perché l'immutabilità è così importante? Cosa c'è di sbagliato negli oggetti mutanti? Non semplifica le cose?

In realtà, è vero il contrario: la mutabilità rende le cose più complicate, almeno nel lungo periodo. Sì, semplifica la tua codifica iniziale perché puoi semplicemente cambiare le cose dove vuoi, ma quando il tuo programma diventa più grande diventa un problema: se un valore è cambiato, cosa l'ha cambiato?

Quando rendi tutto immutabile, significa che i dati non possono più essere modificati di sorpresa. Sai per certo che se passi un valore in una funzione, non può essere cambiato in quella funzione.

In parole povere: se usi valori immutabili, è molto facile ragionare sul tuo codice: tutti ricevono una copia unica * dei tuoi dati, quindi non possono andare a vuoto con esso e rompere altre parti del tuo codice. Immagina quanto più semplice ciò renda lavorare in un ambiente multi-thread!

Nota 1: esiste un potenziale costo in termini di prestazioni per l'immutabilità a seconda di ciò che stai facendo, ma cose come Immutable.js ottimizzano al meglio.

Nota 2: nell'improbabile caso in cui non si fosse sicuri, Immutable.js ed ES6 constsignificano cose molto diverse.

Nel solito caso, avrei potuto semplicemente aggiungere l'oggetto all'array. Come posso ottenere in questo caso? Eliminare il negozio e ricrearlo? L'aggiunta di un oggetto all'array non è un'operazione meno costosa? PS: Se l'esempio non è il modo giusto per spiegare l'immutabilità, per favore fammi sapere qual è l'esempio pratico giusto.

Sì, il tuo esempio di notizie è perfettamente valido e il tuo ragionamento è esattamente corretto: non puoi semplicemente modificare l'elenco esistente, quindi devi crearne uno nuovo:

var originalItems = Immutable.List.of(1, 2, 3);
var newItems = originalItems.push(4, 5, 6);

1
Non sono in disaccordo con questa risposta, ma non affronta la sua parte "Mi piacerebbe imparare da un esempio pratico" della domanda. Si potrebbe sostenere che un singolo riferimento all'elenco delle intestazioni delle notizie utilizzate in più aree è una buona cosa. "Devo aggiornare la lista solo una volta e tutto ciò che fa riferimento alla lista delle notizie viene aggiornato gratuitamente" - Penso che una risposta migliore richiederebbe un problema comune come quello presentato, e mostrerebbe una valida alternativa che utilizza l'immutabilità.
Grazie

1
Sono contento che la risposta sia stata utile! Per quanto riguarda la tua nuova domanda: non tentare di indovinare il sistema :) In questo caso, qualcosa chiamato "condivisione strutturale" riduce drasticamente il thrashing GC - se hai 10.000 elementi in un elenco e ne aggiungi altri 10, credo che sia immutabile. js proverà a riutilizzare la struttura precedente nel miglior modo possibile. Lascia che Immutable.js si preoccupi della memoria e delle possibilità che troverai che esce meglio.
TwoStraws,

6
Imagine how much easier this makes working in a multi-threaded environment!-> Ok per altre lingue, ma questo non è un vantaggio in JavaScript a thread singolo.
Steven de Salas,

1
@StevendeSalas nota che JavaScript è principalmente asincrono e guidato dagli eventi. Non è affatto immune alle condizioni di razza.
Jared Smith,

1
@JaredSmith rimane ancora il mio punto. FP e Immutability sono potenti paradigmi utili per evitare il danneggiamento dei dati e / o il blocco delle risorse in ambienti multithreading, ma non così in JavaScript perché sono single thread. A meno che non mi manchi qualche sacro pepita di saggezza, il principale compromesso qui è se sei pronto a rendere il codice più complesso (e più lento) in una ricerca per evitare le condizioni di gara ... che sono molto meno un problema rispetto alla maggior parte delle persone pensare.
Steven de Salas,

37

Sebbene le altre risposte vadano bene, rispondere alla tua domanda su un caso d'uso pratico (dai commenti alle altre risposte) consente di uscire un attimo dal codice in esecuzione e guardare la risposta onnipresente proprio sotto il naso: git . Cosa succederebbe se ogni volta che spingessi un commit sovrascrivessi i dati nel repository?

Ora stiamo affrontando uno dei problemi che devono affrontare le collezioni immutabili: la memoria gonfia. Git è abbastanza intelligente da non fare semplicemente nuove copie di file ogni volta che apporti una modifica, ma semplicemente tiene traccia delle differenze .

Anche se non so molto sul funzionamento interno di Git, posso solo supporre che usi una strategia simile a quella delle biblioteche a cui fai riferimento: la condivisione strutturale. Sotto il cofano le librerie usano try o altri alberi per tracciare solo i nodi che sono diversi.

Questa strategia è anche ragionevolmente performante per le strutture di dati in memoria poiché esistono noti algoritmi di operazioni ad albero che operano nel tempo logaritmico.

Un altro caso d'uso: supponiamo che tu voglia un pulsante Annulla sulla tua webapp. Con rappresentazioni immutabili dei tuoi dati, implementarli è relativamente banale. Ma se fai affidamento sulla mutazione, significa che devi preoccuparti di memorizzare nella cache lo stato del mondo e fare aggiornamenti atomici.

In breve, c'è un prezzo da pagare per l'immutabilità nelle prestazioni di runtime e nella curva di apprendimento. Ma qualsiasi programmatore esperto ti dirà che il tempo di debug supera il tempo di scrittura del codice di un ordine di grandezza. E il leggero successo delle prestazioni di runtime è probabilmente compensato dai bug relativi allo stato che gli utenti non devono sopportare.


1
Un brillante esempio che dico. La mia comprensione dell'immutabilità è più chiara ora. Grazie Jared. In realtà, una delle implementazioni è il pulsante UNDO: D E mi hai reso le cose abbastanza semplici.
Bozzmob,

3
Solo perché uno schema ha senso in Git non significa che la stessa cosa abbia senso ovunque. In git ti preoccupi davvero di tutta la storia memorizzata e vuoi essere in grado di unire diversi rami. In frontend non ti interessa la maggior parte della storia dello stato e non hai bisogno di tutta questa complessità.
Sci

2
@Ski è solo complesso perché non è l'impostazione predefinita. Di solito non utilizzo mori o immutable.js nei miei progetti: sono sempre riluttante ad affrontare deps di terze parti. Ma se quello fosse il default (a la clojurescript) o almeno avesse un'opzione nativa opt-in, la userei sempre, perché quando per esempio programma in clojure non inserisco immediatamente tutto in atomi.
Jared Smith,

Joe Armstrong direbbe che non preoccuparti della performance, aspetta solo qualche anno e la legge di Moore si occuperà di questo per te.
ximo,

1
@JaredSmith Hai ragione, le cose stanno diventando sempre più piccole e limitate le risorse. Non sono sicuro se questo sarà il fattore limitante per JavaScript. Continuiamo a trovare nuovi modi per migliorare le prestazioni (ad esempio Svelte). A proposito, sono completamente d'accordo con l'altro tuo commento. La complessità o la difficoltà di utilizzare strutture di dati immutabili spesso dipende dal linguaggio che non ha il supporto incorporato per il concetto. Clojure rende l'immutabilità semplice perché è cotta nella lingua, l'intera lingua è stata progettata attorno all'idea.
ximo,

8

La domanda è: perché l'immutabilità è così importante? Cosa c'è di sbagliato negli oggetti mutanti? Non semplifica le cose?

Informazioni sulla mutabilità

Nulla di sbagliato nella mutabilità dal punto di vista tecnico. È veloce, sta riutilizzando la memoria. Gli sviluppatori ci sono abituati sin dall'inizio (come lo ricordo). Esistono problemi nell'uso della mutabilità e dei problemi che questo uso può portare.

Se l'oggetto non è condiviso con nulla, ad esempio esiste nell'ambito della funzione e non è esposto all'esterno, allora è difficile vedere i benefici dell'immutabilità. Davvero in questo caso non ha senso essere immutabili. Il senso di immutabilità inizia quando qualcosa è condiviso.

Mal di testa da mutabilità

La mutevole struttura condivisa può facilmente creare molte insidie. Qualsiasi modifica in qualsiasi parte del codice con accesso al riferimento ha impatto su altre parti con visibilità di questo riferimento. Tale impatto collega tutte le parti insieme, anche quando non dovrebbero essere a conoscenza di moduli diversi. La mutazione in una funzione può causare il crash di parti totalmente diverse dell'app. Tale cosa è un brutto effetto collaterale.

Successivamente spesso il problema con la mutazione è lo stato danneggiato. Lo stato corrotto può verificarsi quando la procedura di mutazione fallisce nel mezzo e alcuni campi sono stati modificati e altri no.

Inoltre, con la mutazione è difficile tenere traccia del cambiamento. Un semplice controllo di riferimento non mostrerà la differenza, per sapere cosa è cambiato è necessario eseguire alcuni controlli approfonditi. Anche per monitorare il cambiamento è necessario introdurre alcuni schemi osservabili.

Infine, la mutazione è la ragione del deficit di fiducia. Come puoi essere sicuro che una struttura abbia desiderato valore, se può essere mutata.

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

Come mostra l'esempio precedente, il passaggio di una struttura mutabile può sempre terminare con una struttura diversa. Funzione doSomething sta mutando l'attributo dato dall'esterno. Nessuna fiducia per il codice, davvero non sai cosa hai e cosa avrai. Tutti questi problemi si verificano perché: le strutture mutabili rappresentano i puntatori alla memoria.

L'immutabilità riguarda i valori

Immutabilità significa che il cambiamento non è fatto sullo stesso oggetto, struttura, ma il cambiamento è rappresentato in uno nuovo. E questo perché il riferimento rappresenta un valore non solo un puntatore alla memoria. Ogni modifica crea nuovo valore e non tocca quella precedente. Regole così chiare restituiscono la fiducia e la prevedibilità del codice. Le funzioni sono sicure da usare perché invece della mutazione, gestiscono le proprie versioni con i propri valori.

L'uso di valori anziché di contenitori di memoria offre la certezza che ogni oggetto rappresenta un valore immutabile specifico ed è sicuro utilizzarlo.

Le strutture immutabili rappresentano valori.

Sto approfondendo ancora di più l'argomento in un articolo di medie dimensioni - https://medium.com/@macsikora/the-state-of-immutability-169d2cd11310


6

Perché l'immutabilità è così importante (o necessaria) in JavaScript?

L'immutabilità può essere tracciata in contesti diversi, ma la cosa più importante sarebbe quella di tracciarla rispetto allo stato dell'applicazione e all'interfaccia utente dell'applicazione.

Considero il modello JavaScript Redux come un approccio molto alla moda e moderno e perché lo hai menzionato.

Per l'interfaccia utente dobbiamo renderlo prevedibile . Sarà prevedibile se UI = f(application state).

Le applicazioni (in JavaScript) cambiano lo stato tramite azioni implementate utilizzando la funzione di riduzione .

La funzione di riduzione prende semplicemente l'azione e il vecchio stato e restituisce il nuovo stato, mantenendo intatto il vecchio stato.

new state  = r(current state, action)

inserisci qui la descrizione dell'immagine

Il vantaggio è: viaggi nel tempo negli stati poiché tutti gli oggetti di stato vengono salvati e da allora puoi eseguire il rendering dell'app in qualsiasi stato UI = f(state)

Quindi puoi annullare / ripetere facilmente.


Capita di creare tutti questi stati può comunque essere efficiente in termini di memoria, un'analogia con Git è eccezionale, e abbiamo l'analogia simile nel sistema operativo Linux con collegamenti simbolici (basato sugli inode).


5

Un altro vantaggio dell'immutabilità in Javascript è che riduce l'accoppiamento temporale, che presenta vantaggi sostanziali per la progettazione in generale. Considera l'interfaccia di un oggetto con due metodi:

class Foo {

      baz() {
          // .... 
      }

      bar() {
          // ....
      }

}

const f = new Foo();

È possibile che baz()sia necessaria una chiamata a per rendere l'oggetto in uno stato valido affinché una chiamata bar()funzioni correttamente. Ma come lo sai?

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

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

Per capirlo è necessario esaminare gli interni della classe perché non risulta immediatamente dall'esame dell'interfaccia pubblica. Questo problema può esplodere in una base di codice di grandi dimensioni con molti stati e classi mutabili.

Se Fooè immutabile, questo non è più un problema. È sicuro supporre che possiamo chiamare bazo barin qualsiasi ordine perché lo stato interno della classe non può cambiare.


4

C'era una volta, c'era un problema con la sincronizzazione dei dati tra i thread. Questo problema è stato un grande dolore, c'erano oltre 10 soluzioni. Alcune persone hanno cercato di risolverlo radicalmente. Era un posto dove nacque la programmazione funzionale. È proprio come il marxismo. Non riuscivo a capire come Dan Abramov vendesse questa idea in JS, perché è single thread. Lui è un genio.

Posso fare un piccolo esempio. C'è un attributo __attribute__((pure))in gcc. I compilatori cercano di risolvere se la tua funzione è pura o meno se non la decifrerai in modo speciale. La tua funzione può essere pura anche se il tuo stato è mutevole. L'immutabilità è solo uno degli oltre 100 modi per garantire che la tua funzione sia pura. In realtà il 95% delle tue funzioni sarà puro.

Non dovresti usare alcuna limitazione (come l'immutabilità) se in realtà non hai un motivo serio. Se si desidera "annullare" alcuni stati, è possibile creare transazioni. Se si desidera semplificare le comunicazioni, è possibile inviare eventi con dati immutabili. Spetta a voi.

Sto scrivendo questo messaggio dalla repubblica post marxismo. Sono sicuro che la radicalizzazione di qualsiasi idea sia un modo sbagliato.


Il terzo paragrafo ha molto senso. Grazie per questo. 'Se vuoi "annullare" un certo stato, puoi creare transazioni' !!
Bozzmob,

A proposito, il confronto con il marxismo può anche essere fatto per OOP. Ricorda Java? Diamine, i bit strani di Java in JavaScript? L'hype non è mai buono, provoca radicalizzazione e polarizzazione. Storicamente, OOP è stato molto più esaltato dell'hyping di Redux da parte di Facebook. Anche se hanno sicuramente fatto del loro meglio.
ximo,

4

Un approccio diverso ...

La mia altra risposta affronta la domanda da un punto di vista molto pratico e mi piace ancora. Ho deciso di aggiungere questo come un'altra risposta piuttosto che un addendum a quello perché è un noioso rant filosofo che si spera risponda anche alla domanda, ma non si adatta davvero alla mia risposta esistente.

TL; DR

Anche nei piccoli progetti l'immutabilità può essere utile, ma non dare per scontato che, perché esiste, è pensata per te.

Risposta molto, molto più lunga

NOTA: ai fini di questa risposta sto usando la parola "disciplina" per indicare l'abnegazione per qualche beneficio.

Questo è simile nella forma a un'altra domanda: "Dovrei usare Typescript? Perché i tipi sono così importanti in JavaScript?". Ha anche una risposta simile. Considera il seguente scenario:

Sei l'unico autore e manutentore di una base di codice JavaScript / CSS / HTML di circa 5000 righe. Il tuo capo semi-tecnico legge qualcosa su Typescript-come-il-nuovo-piccante e suggerisce che potremmo voler passare a esso, ma lascia la decisione a te. Quindi leggi su di esso, giocaci, ecc.

Quindi ora hai una scelta da fare, passi a Typescript?

Typescript ha alcuni avvincenti vantaggi: intellisense, rilevazione precoce degli errori, specifica anticipata delle API, facilità di correzione delle cose quando il refactoring le interrompe, meno test. Typescript ha anche alcuni costi: alcuni idiomi JavaScript molto naturali e corretti possono essere difficili da modellare nel suo sistema di tipi non particolarmente potente, le annotazioni aumentano il LoC, il tempo e lo sforzo di riscrivere la base di codice esistente, un passaggio aggiuntivo nella pipeline di costruzione, ecc. Più fondamentalmente, ritaglia un sottoinsieme di possibili programmi JavaScript corretti in cambio della promessa che il tuo codice ha maggiori probabilità di essere corretto. È arbitrariamente restrittivo. Questo è il punto: imponi un po 'di disciplina che ti limita (si spera di spararti al piede).

Tornando alla domanda, riformulata nel contesto del paragrafo precedente: ne vale la pena ?

Nello scenario descritto, direi che se hai molta familiarità con una base di codice JS di dimensioni medio-piccole, la scelta di usare Typescript è più estetica che pratica. E va bene , non c'è niente di sbagliato in estetica, semplicemente non sono necessariamente convincenti.

Scenario B:

Cambia lavoro e ora sei un programmatore line-of-business presso Foo Corp. Stai lavorando con un team di 10 su un 90000 LoC (e contando) base di codice JavaScript / HTML / CSS con una pipeline di build abbastanza complicata che coinvolge babel, webpack , una suite di polyfill, reagisce con vari plugin, un sistema di gestione dello stato, ~ 20 librerie di terze parti, ~ 10 librerie interne, plugin di editor come una linter con regole per la guida di stile interna, ecc. ecc.

Quando eri un ragazzo / ragazza di 5k LoC, non importava molto. Anche la documentazione non era che affare un grande, anche tornando a una particolare porzione del codice dopo 6 mesi si riusciva a capire abbastanza facilmente. Ma ora la disciplina non è solo piacevole ma necessaria . Che la disciplina non può comportare tipografico, ma sarà probabilmente comporterà una qualche forma di analisi statica, così come tutte le altre forme di disciplina di codifica (documentazione, guida di stile, script di build, test di regressione, CI). La disciplina non è più un lusso , è una necessità .

Tutto questo è stato applicato GOTOnel 1978: il tuo piccolo gioco di blackjack in C potrebbe usare GOTOla logica s e spaghetti e non era un grosso problema scegliere-la tua-avventura-avventura attraverso di essa, ma man mano che i programmi diventavano più grandi e non è stato possibile sostenere un uso più ambizioso, ben disciplinatoGOTO . E tutto ciò vale oggi per l'immutabilità.

Proprio come i tipi statici, se non si lavora su una base di codice di grandi dimensioni con un team di ingegneri che la mantiene / estende, la scelta di utilizzare l'immutabilità è più estetica che pratica: i suoi vantaggi sono ancora lì, ma potrebbero non superare i costi.

Ma come per tutte le discipline utili, arriva un punto in cui non è più facoltativo. Se voglio mantenere un peso sano, la disciplina del gelato può essere facoltativa. Ma se voglio essere un atleta agonistico, la mia scelta se mangiare o meno il gelato è inclusa nella mia scelta degli obiettivi. Se vuoi cambiare il mondo con il software, l'immutabilità potrebbe far parte di ciò di cui hai bisogno per evitare che collassi sotto il suo stesso peso.


1
+1 Mi piace. Molto di più sul punto Jared. Eppure l'immutabilità non salverà una squadra dalla sua stessa mancanza di disciplina. 😉
Steven de Salas,

@StevendeSalas è una forma di disciplina. E come tale, penso che sia correlato (ma non sostituisca) alle altre forme di disciplina dell'ingegneria del software. Completa, piuttosto che soppiantare. Ma come ho detto in un commento sulla tua risposta, non mi sorprende affatto che sia stato spinto da un gigante della tecnologia con un gruppo di ingegneri che macinavano tutti sulla stessa enorme base di codice :) hanno bisogno di tutta la disciplina che possono ottenere. Per la maggior parte non muto gli oggetti, ma non uso alcuna forma di applicazione poiché, beh, sono solo io.
Jared Smith,

0

Ho creato una libreria open source agnostica (MIT) per lo stato mutabile (o immutabile) che può sostituire tutti quegli archivi immutabili come le librerie (redux, vuex ecc ...).

Gli stati immutabili sono stati brutti per me perché c'era troppo lavoro da fare (molte azioni per semplici operazioni di lettura / scrittura), il codice era meno leggibile e le prestazioni per grandi set di dati non erano accettabili (re-render dell'intero componente: /).

Con deep-state-observer posso aggiornare solo un nodo con notazione punto e usare i caratteri jolly. Posso anche creare la cronologia dello stato (annulla / ripristina / viaggio nel tempo) mantenendo solo quei valori concreti che sono stati modificati {path:value}= meno utilizzo della memoria.

Con l'osservatore di stato profondo sono in grado di mettere a punto le cose e ho il controllo del comportamento dei componenti in modo da poter migliorare drasticamente le prestazioni. Il codice è più leggibile e il refactoring è molto più semplice: basta cercare e sostituire le stringhe di percorso (non è necessario modificare il codice / la logica).


-1

Penso che la ragione principale per oggetti immutabili, sia mantenere valido lo stato dell'oggetto.

Supponiamo di avere un oggetto chiamato arr. Questo oggetto è valido quando tutti gli articoli hanno la stessa lettera.

// 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

se arrdiventa un oggetto immutabile, allora saremo sicuri che arr sia sempre in uno stato valido.


Penso che arrvenga mutato ogni volta che chiamifillWithZ
rodrigo-silveira,

se usi immutable.js, otterrai una nuova copia dell'oggetto ogni volta che lo cambi. così l'oggetto originale rimane intatto
bedorlan
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.