Jaka jest różnica między definicją var a val w Scali?


301

Jaka jest różnica między a vara valdefinicją w Scali i dlaczego język potrzebuje obu? Dlaczego miałbyś wybrać valponad vari odwrotnie?

Odpowiedzi:


331

Jak wielu innych powiedziało, obiektu przypisanego do valpuszki nie można zastąpić, a obiektu przypisanego do varpuszki. Jednak wymieniony obiekt może mieć zmodyfikowany stan wewnętrzny. Na przykład:

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

Tak więc, chociaż nie możemy zmienić przypisanego obiektu x, możemy zmienić stan tego obiektu. U jego podstaw leżało jednak var.

Niezmienność jest dobra z wielu powodów. Po pierwsze, jeśli obiekt nie zmienia stanu wewnętrznego, nie musisz się martwić, że zmienia go jakaś inna część kodu. Na przykład:

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

Staje się to szczególnie ważne w systemach wielowątkowych. W systemie wielowątkowym mogą się zdarzyć:

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

Jeśli używasz valwyłącznie i używasz niezmiennych struktur danych (to znaczy unikaj tablic, wszystkiego w scala.collection.mutableitd.), Możesz mieć pewność, że tak się nie stanie. To znaczy, chyba że jest jakiś kod, może nawet framework, wykonujący sztuczki refleksyjne - odbicie może niestety zmienić „niezmienne” wartości.

To jeden powód, ale jest inny powód. Kiedy używasz var, możesz ulec pokusie ponownego użycia tego samego vardo wielu celów. Ma to pewne problemy:

  • Ludziom czytającym kod trudniej będzie wiedzieć, jaka jest wartość zmiennej w określonej części kodu.
  • Możesz zapomnieć o ponownej inicjalizacji zmiennej w jakiejś ścieżce kodu i ostatecznie przekazać niepoprawne wartości w dalszej części kodu.

Mówiąc prosto, używanie valjest bezpieczniejsze i prowadzi do bardziej czytelnego kodu.

Możemy zatem pójść w innym kierunku. Jeśli tak valjest lepiej, dlaczego varw ogóle mają ? Cóż, niektóre języki wybrały tę drogę, ale są sytuacje, w których zmienność bardzo poprawia wydajność.

Na przykład weź niezmienny Queue. Kiedy obaj enqueuelub dequeuerzeczy w nim, pojawi się nowy Queueobiekt. Jak więc zająłby się przetwarzaniem wszystkich elementów w nim zawartych?

Przejdę przez to z przykładem. Powiedzmy, że masz kolejkę cyfr i chcesz z nich utworzyć liczbę. Na przykład, jeśli mam kolejkę z 2, 1, 3 w tej kolejności, chcę odzyskać liczbę 213. Najpierw rozwiążmy ją za pomocą mutable.Queue:

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

Ten kod jest szybki i łatwy do zrozumienia. Jego główną wadą jest to, że przekazywana kolejka jest modyfikowana toNum, dlatego należy wcześniej wykonać jej kopię. Jest to rodzaj zarządzania obiektami, od którego niezmienność cię uwalnia.

Teraz ukryjmy to w 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)
}

Ponieważ nie mogę ponownie użyć jakiejś zmiennej do śledzenia mojej num, tak jak w poprzednim przykładzie, muszę skorzystać z rekurencji. W tym przypadku jest to rekurencja ogona, która ma całkiem niezłą wydajność. Ale nie zawsze tak jest: czasami nie ma po prostu dobrego (czytelnego, prostego) rozwiązania rekurencji ogona.

Zauważ jednak, że mogę przepisać ten kod, aby używać immutable.Queuei varjednocześnie! Na przykład:

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
}

Ten kod jest nadal skuteczny, nie wymaga rekurencji i nie musisz się martwić, czy musisz wykonać kopię kolejki, czy nie, zanim zadzwonisz toNum. Oczywiście unikałem ponownego wykorzystywania zmiennych do innych celów i żaden kod poza tą funkcją ich nie widzi, więc nie muszę się martwić, że ich wartości zmieniają się z jednej linii do drugiej - z wyjątkiem przypadków, gdy jawnie to robię.

