Was ist der Unterschied zwischen einer var- und einer val-Definition in Scala?


301

Was ist der Unterschied zwischen a varund valDefinition in Scala und warum braucht die Sprache beides? Warum würden Sie ein valüber ein varund umgekehrt wählen ?

Antworten:


331

Wie so viele andere gesagt haben, kann das einer zugewiesene Objekt valnicht ersetzt werden, und das einer varDose zugewiesene Objekt . Der interne Zustand des Objekts kann jedoch geändert werden. Zum Beispiel:

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.
  }
}

Obwohl wir das zugewiesene Objekt nicht ändern können x, können wir den Status dieses Objekts ändern. An der Wurzel lag jedoch ein var.

Unveränderlichkeit ist aus vielen Gründen eine gute Sache. Erstens, wenn ein Objekt den internen Status nicht ändert, müssen Sie sich keine Sorgen machen, wenn ein anderer Teil Ihres Codes es ändert. Zum Beispiel:

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")

Dies ist besonders wichtig bei Multithread-Systemen. In einem Multithread-System kann Folgendes passieren:

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

Wenn Sie valausschließlich unveränderliche Datenstrukturen verwenden (dh Arrays, alles in scala.collection.mutableusw. vermeiden ), können Sie sicher sein, dass dies nicht der Fall ist. Das heißt, es sei denn, es gibt Code, vielleicht sogar ein Framework, das Reflexionstricks ausführt - Reflexion kann leider "unveränderliche" Werte ändern.

Das ist ein Grund, aber es gibt noch einen anderen Grund dafür. Wenn Sie verwenden var, können Sie versucht sein, dasselbe varfür mehrere Zwecke wiederzuverwenden. Dies hat einige Probleme:

  • Für Benutzer, die den Code lesen, ist es schwieriger zu wissen, welchen Wert eine Variable in einem bestimmten Teil des Codes hat.
  • Möglicherweise vergessen Sie, die Variable in einem Codepfad neu zu initialisieren, und übergeben am Ende falsche Werte im Code.

Einfach ausgedrückt ist die Verwendung valsicherer und führt zu besser lesbarem Code.

Wir können also in die andere Richtung gehen. Wenn valdas besser ist, warum varüberhaupt? Nun, einige Sprachen haben diesen Weg eingeschlagen, aber es gibt Situationen, in denen die Veränderlichkeit die Leistung erheblich verbessert.

Nehmen Sie zum Beispiel eine unveränderliche Queue. Wenn Sie entweder enqueueoder dequeueDinge darin, erhalten Sie ein neues QueueObjekt. Wie würden Sie dann alle darin enthaltenen Elemente verarbeiten?

Ich werde das mit einem Beispiel durchgehen. Angenommen, Sie haben eine Warteschlange mit Ziffern und möchten daraus eine Zahl zusammensetzen. Wenn ich beispielsweise eine Warteschlange mit 2, 1, 3 in dieser Reihenfolge habe, möchte ich die Nummer 213 zurückerhalten. Lösen wir sie zunächst mit mutable.Queue:

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

Dieser Code ist schnell und einfach zu verstehen. Der Hauptnachteil besteht darin, dass die übergebene Warteschlange von geändert wird toNum, sodass Sie zuvor eine Kopie davon erstellen müssen. Das ist die Art von Objektverwaltung, von der Unveränderlichkeit Sie frei macht.

Jetzt verdecken wir es zu einem 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)
}

Da ich eine Variable nicht wiederverwenden kann, um meine zu verfolgen num, wie im vorherigen Beispiel, muss ich auf die Rekursion zurückgreifen. In diesem Fall handelt es sich um eine Schwanzrekursion, die eine ziemlich gute Leistung aufweist. Dies ist jedoch nicht immer der Fall: Manchmal gibt es einfach keine gute (lesbare, einfache) Lösung für die Schwanzrekursion.

Beachten Sie jedoch, dass ich diesen Code neu schreiben kann, um ein immutable.Queueund ein vargleichzeitig zu verwenden! Zum Beispiel:

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
}

Dieser Code ist immer noch effizient, erfordert keine Rekursion und Sie müssen sich keine Sorgen machen, ob Sie vor dem Aufruf eine Kopie Ihrer Warteschlange erstellen müssen oder nicht toNum. Natürlich habe ich es vermieden, Variablen für andere Zwecke wiederzuverwenden, und kein Code außerhalb dieser Funktion sieht sie, sodass ich mir keine Sorgen machen muss, dass sich ihre Werte von einer Zeile zur nächsten ändern - außer wenn ich dies ausdrücklich tue.

