Nota se questo è adatto a te, ma in linguaggi funzionali come Standard ML tutto è immutabile per impostazione predefinita. La mutazione è supportata attraverso un ref
tipo di erenza generico . Quindi una int
variabile è immutabile e una ref int
variabile è un contenitore mutabile per int
s. Fondamentalmente, le variabili sono variabili reali in senso matematico (un valore sconosciuto ma fisso) e ref
s sono "variabili" in senso imperativo di programmazione - una cella di memoria che può essere scritta e da cui leggere. (Mi piace chiamarli assegnabili .)
Penso che il problema const
sia duplice. Innanzitutto, il C ++ manca della garbage collection, necessaria per avere strutture di dati persistenti non banali . const
deve essere profondo per avere un senso, ma avere valori completamente immutabili in C ++ è impraticabile.
In secondo luogo, in C ++ è necessario optare const
piuttosto che non. Ma quando ti dimentichi di const
qualcosa e in seguito lo risolvi, finirai nella situazione di "avvelenamento da costanti" menzionata nella risposta di @ RobY in cui il const
cambiamento si ripercuoterà su tutto il codice. Se const
fosse l'impostazione predefinita, non ti ritroveresti ad applicare const
retroattivamente. Inoltre, dover aggiungere const
ovunque aggiunge molto rumore al codice.
Sospetto che i linguaggi tradizionali che seguirono (ad es. Java) furono fortemente modellati dal successo e dal modo di pensare di C e C ++. Caso in questione, anche con la raccolta dei rifiuti, le API della maggior parte delle lingue assumono strutture di dati mutabili. Il fatto che tutto sia mutevole e l'immutabilità è visto come un caso esemplare che parla a lungo della mentalità imperativa dietro le lingue popolari.
EDIT : Dopo aver riflettuto sul commento di Greenoldman mi sono reso conto che const
non riguarda direttamente l'immutabilità dei dati; const
codifica nel tipo di metodo se ha effetti collaterali sull'istanza.
È possibile utilizzare la mutazione per ottenere un comportamento referenzialmente trasparente . Supponiamo di avere una funzione che, quando chiamata, restituisce in successione valori diversi, ad esempio una funzione che legge un singolo carattere da stdin
. Potremmo usare cache / memoize i risultati di questa funzione per produrre un flusso di valori referenzialmente trasparente. Lo stream sarebbe un elenco collegato i cui nodi chiameranno la funzione la prima volta che si tenta di recuperare il loro valore, ma quindi memorizzare nella cache il risultato. Quindi, se il stdin
problema persiste Hello, world!
, la prima volta che provi a recuperare il valore del primo nodo, ne leggerà uno char
e tornerà H
. Successivamente continuerà a tornare H
senza ulteriori chiamate per leggere a char
. Allo stesso modo, il secondo nodo avrebbe letto un char
dastdin
la prima volta che provi a recuperarne il valore, questa volta restituisce e
e memorizza nella cache quel risultato.
La cosa interessante qui è che hai trasformato un processo intrinsecamente stato in un oggetto apparentemente senza stato. Tuttavia, è stato necessario mutare lo stato interno dell'oggetto (memorizzando nella cache i risultati) per raggiungere questo obiettivo: la mutazione era un effetto benigno . È impossibile rendere il nostro CharStream
const
anche se il flusso si comporta come un valore immutabile. Ora immagina che ci sia Stream
un'interfaccia con i const
metodi e tutte le tue funzioni si aspettano const Streams
. Il tuo CharStream
non può implementare l'interfaccia!
( EDIT 2: Apparentemente c'è una parola chiave C ++ chiamata mutable
che ci permetterebbe di imbrogliare e creareCharStream
const
. Tuttavia, questa scappatoia distrugge const
le garanzie - ora non puoi davvero essere sicuro che qualcosa non muterà attraverso i suoi const
metodi. Suppongo che non sia così male dal momento che è necessario richiedere esplicitamente la scappatoia, ma si fa ancora completamente affidamento sul sistema d'onore.)
In secondo luogo, supponiamo di avere funzioni di ordine elevato, ovvero è possibile passare funzioni come argomenti ad altre funzioni. const
ness fa parte della firma di una funzione, quindi non si sarebbe in grado di passare le non const
funzioni come argomenti alle funzioni che si aspettano const
funzioni. Applicare ciecamente const
qui porterebbe a una perdita di generalità.
Infine, manipolare un const
oggetto non garantisce che non stia mutando uno stato esterno (statico o globale) alle tue spalle, quindi const
le garanzie non sono così forti come sembrano inizialmente.
Non mi è chiaro che codificare la presenza o l'assenza di effetti collaterali nel sistema dei tipi sia universalmente una buona cosa.