val-mutable contro var-immutable in Scala


97

Esistono linee guida in Scala su quando usare val con una collezione mutabile rispetto all'uso di var con una collezione immutabile? O dovresti davvero puntare a val con una collezione immutabile?

Il fatto che ci siano entrambi i tipi di collezione mi dà molta scelta e spesso non so come fare quella scelta.


Risposte:


104

Domanda abbastanza comune, questa. La cosa difficile è trovare i duplicati.

Dovresti lottare per la trasparenza referenziale . Ciò significa che, se ho un'espressione "e", potrei fare una val x = ee sostituirla econ x. Questa è la proprietà che la mutabilità interrompe. Ogni volta che è necessario prendere una decisione di progettazione, massimizza la trasparenza referenziale.

In pratica, un metodo locale varè il più sicuro varche esista, poiché non sfugge al metodo. Se il metodo è breve, ancora meglio. Se non lo è, prova a ridurlo estraendo altri metodi.

D'altra parte, una collezione mutevole ha il potenziale per sfuggire, anche se non lo fa. Quando si modifica il codice, è possibile passarlo ad altri metodi o restituirlo. Questo è il genere di cose che infrange la trasparenza referenziale.

Su un oggetto (un campo) accade più o meno la stessa cosa, ma con conseguenze più disastrose. In entrambi i casi l'oggetto avrà uno stato e, quindi, interromperà la trasparenza referenziale. Ma avere una collezione mutevole significa che anche l'oggetto stesso potrebbe perdere il controllo di chi lo sta cambiando.


38
Bello, nuovo grande immagine nella mia mente: Preferisco immutable valsopra immutable varsopra mutable valsopra mutable var. Specialmente immutable varfinita mutable val!
Peter Schmitz

2
Tieni presente che potresti ancora chiudere (come nel leak una "funzione" con effetti collaterali che può cambiarla) su un mutabile locale var. Un'altra caratteristica interessante dell'utilizzo di raccolte immutabili è che puoi mantenere in modo efficiente le vecchie copie, anche se le varmutano.
Mysterious Dan

1
tl; dr: preferisci var x: Set[Int] over val x: mutable.Set[Int] poiché se passi xa qualche altra funzione, nel primo caso, sei sicuro, quella funzione non può mutare xper te.
pathikrit

17

Se lavori con raccolte immutabili e hai bisogno di "modificarle", ad esempio, aggiungendovi elementi in un ciclo, allora devi usare vars perché devi memorizzare la raccolta risultante da qualche parte. Se leggi solo da raccolte immutabili, usa vals.

In generale, assicurati di non confondere riferimenti e oggetti. vals sono riferimenti immutabili (puntatori costanti in C). Cioè, quando lo usi val x = new MutableFoo(), sarai in grado di cambiare l'oggetto a cui xpunta, ma non sarai in grado di cambiare a quale oggetto x punta. Se usi var x = new ImmutableFoo(). Riprendo il mio consiglio iniziale: se non hai bisogno di cambiare a quale oggetto punta un riferimento, usa vals.


1
var immutable = something(); immutable = immutable.update(x)sconfigge lo scopo di utilizzare una raccolta immutabile. Hai già rinunciato alla trasparenza referenziale e di solito puoi ottenere lo stesso effetto da una collezione mutevole con una migliore complessità temporale. Delle quattro possibilità ( vale var, mutevole e immutabile), questa ha meno senso. Lo uso spesso val mutable.
Jim Pivarski,

3
@JimPivarski Non sono d'accordo, come fanno gli altri, vedere la risposta di Daniel e il commento di Peter. Se è necessario aggiornare una struttura dati, l'utilizzo di una var immutabile invece di una val mutabile ha il vantaggio di poter far trapelare i riferimenti alla struttura senza rischiare che venga modificata da altri in un modo che infrange le proprie ipotesi locali. Lo svantaggio per questi "altri" è che potrebbero leggere dati non aggiornati.
Malte Schwerhoff

Ho cambiato idea e sono d'accordo con te (lascio il mio commento originale per la storia). Da allora l'ho usato, soprattutto in var list: List[X] = Nil; list = item :: list; ...e avevo dimenticato che una volta scrivevo in modo diverso.
Jim Pivarski

@MalteSchwerhoff: "dati obsoleti" è effettivamente desiderabile, a seconda di come hai progettato il tuo programma, se la coerenza è cruciale; questo è ad esempio uno dei principali principi alla base del funzionamento della concorrenza in Clojure.
Erik Kaplun

