AGGIORNAMENTO: Questa domanda è stata oggetto del mio blog l'8 giugno 2012 . Grazie per l'ottima domanda!
Ottima domanda. Abbiamo discusso le questioni che sollevi per molto, molto tempo.
Vorremmo avere una struttura dati che abbia le seguenti caratteristiche:
- Immutabile.
- La forma di un albero.
- Accesso economico ai nodi padre dai nodi figlio.
- È possibile mappare da un nodo nell'albero a un offset di carattere nel testo.
- Persistente .
Per persistenza intendo la capacità di riutilizzare la maggior parte dei nodi esistenti nell'albero quando viene apportata una modifica al buffer di testo. Poiché i nodi sono immutabili, non ci sono ostacoli al loro riutilizzo. Ne abbiamo bisogno per le prestazioni; non possiamo rieseguire l'analisi di enormi porzioni del file ogni volta che si preme un tasto. Dobbiamo ri-lex e rieseguire il parsing solo delle parti dell'albero che sono state influenzate dalla modifica.
Ora, quando provi a mettere tutte e cinque queste cose in una struttura dati, incappi immediatamente in problemi:
- Come si costruisce un nodo in primo luogo? Il genitore e il bambino si riferiscono entrambi e sono immutabili, quindi quale viene costruito per primo?
- Supponendo che tu riesca a risolvere quel problema: come renderlo persistente? Non è possibile riutilizzare un nodo figlio in un genitore diverso perché ciò implicherebbe dire al figlio che ha un nuovo genitore. Ma il bambino è immutabile.
- Supponiamo che tu riesca a risolvere quel problema: quando inserisci un nuovo carattere nel buffer di modifica, la posizione assoluta di ogni nodo che è mappato in una posizione dopo quel punto cambia. Ciò rende molto difficile creare una struttura dati persistente, perché qualsiasi modifica può cambiare gli intervalli della maggior parte dei nodi!
Ma nel team di Roslyn facciamo regolarmente cose impossibili. In realtà facciamo l'impossibile mantenendo due alberi di analisi. L'albero "verde" è immutabile, persistente, non ha riferimenti padre, è costruito "dal basso verso l'alto" e ogni nodo tiene traccia della sua larghezza ma non della sua posizione assoluta . Quando si verifica una modifica, ricostruiamo solo le parti dell'albero verde che sono state influenzate dalla modifica, che in genere è circa O (log n) del totale dei nodi di analisi nell'albero.
L'albero "rosso" è una facciata immutabile che è costruita intorno all'albero verde; è costruito "dall'alto verso il basso" su richiesta e gettato via ad ogni modifica. Calcola i riferimenti padre producendoli su richiesta mentre scendi attraverso l'albero dall'alto . Produce posizioni assolute calcolandole dalle larghezze, di nuovo, mentre scendi.
Tu, l'utente, vedi sempre e solo l'albero rosso; l'albero verde è un dettaglio di implementazione. Se guardi nello stato interno di un nodo di analisi, vedrai infatti che c'è un riferimento a un altro nodo di analisi di un tipo diverso; questo è il nodo dell'albero verde.
Per inciso, questi sono chiamati "alberi rosso / verde" perché quelli erano i colori dell'indicatore della lavagna che abbiamo usato per disegnare la struttura dei dati nella riunione di progettazione. Non c'è altro significato per i colori.
Il vantaggio di questa strategia è che otteniamo tutte quelle grandi cose: immutabilità, persistenza, riferimenti ai genitori e così via. Il costo è che questo sistema è complesso e può consumare molta memoria se le facciate "rosse" diventano grandi. Attualmente stiamo effettuando esperimenti per vedere se possiamo ridurre alcuni dei costi senza perdere i benefici.