Qual è la differenza tra una definizione var e val in Scala?


301

Qual è la differenza tra a vare la valdefinizione in Scala e perché la lingua ha bisogno di entrambi? Perché dovresti scegliere aval over a vare viceversa?

Risposte:


331

Come molti altri hanno già detto, l'oggetto assegnato a una valnon può essere sostituito e l'oggetto assegnato a una varlattina. Tuttavia, detto oggetto può avere il suo stato interno modificato. Per esempio:

class A(n: Int) {
  var value = n
}

class B(n: Int) {
  val value = new A(n)
}

object Test {
  def main(args: Array[String]) {
    val x = new B(5)
    x = new B(6) // Doesn't work, because I can't replace the object created on the line above with this new one.
    x.value = new A(6) // Doesn't work, because I can't replace the object assigned to B.value for a new one.
    x.value.value = 6 // Works, because A.value can receive a new object.
  }
}

Quindi, anche se non possiamo cambiare l'oggetto assegnato x, potremmo cambiare lo stato di quell'oggetto. Alla radice, tuttavia, c'era unvar .

Ora, l'immutabilità è una buona cosa per molte ragioni. Innanzitutto, se un oggetto non cambia lo stato interno, non devi preoccuparti se un'altra parte del codice lo sta modificando. Per esempio:

x = new B(0)
f(x)
if (x.value.value == 0)
  println("f didn't do anything to x")
else
  println("f did something to x")

Ciò diventa particolarmente importante con i sistemi multithread. In un sistema multithread, può succedere quanto segue:

x = new B(1)
f(x)
if (x.value.value == 1) {
  print(x.value.value) // Can be different than 1!
}

Se si utilizza valesclusivamente e si utilizzano solo strutture di dati immutabili (vale a dire, evitare array, tutto in scala.collection.mutableecc.), Si può essere certi che ciò non accadrà. Cioè, a meno che non ci sia del codice, forse persino un framework, che fa trucchi di riflessione - la riflessione può cambiare i valori "immutabili", sfortunatamente.

Questa è una ragione, ma c'è un'altra ragione. Quando lo usi var, puoi essere tentato di riutilizzare lo stesso varper diversi scopi. Questo ha alcuni problemi:

  • Sarà più difficile per le persone che leggono il codice sapere qual è il valore di una variabile in una determinata parte del codice.
  • Potresti dimenticare di reinizializzare la variabile in qualche percorso del codice e finire per passare valori errati a valle nel codice.

In poche parole, l'utilizzo valè più sicuro e porta a un codice più leggibile.

Possiamo, quindi, andare nella direzione opposta. Se valè meglio, perché averlo var? Bene, alcune lingue hanno preso quella strada, ma ci sono situazioni in cui la mutabilità migliora molto le prestazioni.

Ad esempio, prendi un immutabile Queue. Quando uno enqueueo più dequeuecose, ottieni un nuovo Queueoggetto. In che modo, ti occuperesti di elaborare tutti gli elementi in esso?

Lo esaminerò con un esempio. Diciamo che hai una coda di cifre e vuoi comporre un numero da loro. Ad esempio, se ho una coda con 2, 1, 3, in questo ordine, voglio recuperare il numero 213. Prima risolviamolo con un mutable.Queue:

def toNum(q: scala.collection.mutable.Queue[Int]) = {
  var num = 0
  while (!q.isEmpty) {
    num *= 10
    num += q.dequeue
  }
  num
}

Questo codice è veloce e facile da capire. Il suo principale svantaggio è che la coda che viene passata viene modificata da toNum, quindi è necessario crearne una copia in anticipo. Questo è il tipo di gestione degli oggetti da cui l'immutabilità ti rende libero.

Ora, convertiamolo in un immutable.Queue:

def toNum(q: scala.collection.immutable.Queue[Int]) = {
  def recurse(qr: scala.collection.immutable.Queue[Int], num: Int): Int = {
    if (qr.isEmpty)
      num
    else {
      val (digit, newQ) = qr.dequeue
      recurse(newQ, num * 10 + digit)
    }
  }
  recurse(q, 0)
}

Poiché non riesco a riutilizzare alcune variabili per tenere traccia delle mie num, come nell'esempio precedente, devo ricorrere alla ricorsione. In questo caso, si tratta di una ricorsione della coda, che ha prestazioni piuttosto buone. Ma non è sempre così: a volte non esiste una buona soluzione di ricorsione della coda (leggibile, semplice).

Nota, tuttavia, che posso riscrivere quel codice per usare un immutable.Queuee un varallo stesso tempo! Per esempio:

