Non posso davvero rispondere al n. 2 senza perdersi (ci sono troppe dimensioni lungo le quali è possibile confrontare queste strutture), ma per il n. 3 la risposta è piuttosto semplice.
Utilizzare una struttura di dati imperativa se: (a) non esiste assolutamente alcun aliasing o (b) è davvero necessario utilizzare l'aliasing per una trasmissione efficiente.
Se non esiste alcun aliasing della struttura dei dati, non si sta sfruttando il fatto che le strutture di dati funzionali siano persistenti. Quindi non c'è motivo di pagare per i loro costi. Ci sono due avvertenze per questo consiglio. Innanzitutto, potresti preferire la semplicità di implementazione di una struttura di dati funzionale: implementare la cancellazione per un albero rosso-nero funzionale ti farà maledire, ma implementare la cancellazione in un albero rosso-nero imperativo con puntatori genitore ti lascerà a contemplare il suicidio. In secondo luogo, l'assegnazione può essere più costosa di quanto ci si aspetti in un linguaggio gc'd, poiché le scritture possono spostare le strutture di dati fuori dalla generazione giovane. Non abbiamo davvero una buona teoria degli effetti cache e gc, quindi non hai altra scelta che fare benchmark.
In secondo luogo, se hai bisogno di un canale di trasmissione, una struttura di dati condivisa è un modo eccellente per farlo. Con un aggiornamento a tempo costante, puoi dire arbitrariamente a molte altre persone che un valore è cambiato. (Questo è il motivo per cui union-find è una struttura di dati così eccezionale.) Con una configurazione puramente funzionale, o è necessario modificare tutte quelle altre persone o dare loro puntatori astratti in uno stato che si codifica manualmente (che è una specie di ottuso cose da fare).
Se o non vuoi ragionare sull'aliasing e sulla proprietà degli oggetti, o se hai bisogno di più versioni della stessa struttura di dati (hai bisogno sia di una nuova che di una vecchia versione, diciamo), usa semplicemente una struttura di dati funzionale.
Il posto in cui trovo più difficile seguire questi consigli è con gli algoritmi grafici. Ci sono molti algoritmi grafici imperativi davvero eleganti, ma spesso è il caso (diciamo, quando si scrivono compilatori) che si desidera anche la persistenza. Le persone in genere cercano di dividere la differenza e utilizzano l'algoritmo imperativo freddo, ma cercano di bloccare la versione sul lato per ottenere la persistenza. Questo è generalmente piuttosto orribile, pieno di bug e incline a perdere il vantaggio prestazionale dell'algoritmo imperativo.