Scala zdecydował się na to, aby programista mógł to zrobić, jeśli uzna to za najlepsze rozwiązanie. Inne języki zdecydowały się utrudnić taki kod. Cena, którą płaci Scala (i każdy język o powszechnej zmienności), polega na tym, że kompilator nie ma tak dużej swobody w optymalizacji kodu, jak mógłby inaczej. Odpowiedzią Javy na to jest optymalizacja kodu w oparciu o profil wykonawczy. Moglibyśmy ciągle mówić o zaletach i wadach każdej ze stron.

Osobiście uważam, że na razie Scala osiąga właściwą równowagę. Zdecydowanie nie jest idealny. Myślę, że zarówno Clojure, jak i Haskell mają bardzo interesujące pojęcia, które nie zostały przyjęte przez Scalę, ale Scala ma również swoje zalety. Zobaczymy, co będzie w przyszłości.


Trochę późno, ale ... Czy var qr = qrobi kopię q?

5
@davips Nie tworzy kopii obiektu, do którego się odwołuje q. Robi kopię - na stosie, a nie na stercie - odwołania do tego obiektu. Jeśli chodzi o wydajność, będziesz musiał lepiej określić, o czym mówisz.
Daniel C. Sobral

Ok, z twoją pomocą i pewnymi informacjami ( (x::xs).drop(1)to xsnie jest „kopia” xs) linku, który mogłem zrozumieć. tnx!

„Ten kod jest nadal skuteczny” - prawda? Ponieważ qrjest to kolejka niezmienna, za każdym razem, gdy qr.dequeuewywoływane jest wyrażenie , tworzy new Queue( a << github.com/scala/scala/blob/2.13.x/src/library/scala/collection/... ).
Owen,

@Owen Tak, ale zauważ, że jest to płytki przedmiot. Kod nadal ma wartość O (n), niezależnie od tego, czy można go modyfikować, czy kopiować kolejkę, czy jest niezmienny.
Daniel C. Sobral

61

valjest ostateczny, to znaczy nie można go ustawić. Pomyśl finalw java.


3
Ale jeśli dobrze rozumiem (nie jest ekspertem Scala), val zmienne są niezmienne, ale obiekty, do których się odwołują, nie muszą być. Zgodnie z linkiem opublikowanym przez Stefana: „Nie można zmienić odwołania do nazw, aby wskazywało na inną tablicę, ale samą tablicę można modyfikować. Innymi słowy, zawartość / elementy tablicy można modyfikować”. To tak, jak finaldziała w Javie.
Daniel Pryden

3
Właśnie dlatego opublikowałem to tak, jak jest. Mogę wywołać +=zmienioną mapę skrótów zdefiniowaną jako po valprostu w porządku - uważam, że dokładnie tak finaldziała w java
Jackson Davis,

Ack, myślałem, że wbudowane typy Scala mogą zrobić lepiej niż po prostu pozwolić na zmianę przypisania. Muszę sprawdzić fakty.
Stefan Kendall

Myliłem niezmienne typy sekwencji Scali z ogólnym pojęciem. Programowanie funkcjonalne mnie odwróciło.
Stefan Kendall

Dodałem i usunąłem manekina w twojej odpowiedzi, abym mógł dać ci głos.
Stefan Kendall


20

Różnica polega na tym, że varmożna przypisać a, a valnie można. Zmienność lub w inny sposób tego, co jest faktycznie przypisane, jest kwestią uboczną:

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.

Natomiast:

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

I stąd:

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

Jeśli budujesz strukturę danych, a wszystkie jej pola są vals, to struktura danych jest zatem niezmienna, ponieważ jej stan nie może się zmienić.


1
Jest to prawdą tylko wtedy, gdy klasy tych pól są również niezmienne.
Daniel C. Sobral

Tak - zamierzałem to włożyć, ale pomyślałem, że to może być o krok za daleko! Jest to również kwestia sporna; z jednej perspektywy (choć nie funkcjonalnej) jego stan nie zmienia się, nawet jeśli zmienia się stan jego stanu.
oxbow_lakes

Dlaczego tak trudno jest stworzyć niezmienny obiekt w języku JVM? Ponadto, dlaczego Scala domyślnie nie uczyniła obiektów niezmiennymi?
Derek Mahar

19

valoznacza niezmienny i varoznacza zmienny.

Pełna dyskusja.


1
To po prostu nieprawda. Połączony artykuł daje zmienną tablicę i nazywa ją niezmienną. Brak poważnego źródła.
Klaus Schulz