Scala entschied sich dafür, den Programmierer dies tun zu lassen, wenn der Programmierer dies für die beste Lösung hielt. Andere Sprachen haben sich dafür entschieden, solchen Code schwierig zu machen. Der Preis, den Scala (und jede Sprache mit weit verbreiteter Veränderlichkeit) zahlt, ist, dass der Compiler nicht so viel Spielraum bei der Optimierung des Codes hat, wie er es sonst könnte. Die Antwort von Java darauf besteht darin, den Code basierend auf dem Laufzeitprofil zu optimieren. Wir könnten auf jeder Seite über Vor- und Nachteile sprechen.

Persönlich denke ich, dass Scala vorerst die richtige Balance findet. Es ist bei weitem nicht perfekt. Ich denke, sowohl Clojure als auch Haskell haben sehr interessante Vorstellungen, die nicht von Scala übernommen wurden, aber Scala hat auch ihre eigenen Stärken. Wir werden sehen, was in der Zukunft auf uns zukommt.


Ein bisschen spät, aber ... Macht var qr = qeine Kopie von q?

5
@davips Es wird keine Kopie des Objekts erstellt, auf das verwiesen wird q. Es wird eine Kopie des Verweises auf dieses Objekt erstellt - auf dem Stapel, nicht auf dem Heap . Was die Leistung betrifft, müssen Sie klarer sein, von welchem ​​"es" Sie sprechen.
Daniel C. Sobral

Ok, mit Ihrer Hilfe und einigen Informationen ( (x::xs).drop(1)ist genau xskeine "Kopie" von xs) von diesem Link konnte ich verstehen. tnx!

"Dieser Code ist immer noch effizient" - oder? Da qres sich um eine unveränderliche Warteschlange handelt, wird bei jedem qr.dequeueAufruf des Ausdrucks ein new Queue(siehe < github.com/scala/scala/blob/2.13.x/src/library/scala/collection/… ) erstellt.
Owen

@ Owen Ja, aber beachte, dass es sich um ein flaches Objekt handelt. Der Code ist immer noch O (n), ob veränderlich, wenn Sie die Warteschlange kopieren oder unveränderlich.
Daniel C. Sobral

61

valist endgültig, kann also nicht gesetzt werden. Denken Sie finalin Java.


3
Aber wenn ich es richtig verstehe (kein Scala-Experte), sind val Variablen unveränderlich, aber die Objekte, auf die sie verweisen, müssen es nicht sein. Laut dem Link, den Stefan gepostet hat: "Hier kann die Namensreferenz nicht geändert werden, um auf ein anderes Array zu verweisen, aber das Array selbst kann geändert werden. Mit anderen Worten, der Inhalt / die Elemente des Arrays können geändert werden." Es ist also so, wie es finalin Java funktioniert.
Daniel Pryden

3
Genau deshalb habe ich es so gepostet, wie es ist. Ich kann +=auf eine veränderliche Hashmap zurückgreifen, die als valgut definiert ist - ich glaube, genau so finalfunktioniert es in Java
Jackson Davis

Ack, ich dachte, die eingebauten Scala-Typen könnten besser funktionieren, als nur eine Neuzuweisung zuzulassen. Ich muss die Fakten überprüfen.
Stefan Kendall

Ich habe Scalas unveränderliche Sequenztypen mit dem allgemeinen Begriff verwechselt. Die funktionale Programmierung hat mich alle umgedreht.
Stefan Kendall

Ich habe in Ihrer Antwort einen Dummy-Charakter hinzugefügt und entfernt, damit ich Ihnen die positive Bewertung geben kann.
Stefan Kendall


20

Der Unterschied besteht darin, dass a varneu zugewiesen werden kann, während a valnicht zugewiesen werden kann. Die Veränderbarkeit oder das Gegenteil von dem, was tatsächlich zugewiesen ist, ist ein Nebenproblem:

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.

Wohingegen:

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

Und daher:

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

Wenn Sie eine Datenstruktur erstellen und alle Felder vals sind, ist diese Datenstruktur daher unveränderlich, da sich ihr Status nicht ändern kann.


1
Dies gilt nur, wenn die Klassen dieser Felder ebenfalls unveränderlich sind.
Daniel C. Sobral

Ja - ich wollte das einfügen, aber ich dachte, es könnte ein Schritt zu weit sein! Es ist auch ein strittiger Punkt, den ich sagen würde; aus einer Perspektive (wenn auch keine funktionale) ändert sich sein Zustand nicht, selbst wenn der Zustand seines Zustands dies tut.
oxbow_lakes

Warum ist es immer noch so schwierig, ein unveränderliches Objekt in einer JVM-Sprache zu erstellen? Warum hat Scala Objekte nicht standardmäßig unveränderlich gemacht?
Derek Mahar

19

valbedeutet unveränderlich und varbedeutet veränderlich.

Vollständige Diskussion.