@ErikAllik Non direi che i dati non aggiornati siano desiderabili di per sé, ma sono d'accordo sul fatto che possono essere perfettamente a posto, a seconda delle garanzie che vuoi / devi dare ai tuoi clienti. O hai un esempio in cui il solo fatto di leggere dati non aggiornati è effettivamente un vantaggio? Non intendo le conseguenze dell'accettazione di dati obsoleti, che potrebbero essere prestazioni migliori o un'API più semplice.
Malte Schwerhoff

9

Il modo migliore per rispondere a questa domanda è con un esempio. Supponiamo di avere qualche processo che raccoglie semplicemente i numeri per qualche motivo. Vorremmo registrare questi numeri e invieremo la raccolta a un altro processo per farlo.

Naturalmente, stiamo ancora raccogliendo numeri dopo aver inviato la raccolta al logger. E diciamo che c'è un sovraccarico nel processo di registrazione che ritarda la registrazione effettiva. Spero che tu possa vedere dove sta andando.

Se memorizziamo questa raccolta in un mutabile val(mutabile perché la stiamo aggiungendo continuamente), ciò significa che il processo che esegue la registrazione guarderà lo stesso oggetto che è ancora aggiornato dal nostro processo di raccolta. Quella raccolta può essere aggiornata in qualsiasi momento, quindi quando è il momento di accedere potremmo non registrare effettivamente la raccolta che abbiamo inviato.

Se usiamo un immutabile var, inviamo una struttura dati immutabile al logger. Quando aggiungeremo più numeri alla nostra raccolta, sostituiremo il nostro varcon una nuova struttura dati immutabile . Questo non significa che la raccolta inviata al logger venga sostituita! Fa ancora riferimento alla raccolta che è stata inviata. Quindi il nostro logger registrerà effettivamente la raccolta che ha ricevuto.



-2

var immutable vs. val mutable

Oltre a tante ottime risposte a questa domanda. Ecco un semplice esempio, che illustra i potenziali pericoli di val mutable:

Gli oggetti mutabili possono essere modificati all'interno di metodi, che li prendono come parametri, mentre la riassegnazione non è consentita.

import scala.collection.mutable.ArrayBuffer

object MyObject {
    def main(args: Array[String]) {

        val a = ArrayBuffer(1,2,3,4)
        silly(a)
        println(a) // a has been modified here
    }

    def silly(a: ArrayBuffer[Int]): Unit = {
        a += 10
        println(s"length: ${a.length}")
    }
}

Risultato:

length: 5
ArrayBuffer(1, 2, 3, 4, 10)

Qualcosa del genere non può accadere var immutable, perché la riassegnazione non è consentita:

object MyObject {
    def main(args: Array[String]) {
        var v = Vector(1,2,3,4)
        silly(v)
        println(v)
    }

    def silly(v: Vector[Int]): Unit = {
        v = v :+ 10 // This line is not valid
        println(s"length of v: ${v.length}")
    }
}

Risultati in:

error: reassignment to val

Poiché i parametri della funzione vengono trattati come valtale riassegnazione non è consentita.


Questo non è corretto. Il motivo per cui hai ricevuto quell'errore è perché hai usato Vector nel tuo secondo esempio che, per impostazione predefinita, è immutabile. Se usi un ArrayBuffer vedrai che si compila bene e fa la stessa cosa aggiungendo semplicemente il nuovo elemento e stampando il buffer mutato. pastebin.com/vfq7ytaD
EdgeCaseBerg,

@EdgeCaseBerg, sto intenzionalmente usando un vettore nel mio secondo esempio, perché sto cercando di mostrare che il comportamento del primo esempio mutable valnon è possibile con il immutable var. Cosa c'è di sbagliato qui?
Akavall,

Stai confrontando le mele con le arance qui. Il vettore non ha un +=metodo come il buffer dell'array. Le tue risposte implicano che +=è la stessa x = x + ycosa che non è. La tua affermazione che i parametri di funzione sono trattati come vals è corretta e ottieni l'errore che hai menzionato ma solo perché hai usato =. Puoi ottenere lo stesso errore con un ArrayBuffer, quindi la mutabilità delle raccolte qui non è realmente rilevante. Quindi non è una buona risposta perché non sta ottenendo ciò di cui parla l'OP. Anche se è un buon esempio dei pericoli di passare in giro una collezione mutevole se non si intendeva.
EdgeCaseBerg

@EdgeCaseBerg Ma non puoi replicare il comportamento con cui ottengo ArrayBuffer, usando Vector. La domanda dell'OP è ampia, ma stavano cercando suggerimenti su quando usarli, quindi credo che la mia risposta sia utile perché illustra i pericoli del passaggio della collezione mutabile (il fatto che sia valnon aiuta); immutable varè più sicuro di mutable val.
Akavall
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.