def toNum(q: scala.collection.immutable.Queue[Int]) = {
  var qr = q
  var num = 0
  while (!qr.isEmpty) {
    val (digit, newQ) = qr.dequeue
    num *= 10
    num += digit
    qr = newQ
  }
  num
}

Questo codice è ancora efficiente, non richiede ricorsione e non devi preoccuparti se devi fare una copia della coda o meno prima di chiamare toNum. Naturalmente, ho evitato di riutilizzare le variabili per altri scopi e nessun codice esterno a questa funzione le vede, quindi non devo preoccuparmi che i loro valori cambino da una riga all'altra, tranne quando lo faccio esplicitamente.

Scala ha optato per lasciare che il programmatore lo facesse, se il programmatore lo ritenesse la soluzione migliore. Altre lingue hanno scelto di rendere difficile tale codice. Il prezzo che Scala (e qualsiasi linguaggio con mutevole mutabilità) paga è che il compilatore non ha un margine di manovra nell'ottimizzare il codice come potrebbe altrimenti. La risposta di Java a questo è l'ottimizzazione del codice basato sul profilo di runtime. Potremmo continuare a parlare di pro e contro di entrambe le parti.

Personalmente, penso che Scala trovi il giusto equilibrio, per ora. Di gran lunga non è perfetto. Penso che sia Clojure che Haskell abbiano nozioni molto interessanti non adottate da Scala, ma Scala ha anche i suoi punti di forza. Vedremo cosa succederà nel futuro.


Un po 'in ritardo, ma ... Ne var qr = qfa una copia q?

5
@davips Non crea una copia dell'oggetto a cui fa riferimento q. Fa una copia - nello stack, non nell'heap - del riferimento a quell'oggetto. Per quanto riguarda le prestazioni, dovrai essere più chiaro di cosa "parli" di cui stai parlando.
Daniel C. Sobral,

Ok, con il tuo aiuto e alcune informazioni ( (x::xs).drop(1)è esattamentexs una "copia" di xs) da qui il link che ho capito. tnx!

"Questo codice è ancora efficiente" - vero? Poiché qrè una coda immutabile, ogni volta che l'espressione qr.dequeueviene chiamata crea un new Queue(vedi < github.com/scala/scala/blob/2.13.x/src/library/scala/collection/… ).
Owen,

@Owen Sì, ma nota che è un oggetto superficiale. Il codice è ancora O (n) se mutabile, se si copia la coda o immutabile.
Daniel C. Sobral,

61

valè finale, cioè non può essere impostato. Pensa finala Java.


3
Ma se capisco correttamente (non un esperto di Scala), le val variabili sono immutabili, ma gli oggetti a cui fanno riferimento non devono essere. Secondo il link pubblicato da Stefan: "Qui i riferimenti ai nomi non possono essere cambiati per puntare a un array diverso, ma l'array stesso può essere modificato. In altre parole, i contenuti / elementi dell'array possono essere modificati." Quindi è come finalfunziona in Java.
Daniel Pryden,

3
Esattamente perché l'ho pubblicato così com'è. Posso fare appello +=a una hashmap mutevole definita come valgiusta, credo proprio come funzioni finalin Java
Jackson Davis,

Ack, ho pensato che i tipi di scala integrati potessero fare meglio che semplicemente consentire la riassegnazione. Devo verificare i fatti.
Stefan Kendall,

Stavo confondendo i tipi di sequenza immutabili di Scala con l'idea generale. La programmazione funzionale mi ha completamente cambiato.
Stefan Kendall,

Ho aggiunto e rimosso un carattere fittizio nella tua risposta in modo da poterti dare il voto.
Stefan Kendall,


20

La differenza è che a varpuò essere riassegnato a mentre a valnon può. La mutabilità, o altrimenti di qualsiasi cosa sia effettivamente assegnata, è un problema secondario:

import collection.immutable
import collection.mutable
var m = immutable.Set("London", "Paris")
m = immutable.Set("New York") //Reassignment - I have change the "value" at m.

Mentre:

val n = immutable.Set("London", "Paris")
n = immutable.Set("New York") //Will not compile as n is a val.

E quindi:

val n = mutable.Set("London", "Paris")
n = mutable.Set("New York") //Will not compile, even though the type of n is mutable.

Se stai costruendo una struttura di dati e tutti i suoi campi sono vals, allora quella struttura di dati è quindi immutabile, poiché il suo stato non può cambiare.


1
Questo è vero solo se anche le classi di quei campi sono immutabili.
Daniel C. Sobral,

Sì, lo avrei inserito ma ho pensato che potrebbe essere un passo troppo avanti! È anche un punto discutibile, direi; da una prospettiva (anche se non funzionale) il suo stato non cambia anche se lo stato del suo stato cambia.
oxbow_lakes,

