By efficient I mean they either have better or comparable time and/or
memory performance compared to their mutable versions.
Strettamente parlando, non credo che possa esistere, perché puoi sempre mostrarmi una struttura dati immutabile e posso trovare il modo di rendere qualcosa di più economico se potesse essere reso mutevole. Ad esempio, potrei essere in grado di eliminare il requisito di garbage collection o reference-counting se fosse mutabile.
Esistono strutture dati che sono adatte a diventare immutabili anche se i costi sono relativamente economici. La maggior parte dei non banali sono in genere almeno in parte contigue. Ciò consente di banalizzare i costi di ref-counting o GC se i nodi vengono srotolati e memorizzare più elementi ciascuno, non un elemento per nodo.
Spesso c'è anche un atto di bilanciamento tra copiatura superficiale di più puntatori rispetto alla copia profonda di pochi elementi non univoci, perché qualsiasi struttura dati può essere resa immutabile se si copia l'intera cosa terribile ogni volta che si desidera modificare qualcosa, ma ciò potrebbe essere esplosivi nella memoria e requisiti di elaborazione. Il rovescio della medaglia, se riferimento superficiale ogni singolo elemento, che potrebbe essere esplosivo in memoria e requisiti di elaborazione con tutti i puntatori extra, tutta l'indiretta e la potenziale frammentazione della memoria, il costo di ref-counting o GC devono essere pagati per ogni singolo elemento, ecc.
Spesso penso che le strutture di dati immutabili più efficienti per insiemi e dizionari siano parzialmente contigue, come una tabella hash che utilizza l'indirizzamento aperto, ma invece di usare un array gigante per l'intera tabella, usa i blocchi srotolati che memorizzano, diciamo, 64 tasti ciascuno. Un altro esempio potrebbe essere un albero n-ary che memorizza molte chiavi in un nodo.
Ovviamente una tabella hash che utilizza il concatenamento separato è davvero semplice da rendere immutabile, dal momento che rendere le liste LIFO single-linked immutabili richiede semplicemente la memorizzazione di un diverso puntatore testa per lista immutabile. Tuttavia, è semplice ma non molto economico poiché ciò implica, ancora una volta, il conteggio dei riferimenti o il GC pagato a livello di singolo elemento.
È anche probabile che avrai bisogno di qualcosa come un "costruttore" o "transitorio" per esprimere ciò che vuoi fare con esso, dal momento che non vuoi pagare il costo di generare una nuova istanza immutabile ogni volta basta inserire o rimuovere una chiave.
A concrete example is Guava, where I have seen memory savings when
used with sets that don't need to be modified.
Qui forse non si tratta tanto di immutabilità, ma solo di una struttura dati in grado di scartare il presupposto che gli elementi verranno semplicemente inseriti e conosciuti in anticipo e non avendo a che fare con la rimozione dinamica e l'inserimento di elementi dopo la sua costruzione .
In questo caso, ad esempio, è possibile utilizzare un allocatore di memoria sequenziale come ottimizzazione perché non è necessario gestire la memoria di liberazione per i singoli elementi poiché gli elementi non verranno mai rimossi singolarmente dalla struttura dei dati. Tutto ciò che devi essere in grado di fare è eliminare tutta la memoria per tutti gli elementi quando la struttura dei dati viene distrutta.
Anche se gli elementi sono tutti noti in anticipo, potresti essere in grado di evitare di dover effettuare riallocazioni per espandere la dimensione della struttura. Puoi renderlo perfettamente proporzionato poiché conosci tutti gli elementi che inserirai in anticipo in modo da non dover riservare memoria aggiuntiva per gli inserimenti futuri.
Le strutture dati che devono soddisfare solo questi tipi di requisiti "statici" e non "dinamici" offrono anche molto spazio per la post-elaborazione dopo la loro costruzione. Ad esempio, un albero binario potrebbe essere post-elaborato per riallocare i suoi nodi in un modello di accesso compatibile con la cache. Potete permettervelo con un tipo statico di struttura dati che non si occupa di inserimenti e rimozioni dinamiche poiché c'è una fase di costruzione chiara che lascia spazio per la post-elaborazione, dopodiché non dovrete più gestire le modifiche alla struttura dati.
Es: metodo "Cancella" mutevole
Qualunque struttura di dati che abbia meno requisiti funzionali da trattare avrà generalmente più spazio per l'ottimizzazione. Ma qui non si tratta di strutture di dati immutabili, quanto di strutture di dati che possono essere solo costruite in anticipo e non devono occuparsi di inserimenti e rimozioni dinamici. Tali strutture di dati potrebbero ancora essere rese mutevoli e rimanere a buon mercato. Ad esempio, una tale struttura dati potrebbe ancora fornire un metodo mutabile clear
per cancellare l'intero contenuto dell'insieme / dizionario, e fornire un tale metodo mutabile non richiederebbe di perdere quelle potenziali ottimizzazioni descritte sopra. Quindi non c'è alcun caso per quanto vedo dove l'immutabilità rende tutto meno costoso, dal momento che l'immutabilità impone più requisiti funzionali su una struttura dati, per così dire, non meno.