Rzeczywiście nieprawda. Spróbuj tego val b = Array [Int] (1,2,3) b (0) = 4 println (b.mkString ("")) println ("")
Guillaume

13

Myślenie w kategoriach C ++,

val x: T

jest analogiczny do stałego wskaźnika do nie stałych danych

T* const x;

podczas

var x: T 

jest analogiczny do niestałego wskaźnika do niestałych danych

T* x;

Faworyzowanie w valstosunku do varzwiększa niezmienność bazy kodu, co może ułatwić jej poprawność, współbieżność i zrozumiałość.

Aby zrozumieć znaczenie posiadania stałego wskaźnika do nietrwałych danych, rozważ następujący fragment kodu Scala:

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

W tym przypadku „wskaźnik” val m jest stały, więc nie możemy go ponownie przypisać, aby wskazywał na coś innego

m = n // error: reassignment to val

możemy jednak rzeczywiście zmienić nietrwałe dane, które to mwskazują

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

1
Myślę, że Scala nie doszedł do niezmienności do ostatecznego wniosku: stałego wskaźnika i stałych danych. Scala nie wykorzystał domyślnie możliwości uczynienia obiektów niezmiennymi. W związku z tym Scala nie ma takiego samego pojęcia wartości jak Haskell.
Derek Mahar

@DerekMahar masz rację, ale obiekt może prezentować się jako całkowicie niezmienny, jednocześnie wykorzystując zmienność w swojej implementacji, np . Ze względu na wydajność. W jaki sposób kompilator byłby w stanie rozdzielić między prawdziwą zmiennością a zmiennością tylko wewnętrzną?
francoisr

8

„val oznacza niezmienny, a var oznacza zmienny.”

Parafrazując „val oznacza wartość, a var oznacza zmienną”.

Rozróżnienie, które okazuje się niezwykle ważne w informatyce (ponieważ te dwie koncepcje definiują samą istotę tego, o co chodzi w programowaniu), i że OO udało się niemal całkowicie zatrzeć, ponieważ w OO jedynym aksjomatem jest to, że „wszystko jest obiekt". I w konsekwencji wielu programistów obecnie nie rozumie / nie docenia / nie rozpoznaje, ponieważ zostali oni poddani praniu mózgu wyłącznie w celu „myślenia OO”. Często prowadzi to do wykorzystywania obiektów zmiennych / zmiennych jak wszędzie , kiedy wartości / niezmienne obiekty mogłyby / często byłyby lepsze.


Dlatego na przykład wolę Haskell niż Javę.
Derek Mahar

6

val oznacza niezmienny, a var oznacza zmienny

możesz myśleć valjak finalświat programowania w języku Java lub świat kluczy w języku C ++ const


1

Valoznacza, że ​​jego ostateczny , nie może być przeniesiony

Natomiast Varmoże zostać ponownie przypisany później .


1
Czym różni się ta odpowiedź od 12 już przesłanych odpowiedzi?
jwvh


0

Val - wartości są wpisanymi stałymi pamięci. Po utworzeniu jego wartość nie może zostać ponownie przypisana. nową wartość można zdefiniować za pomocą słowa kluczowego val.

na przykład. val x: Int = 5

Tutaj typ jest opcjonalny, ponieważ scala może wywnioskować go z przypisanej wartości.

Zmienne - zmienne są wpisywanymi jednostkami pamięci, którym można ponownie przypisać wartości, o ile zarezerwowane jest miejsce w pamięci.

na przykład. var x: Int = 5

Dane przechowywane w obu jednostkach pamięci są automatycznie rozdzielane przez JVM, gdy nie są już potrzebne.

W skalach wartości są bardziej preferowane niż zmienne ze względu na stabilność, które doprowadzają do kodu, szczególnie w kodzie współbieżnym i wielowątkowym.


0

Chociaż wielu już odpowiedziało na różnicę między Val a var . Należy jednak zauważyć, że val nie jest dokładnie końcowym słowem kluczowym.

Możemy zmienić wartość val za pomocą rekurencji, ale nigdy nie możemy zmienić wartości końcowej. Ostateczny jest bardziej stały niż Val.

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

Parametry metody są domyślnie val i przy każdej zmianie wartość połączenia jest zmieniana.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.