Perché è ancora così difficile creare un oggetto immutabile in un linguaggio JVM? Inoltre, perché Scala non ha reso gli oggetti immutabili per impostazione predefinita?
Derek Mahar,

19

valsignifica immutabile e varsignifica mutevole.

Discussione completa


1
Questo non è vero. L'articolo collegato fornisce un array mutabile e lo chiama immutabile. Nessuna fonte seria.
Klaus Schulz,

Non è proprio vero. Prova che val b = Array [Int] (1,2,3) b (0) = 4 println (b.mkString ("")) println ("")
Guillaume

13

Pensando in termini di C ++,

val x: T

è analogo al puntatore costante a dati non costanti

T* const x;

mentre

var x: T 

è analogo al puntatore non costante ai dati non costanti

T* x;

favorendo valovervar aumenta immutabilità del codice di base che può facilitare la sua correttezza, concorrenza e comprensibilità.

Per comprendere il significato di avere un puntatore costante a dati non costanti, prendere in considerazione il seguente frammento di Scala:

val m = scala.collection.mutable.Map(1 -> "picard")
m // res0: scala.collection.mutable.Map[Int,String] = HashMap(1 -> picard)

Qui il "puntatore" val m è costante, quindi non possiamo riassegnarlo per indicare qualcos'altro

m = n // error: reassignment to val

tuttavia possiamo davvero cambiare i dati non costanti stessi che mpuntano a così

m.put(2, "worf")
m // res1: scala.collection.mutable.Map[Int,String] = HashMap(1 -> picard, 2 -> worf)

1
Penso che Scala non abbia portato l'immutabilità alla sua conclusione finale: puntatore costante e dati costanti. Scala ha perso l'occasione di rendere gli oggetti immutabili per impostazione predefinita. Di conseguenza, Scala non ha la stessa nozione di valore di Haskell.
Derek Mahar,

@DerekMahar hai ragione, ma un oggetto può presentarsi perfettamente immutabile, pur usando la mutabilità nella sua implementazione, ad esempio per motivi di prestazioni. In che modo il compilatore sarebbe in grado di separare tra vera mutabilità e mutabilità solo interna?
francoisr,

8

"val significa immutabile e var significa mutevole".

Per parafrasare, "val significa valore e var significa variabile".

Una distinzione che risulta estremamente importante nell'informatica (perché quei due concetti definiscono l'essenza stessa della programmazione) e che OO è riuscito a sfocare quasi completamente, perché in OO l'unico assioma è che "tutto è un oggetto". E di conseguenza, molti programmatori oggigiorno tendono a non capire / apprezzare / riconoscere, perché sono stati sottoposti al lavaggio del cervello nel "pensare alla maniera OO" esclusivamente. Spesso conduce a oggetti variabili / mutabili usati come ovunque , quando oggetti valore / immutabili potrebbero / sarebbero stati spesso migliori.


Questo è il motivo per cui preferisco Haskell a Java, per esempio.
Derek Mahar,

6

val significa immutabile e var significa mutevole

puoi pensare valcome finalmondo chiave del linguaggio di programmazione Java o mondo chiave del linguaggio c ++ const


1

Valsignifica che è definitivo , non può essere riassegnato

Considerando che, Varpuò essere riassegnato più tardi .


1
In che modo questa risposta è diversa dalle 12 risposte già presentate?
jwvh,

0

È semplice come il suo nome.

var significa che può variare

val significa invariabile


0

Val: i valori sono costanti di memoria tipizzate. Una volta creato, il suo valore non può essere riassegnato. un nuovo valore può essere definito con la parola chiave val.

per esempio. val x: Int = 5

Qui il tipo è facoltativo in quanto scala può inferirlo dal valore assegnato.

Var - le variabili sono unità di memoria tipizzate a cui è possibile assegnare nuovamente valori purché lo spazio di memoria sia riservato.

per esempio. var x: Int = 5

I dati memorizzati in entrambe le unità di archiviazione vengono automaticamente allocati da JVM una volta che non sono più necessari.

In scala i valori sono preferiti alle variabili a causa della stabilità, ciò porta al codice in particolare nel codice simultaneo e multithread.


0

Sebbene molti abbiano già risposto alla differenza tra Val e var . Ma un punto da notare è che val non è esattamente come finale parola chiave .

Possiamo cambiare il valore di val usando la ricorsione, ma non possiamo mai cambiare il valore di final. Il finale è più costante di Val.

def factorial(num: Int): Int = {
 if(num == 0) 1
 else factorial(num - 1) * num
}

I parametri del metodo sono di default val e ad ogni chiamata il valore viene modificato.

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.