Czy w Scali są jakieś wytyczne dotyczące tego, kiedy używać val ze zmienną kolekcją, a kiedy var z niezmienną kolekcją? A może naprawdę powinieneś dążyć do val z niezmienną kolekcją?
Fakt, że istnieją oba rodzaje kolekcji, daje mi duży wybór, a często nie wiem, jak tego dokonać.
scala
collections
functional-programming
immutability
James McCabe
źródło
źródło
Odpowiedzi:
To dość powszechne pytanie. Trudno jest znaleźć duplikaty.
Należy dążyć do referencyjnej przejrzystości . Co to znaczy, że jeśli mam wyrażenie „e”, mogę zrobić
val x = e
i wymieniće
zx
. To jest właściwość, którą łamie zmienność. Zawsze, gdy musisz podjąć decyzję projektową, maksymalizuj ją, aby uzyskać referencyjną przejrzystość.Z praktycznego punktu widzenia metoda lokalna
var
jest najbezpieczniejsza zvar
istniejących, ponieważ nie wymyka się metodzie. Jeśli metoda jest krótka, jeszcze lepsza. Jeśli tak nie jest, spróbuj go zmniejszyć, wyodrębniając inne metody.Z drugiej strony zmienna kolekcja ma potencjał do ucieczki, nawet jeśli tak się nie stanie. Zmieniając kod, możesz chcieć przekazać go innym metodom lub zwrócić. To jest rodzaj rzeczy, która łamie referencyjną przejrzystość.
Na obiekcie (polu) dzieje się prawie to samo, ale z bardziej tragicznymi konsekwencjami. Tak czy inaczej obiekt będzie miał stan, a zatem złamie przezroczystość referencyjną. Ale posiadanie zmiennej kolekcji oznacza, że nawet sam obiekt może stracić kontrolę nad tym, kto go zmienia.
źródło
immutable val
ponadimmutable var
ponadmutable val
ponadmutable var
. Zwłaszczaimmutable var
koniecmutable val
!var
. Inną fajną cechą korzystania z niezmiennych kolekcji jest to, że możesz skutecznie przechowywać stare kopie, nawet jeśli sąvar
mutacje.var x: Set[Int]
over,val x: mutable.Set[Int]
ponieważ jeśli przejdzieszx
do jakiejś innej funkcji, w pierwszym przypadku jesteś pewien, że ta funkcja nie może zostaćx
za Ciebie zmieniona .Jeśli pracujesz z niezmiennymi kolekcjami i musisz je „zmodyfikować”, na przykład dodać do nich elementy w pętli, musisz użyć
var
s, ponieważ musisz gdzieś przechowywać wynikową kolekcję. Jeśli czytasz tylko z niezmiennych kolekcji, użyjval
s.Ogólnie upewnij się, że nie pomylisz odniesień i obiektów.
val
s są niezmiennymi referencjami (stałe wskaźniki w C). Oznacza to, że kiedy używaszval x = new MutableFoo()
, będziesz mógł zmienić obiekt, któryx
wskazuje, ale nie będziesz w stanie zmienić tego, który obiektx
wskazuje. Odwrotnie jest, jeśli używaszvar x = new ImmutableFoo()
. Podchodzę do mojej wstępnej rady: jeśli nie musisz zmieniać obiektu, do którego odnosi się punkt odniesienia, użyjval
s.źródło
var immutable = something(); immutable = immutable.update(x)
pokonuje cel używania niezmiennej kolekcji. Zrezygnowałeś już z referencyjnej przezroczystości i zwykle możesz uzyskać ten sam efekt ze zmiennej kolekcji o większej złożoności czasowej. Z czterech możliwości (val
ivar
, zmienna i niezmienna), ta ma najmniej sensu. Często używamval mutable
.var list: List[X] = Nil; list = item :: list; ...
i zapomniałem, że kiedyś pisałem inaczej.Najlepszym sposobem odpowiedzi na to jest przykład. Załóżmy, że z jakiegoś powodu mamy jakiś proces po prostu zbierający liczby. Chcemy zarejestrować te numery i wyślemy kolekcję do innego procesu, aby to zrobić.
Oczywiście po wysłaniu kolekcji do rejestratora nadal zbieramy numery. Załóżmy, że proces rejestrowania jest obciążony, co opóźnia faktyczne rejestrowanie. Mam nadzieję, że widzisz, dokąd to zmierza.
Jeśli przechowujemy tę kolekcję jako zmienną
val
(zmienną, ponieważ stale do niej dodajemy), oznacza to, że proces rejestrujący będzie szukał tego samego obiektu, który wciąż jest aktualizowany przez nasz proces zbierania. Ta kolekcja może zostać zaktualizowana w dowolnym momencie, więc kiedy nadejdzie czas na rejestrację, możemy w rzeczywistości nie rejestrować kolekcji, którą wysłaliśmy.Jeśli używamy niezmiennej
var
, wysyłamy niezmienną strukturę danych do rejestratora. Kiedy dodać więcej numerów do naszej kolekcji, będziemy zastępując Ourvar
z nowym niezmiennej struktury danych . Nie oznacza to, że zbiór przesłany do rejestratora zostanie zastąpiony! Nadal odwołuje się do kolekcji, którą wysłał. Zatem nasz logger rzeczywiście zarejestruje otrzymaną kolekcję.źródło
Myślę, że przykłady w tym wpisie na blogu rzucą więcej światła, ponieważ kwestia, którego kombinacji użyć, staje się jeszcze ważniejsza w scenariuszach współbieżności: znaczenie niezmienności dla współbieżności . A skoro już o tym mowa, zwróć uwagę na preferowane użycie synchronized vs @volatile vs czegoś takiego jak AtomicReference: trzy narzędzia
źródło
var immutable
vs.val mutable
Oprócz wielu doskonałych odpowiedzi na to pytanie. Oto prosty przykład, który ilustruje potencjalne zagrożenia
val mutable
:Mutowalne obiekty można modyfikować wewnątrz metod, które przyjmują je jako parametry, podczas gdy ponowne przypisywanie jest niedozwolone.
Wynik:
Coś takiego nie może się zdarzyć
var immutable
, ponieważ zmiana przypisania jest niedozwolona:Prowadzi do:
Ponieważ parametry funkcji są traktowane jako
val
takie ponowne przypisanie, nie jest dozwolone.źródło
mutable val
nie jest możliwe w przypadkuimmutable var
. Co tutaj jest nieprawidłowe?+=
metody takiej jak bufor tablicy. Twoje odpowiedzi sugerują, że+=
jest to to samo,x = x + y
co nie. Twoje stwierdzenie, że parametry funkcji są traktowane jako wartości vals, jest poprawne i pojawia się błąd, o którym wspomniałeś, ale tylko dlatego, że użyłeś=
. Ten sam błąd można uzyskać w przypadku obiektu ArrayBuffer, więc zmienność kolekcji tutaj nie jest tak naprawdę istotna. Więc to nie jest dobra odpowiedź, ponieważ nie dociera do tego, o czym mówi OP. Chociaż jest to dobry przykład niebezpieczeństw związanych z przekazywaniem zmiennej kolekcji, jeśli nie zamierzałeś tego robić.ArrayBuffer
, używającVector
. Pytanie PO jest szerokie, ale szukali sugestii, kiedy użyć którego, więc uważam, że moja odpowiedź jest przydatna, ponieważ ilustruje niebezpieczeństwa związane z przekazywaniem mutowalnej kolekcji (fakt, że toval
nie pomaga);immutable var
jest bezpieczniejszy niżmutable val
.