1
Das ist einfach nicht wahr. Der verlinkte Artikel gibt ein veränderliches Array an und nennt es unveränderlich. Keine ernsthafte Quelle.
Klaus Schulz

In der Tat nicht wahr. Versuchen Sie, dass val b = Array [Int] (1,2,3) b (0) = 4 println (b.mkString ("")) println ("")
Guillaume

13

Denken in C ++,

val x: T

ist analog zum konstanten Zeiger auf nicht konstante Daten

T* const x;

während

var x: T 

ist analog zum nicht konstanten Zeiger auf nicht konstante Daten

T* x;

Die Bevorzugung valgegenüber varerhöht die Unveränderlichkeit der Codebasis, was ihre Korrektheit, Parallelität und Verständlichkeit erleichtern kann.

Um die Bedeutung eines konstanten Zeigers auf nicht konstante Daten zu verstehen, betrachten Sie das folgende Scala-Snippet:

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

Hier ist der "Zeiger" val m konstant, so dass wir ihn nicht neu zuweisen können, um auf etwas anderes zu zeigen

m = n // error: reassignment to val

Wir können jedoch tatsächlich die nicht konstanten Daten selbst ändern, die darauf hinweisen, dass dies mgefällt

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

1
Ich denke, Scala hat die Unveränderlichkeit nicht zu ihrem endgültigen Ergebnis gebracht: konstanter Zeiger und konstante Daten. Scala verpasste die Gelegenheit, Objekte standardmäßig unveränderlich zu machen. Folglich hat Scala nicht den gleichen Wertbegriff wie Haskell.
Derek Mahar

@DerekMahar Sie haben Recht, aber ein Objekt kann sich als vollkommen unveränderlich darstellen, während es bei seiner Implementierung weiterhin Veränderlichkeit verwendet, z . B. aus Leistungsgründen. Wie könnte der Compiler zwischen echter Mutabilität und nur interner Mutabilität unterscheiden?
Francoisr

8

"val bedeutet unveränderlich und var bedeutet veränderlich."

Um es zu paraphrasieren: "val bedeutet Wert und var bedeutet Variable".

Eine Unterscheidung, die beim Rechnen äußerst wichtig ist (weil diese beiden Konzepte die Essenz dessen definieren, worum es beim Programmieren geht), und dass OO es geschafft hat, fast vollständig zu verschwimmen, weil in OO das einzige Axiom ist, dass "alles ein ist Objekt". Infolgedessen neigen viele Programmierer heutzutage dazu, nicht zu verstehen / zu schätzen / zu erkennen, weil sie einer Gehirnwäsche unterzogen wurden, um ausschließlich "nach OO-Art zu denken". Dies führt häufig dazu, dass variable / veränderbare Objekte wie überall verwendet werden , wenn Werte / unveränderliche Objekte möglicherweise / besser gewesen wären.


Aus diesem Grund bevorzuge ich beispielsweise Haskell gegenüber Java.
Derek Mahar

6

val bedeutet unveränderlich und var bedeutet veränderlich

Sie können valals Java Programmiersprache finalSchlüsselwelt oder C ++ Sprache constSchlüsselwelt denken 。


1

Valbedeutet, dass es endgültig ist , kann nicht neu zugewiesen werden

Während, Varkann später neu zugewiesen .


1
Wie unterscheidet sich diese Antwort von den 12 bereits eingereichten Antworten?
Jwvh

0

Es ist so einfach wie es heißt.

var bedeutet, dass es variieren kann

val bedeutet unveränderlich


0

Werte sind typisierte Speicherkonstanten. Einmal erstellt, kann sein Wert nicht neu zugewiesen werden. Mit dem Schlüsselwort val kann ein neuer Wert definiert werden.

z.B. val x: Int = 5

Hier ist der Typ optional, da Scala ihn aus dem zugewiesenen Wert ableiten kann.

Var - Variablen sind typisierte Speichereinheiten, denen wieder Werte zugewiesen werden können, solange Speicherplatz reserviert ist.

z.B. var x: Int = 5

In beiden Speichereinheiten gespeicherte Daten werden von JVM automatisch freigegeben, sobald diese nicht mehr benötigt werden.

In Scala werden Werte aufgrund der Stabilität gegenüber Variablen bevorzugt, was den Code insbesondere im gleichzeitigen und Multithread-Code beeinflusst.


0

Obwohl viele bereits den Unterschied zwischen Val und var beantwortet haben . Es ist jedoch zu beachten, dass val nicht genau dem endgültigen Schlüsselwort entspricht.

Wir können den Wert von val mithilfe der Rekursion ändern, aber wir können den Wert von final niemals ändern. Final ist konstanter als Val.

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

Methodenparameter sind standardmäßig val und werden bei jedem Aufruf geändert.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.