val-мутаційний проти var-незмінний у Scala


97

Чи є в Scala якісь вказівки щодо того, коли використовувати val із колекцією, що змінюється, порівняно з var із незмінною колекцією? Або вам справді слід прагнути валь з незмінною колекцією?

Той факт, що є обидва типи колекції, дає мені багато вибору, і часто я не знаю, як зробити цей вибір.


Дивіться, наприклад , stackoverflow.com/questions/10999024 / ...
Луїджі Плюм

Відповіді:


104

Досить поширене питання, це. Найважче - знайти дублікати.

Вам слід прагнути до прозорості . Що це означає, що, якщо у мене є вираз «е», я міг би зробити val x = e, і замінити eз x. Це властивість, яка порушує незмінність. Щоразу, коли вам потрібно прийняти дизайнерське рішення, максимізуйте для прозорості референс.

Щодо практичного питання, метод локальних varє найбезпечнішим із існуючих var, оскільки він не уникає методу. Якщо метод короткий, ще краще. Якщо це не так, спробуйте зменшити його, витягнувши інші методи.

З іншого боку, колекція , що змінюється, має можливість вирватися, навіть якщо її немає. Змінюючи код, ви можете потім передати його іншим методам або повернути його. Це та річ, яка порушує прозору прозорість.

З об’єктом (полем) відбувається майже все те саме, але з більш тяжкими наслідками. У будь-якому випадку об'єкт матиме стан і, отже, порушує прозорість. Але мати колекцію, що змінюється, означає, що навіть сам об'єкт може втратити контроль над тим, хто його змінює.


38
Хороший, нова велика картина в моїй голові: Віддає перевагу immutable valбільш immutable varнад mutable valбільш mutable var. Особливо immutable varзакінчено mutable val!
Петер Шмітц,

2
Майте на увазі, що ви все ще можете закрити (як у витоку побічної "функції", яка може її змінити) над локальним змінним var. Ще одна приємна особливість використання незмінних колекцій - це те, що ви можете ефективно зберігати старі копії навколо, навіть якщо varмутації.
Таємничий Ден

1
ТЛ; ін: віддаю перевагу var x: Set[Int] більш val x: mutable.Set[Int] , так як якщо ви передасте xв яку - то іншу функцію, в першому випадку, ви впевнені, що функція не може мутувати xдля вас.
pathikrit

17

Якщо ви працюєте з незмінними колекціями і вам потрібно "модифікувати" їх, наприклад, додавати до них елементи в циклі, тоді вам доведеться використовувати vars, оскільки вам потрібно десь зберігати отриману колекцію. Якщо ви читаєте лише з незмінних колекцій, тоді використовуйте vals.

Загалом, переконайтеся, що ви не плутаєте посилання та об’єкти. vals - незмінні посилання (постійні покажчики в С). Тобто, коли ви користуєтесь val x = new MutableFoo(), ви зможете змінити об’єкт, на який xвказує, але ви не зможете змінити, на які об’єктні x точки. Якщо ви використовуєте, справедливе протилежне var x = new ImmutableFoo(). Отримавши мою первинну пораду: якщо вам не потрібно змінювати, на який об’єкт посилаються точки, використовуйте vals.


1
var immutable = something(); immutable = immutable.update(x)перемагає мету використання незмінної колекції. Ви вже відмовилися від еталонної прозорості і зазвичай можете отримати той же ефект від колекції, що змінюється, з кращою складністю часу. З чотирьох можливостей ( valі varнезмінних і незмінних) ця має найменший сенс. Я часто користуюся val mutable.
Джим Піварський

3
@JimPivarski Я не згоден, як і інші, дивіться відповідь Даниїла та коментар Петра. Якщо вам потрібно оновити структуру даних, то використання непорушного var замість змінного val має перевагу в тому, що ви можете просочувати посилання на структуру, не ризикуючи, що її модифікують інші таким чином, що порушує ваші локальні припущення. Недоліком цих "інших" є те, що вони можуть читати несвіжі дані.
Мальте Шверхофф

Я передумав і погоджуюся з вами (залишаю свій оригінальний коментар для історії). З тих пір я це використовую, особливо, var list: List[X] = Nil; list = item :: list; ...і я забув, що колись писав інакше.
Джим Піварський

@MalteSchwerhoff: "застарілі дані" насправді бажані, залежно від того, як ви створили свою програму, якщо послідовність має вирішальне значення; це, наприклад, один з основних принципів роботи паралельності в Clojure.
Ерік Каплун

@ErikAllik Я б не сказав, що банальні дані бажані самі по собі, але я згоден у тому, що це може бути ідеально добре, залежно від гарантій, які ви хочете / потрібно дати своїм клієнтам. Або у вас є приклад, коли єдиний факт читання застарілих даних насправді є перевагою? Я не маю на увазі наслідки прийняття несвіжих даних, які можуть мати кращу ефективність або простіший API.
Мальте Швергофф,

9

Найкращий спосіб відповісти на це прикладом. Припустимо, у нас є якийсь процес просто збирання номерів з якоїсь причини. Ми хочемо зареєструвати ці номери і надішлемо колекцію в інший процес для цього.

Звичайно, ми все ще збираємо номери після того, як відправимо колекцію до лісоруба. Скажімо, є деякий накладний процес у процесі реєстрації, який затримує фактичну реєстрацію. Сподіваємось, ви зможете побачити, куди це йде.

Якщо ми зберігаємо цю колекцію в мутаційному вигляді val(мутаційному, оскільки ми постійно додаємо до неї), це означає, що процес, що веде журнал, буде дивитись на той самий об’єкт, який все ще оновлюється нашим процесом колекції. Ця колекція може бути оновлена ​​в будь-який час, і тому, коли настає час для входу, ми можемо насправді не реєструвати надіслану нами колекцію.

Якщо ми використовуємо незмінний var, ми надсилаємо незмінна структура даних в реєстратор. Коли ми додаємо більше чисел в нашу колекцію, ми будемо замінювати OUR varз новими непорушними структурами даних . Це не означає, що колекція, що надсилається до реєстратора, замінена! Він досі посилається на колекцію, яку він надіслав. Тож наш реєстратор дійсно запише отриману колекцію.


1

Я думаю, що приклади в цій публікації в блозі пролиють більше світла, оскільки питання, яке комбо використовувати, стає ще важливішим у сценаріях паралельності: важливість незмінності для одночасності . І хоча ми до цього звертаємо увагу на переважне використання синхронізованого vs @volatile vs щось на зразок AtomicReference: три інструменти


-2

var immutable vs. val mutable

Окрім багатьох відмінних відповідей на це питання. Ось простий приклад, який ілюструє потенційні небезпеки val mutable:

Змінні об'єкти можуть бути змінені всередині методів, які приймають їх за параметри, тоді як перепризначення заборонено.

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

Результат:

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

Щось подібного не може статися з цим var immutable, оскільки перепризначення заборонено:

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

Призводить до:

error: reassignment to val

Оскільки параметри функції розглядаються як valтаке перепризначення, не дозволяється.


Це неправильно. Причиною цієї помилки є те, що ви використовували вектор у своєму другому прикладі, який за замовчуванням незмінний. Якщо ви використовуєте ArrayBuffer, ви побачите, що він складається добре, і він робить те саме, що додає в новий елемент і виводить мутований буфер. pastebin.com/vfq7ytaD
EdgeCaseBerg

@EdgeCaseBerg, я навмисно використовую вектор у своєму другому прикладі, тому що я намагаюся показати, що поведінка першого прикладу mutable valнеможлива з immutable var. Що тут невірно?
Акавал

Ви порівнюєте яблука з апельсинами тут. Вектор не має +=методу, як буфер масиву. З ваших відповідей випливає, що +=це те саме, x = x + yщо і ні. Твоє твердження про те, що параметри функції трактуються як vals, є правильним, і ти отримуєш помилку, яку ти згадуєш, але лише тому, що ти її використовував =. Ви можете отримати таку саму помилку з ArrayBuffer, так що змінність колекцій тут не дуже актуальна. Отже, це не є гарною відповіддю, оскільки не отримує того, про що йдеться в ОП. Хоча це хороший приклад небезпеки передачі змінної колекції, якщо ви цього не мали наміру.
EdgeCaseBerg

@EdgeCaseBerg Але ви не можете повторити поведінку, яку я отримую ArrayBuffer, використовуючи Vector. Питання ОП широке, але вони шукали пропозиції, коли їх використовувати, тому я вважаю, що моя відповідь є корисною, оскільки вона ілюструє небезпеку проходження колекції, що valзмінюється (що не допомагає); immutable varбезпечніше, ніж mutable val.
Акавал
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.