val-mutable versus var-immutable di Scala

97

Apakah ada pedoman di Scala tentang kapan menggunakan val dengan koleksi yang bisa diubah versus menggunakan var dengan koleksi yang tidak bisa diubah? Atau haruskah Anda benar-benar mengincar val dengan koleksi yang tidak dapat diubah?

Fakta bahwa ada kedua jenis koleksi itu memberi saya banyak pilihan, dan seringkali saya tidak tahu bagaimana cara menentukan pilihan itu.

James McCabe
sumber
Lihat misalnya stackoverflow.com/questions/10999024/…
Luigi Plinge

Jawaban:

104

Pertanyaan yang cukup umum, yang ini. Hal yang sulit adalah menemukan duplikatnya.

Anda harus berusaha untuk transparansi referensial . Artinya, jika saya memiliki ekspresi "e", saya bisa membuat val x = e, dan menggantinya edengan x. Ini adalah properti yang dapat dirusak oleh mutabilitas. Kapan pun Anda perlu membuat keputusan desain, maksimalkan untuk transparansi referensial.

Secara praktis, metode-lokal varadalah yang paling aman varyang ada, karena tidak luput dari metode tersebut. Jika metodenya pendek, bahkan lebih baik. Jika tidak, coba kurangi dengan mengekstrak metode lain.

Di sisi lain, koleksi yang bisa berubah berpotensi untuk melarikan diri, meskipun tidak. Saat mengubah kode, Anda mungkin ingin meneruskannya ke metode lain, atau mengembalikannya. Itu adalah jenis hal yang merusak transparansi referensial.

Pada suatu objek (bidang), hal yang hampir sama terjadi, tetapi dengan konsekuensi yang lebih mengerikan. Bagaimanapun, objek akan memiliki status dan, oleh karena itu, merusak transparansi referensial. Tetapi memiliki koleksi yang bisa berubah berarti bahkan objek itu sendiri mungkin kehilangan kendali atas siapa yang mengubahnya.

Daniel C. Sobral
sumber
38
Bagus, gambaran besar dalam benak saya: Memilih untuk immutable vallebih immutable varlebih mutable vallebih mutable var. Terutama immutable varselesai mutable val!
Peter Schmitz
2
Ingatlah bahwa Anda masih bisa menutup (seperti dalam kebocoran "fungsi" efek samping yang dapat mengubahnya) melalui lokal yang bisa berubah var. Fitur bagus lainnya dalam menggunakan koleksi yang tidak dapat diubah adalah Anda dapat menyimpan salinan lama dengan efisien, meskipun varbermutasi.
Misterius Dan
1
tl; dr: lebih var x: Set[Int] lebih val x: mutable.Set[Int] karena jika Anda melewatkan xbeberapa fungsi lain, dalam kasus mantan, Anda yakin, fungsi yang tidak bisa bermutasi xuntuk Anda.
pathikrit
17

Jika Anda bekerja dengan koleksi yang tidak dapat diubah dan Anda perlu "memodifikasinya", misalnya, menambahkan elemen ke dalamnya dalam satu putaran, Anda harus menggunakan vars karena Anda perlu menyimpan koleksi yang dihasilkan di suatu tempat. Jika Anda hanya membaca dari koleksi yang tidak dapat diubah, gunakan vals.

Secara umum, pastikan Anda tidak membingungkan referensi dan objek. vals adalah referensi tetap (penunjuk konstan di C). Artinya, saat Anda menggunakan val x = new MutableFoo(), Anda akan dapat mengubah objek yang xdituju, tetapi Anda tidak akan dapat mengubah ke objek mana x . Kebalikannya berlaku jika Anda menggunakan var x = new ImmutableFoo(). Mengambil saran awal saya: jika Anda tidak perlu mengubah ke objek mana yang menjadi titik referensi, gunakan vals.

Malte Schwerhoff
sumber
1
var immutable = something(); immutable = immutable.update(x)mengalahkan tujuan menggunakan koleksi yang tidak dapat diubah. Anda sudah melepaskan transparansi referensial dan biasanya Anda bisa mendapatkan efek yang sama dari koleksi yang bisa berubah dengan kerumitan waktu yang lebih baik. Dari empat kemungkinan ( valdan var, bisa berubah dan tidak bisa diubah), yang satu ini paling tidak masuk akal. Saya sering menggunakan val mutable.
Jim Pivarski
3
@JimPivarski Saya tidak setuju, seperti yang lain, lihat jawaban Daniel dan komentar Peter. Jika Anda perlu memperbarui struktur data, maka menggunakan var yang tidak dapat diubah alih-alih val yang dapat berubah memiliki keuntungan bahwa Anda dapat membocorkan referensi ke struktur tanpa risiko diubah oleh orang lain dengan cara yang merusak asumsi lokal Anda. Kerugian bagi "orang lain" ini adalah, mereka mungkin membaca data usang.
Malte Schwerhoff
Saya telah berubah pikiran dan saya setuju dengan Anda (saya meninggalkan komentar asli saya untuk sejarah). Sejak itu saya menggunakan ini, terutama di var list: List[X] = Nil; list = item :: list; ...dan saya lupa bahwa saya pernah menulis secara berbeda.
Jim Pivarski
@MalteSchwerhoff: "data lama" sebenarnya diinginkan, tergantung pada bagaimana Anda merancang program Anda, jika konsistensi itu penting; ini misalnya salah satu prinsip dasar utama tentang cara kerja konkurensi di Clojure.
Erik Kaplun
@ErikAllik Saya tidak akan mengatakan bahwa data yang lama memang diinginkan, tetapi saya setuju bahwa data itu bisa sangat baik, tergantung pada jaminan yang ingin / perlu berikan kepada klien Anda. Atau apakah Anda memiliki contoh di mana satu-satunya fakta membaca data lama sebenarnya merupakan keuntungan? Maksud saya bukan konsekuensi dari menerima data usang, yang bisa berupa kinerja yang lebih baik atau API yang lebih sederhana.
Malte Schwerhoff
9

Cara terbaik untuk menjawabnya adalah dengan sebuah contoh. Misalkan kita memiliki beberapa proses yang hanya mengumpulkan angka karena suatu alasan. Kami ingin mencatat nomor ini, dan akan mengirim koleksi ke proses lain untuk melakukannya.

Tentu saja, kami masih mengumpulkan nomor setelah kami mengirimkan koleksi tersebut ke logger. Dan katakanlah ada beberapa overhead dalam proses logging yang menunda logging sebenarnya. Semoga Anda bisa melihat ke mana arahnya.

Jika kita menyimpan koleksi ini dalam bentuk yang bisa berubah val, (bisa berubah karena kita terus menambahkannya), ini berarti bahwa proses yang melakukan pencatatan akan melihat objek yang sama yang masih diperbarui oleh proses pengumpulan kita. Koleksi itu dapat diperbarui kapan saja, jadi ketika waktunya untuk masuk, kami mungkin tidak benar-benar mencatat koleksi yang kami kirim.

Jika kami menggunakan yang tidak dapat diubah var, kami mengirimkan struktur data yang tidak dapat diubah ke pencatat. Ketika kita menambahkan angka lebih untuk koleksi kami, kami akan mengganti kita vardengan struktur data berubah baru . Ini tidak berarti koleksi yang dikirim ke logger diganti! Itu masih merujuk pada koleksi yang dikirimnya. Jadi logger kami memang akan mencatat koleksi yang diterimanya.

jmazin.dll
sumber
1

Saya pikir contoh dalam posting blog ini akan menjelaskan lebih banyak, karena pertanyaan tentang kombo mana yang akan digunakan menjadi lebih penting dalam skenario konkurensi: pentingnya kekekalan untuk konkurensi . Dan sementara kita melakukannya, perhatikan penggunaan yang lebih disukai dari sinkronisasi vs @volatile vs sesuatu seperti AtomicReference: tiga alat

Bogdan Nicolau
sumber
-2

var immutable vs. val mutable

Selain banyak jawaban bagus untuk pertanyaan ini. Berikut adalah contoh sederhana, yang menggambarkan potensi bahaya val mutable:

Objek yang dapat berubah dapat dimodifikasi di dalam metode, yang menjadikannya sebagai parameter, sementara penugasan ulang tidak diperbolehkan.

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

Hasil:

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

Sesuatu seperti ini tidak dapat terjadi dengan var immutable, karena penugasan ulang tidak diperbolehkan:

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

Hasil dalam:

error: reassignment to val

Karena parameter fungsi diperlakukan seperti valpenugasan ulang ini tidak diperbolehkan.

Akavall
sumber
Ini salah Alasan Anda mendapatkan kesalahan itu adalah karena Anda menggunakan Vector dalam contoh kedua yang, secara default tidak dapat diubah. Jika Anda menggunakan ArrayBuffer, Anda akan melihatnya dikompilasi dengan baik dan melakukan hal yang sama di mana ia hanya menambahkan elemen baru dan mencetak buffer yang dimutasi. pastebin.com/vfq7ytaD
EdgeCaseBerg
@EdgeCaseBerg, Saya sengaja menggunakan Vektor dalam contoh kedua saya, karena saya mencoba menunjukkan bahwa perilaku contoh pertama mutable valtidak dimungkinkan dengan immutable var. Apa di sini yang salah?
Akavall
Anda membandingkan apel dengan jeruk di sini. Vektor tidak memiliki +=metode seperti buffer array. Jawaban Anda menyiratkan bahwa +=itu sama dengan x = x + yyang sebenarnya tidak. Pernyataan Anda bahwa parameter fungsi diperlakukan sebagai vals sudah benar dan Anda mendapatkan kesalahan yang Anda sebutkan tetapi hanya karena Anda menggunakannya =. Anda bisa mendapatkan kesalahan yang sama dengan ArrayBuffer sehingga mutabilitas koleksi di sini tidak terlalu relevan. Jadi ini bukan jawaban yang bagus karena tidak mengerti apa yang dibicarakan OP. Meskipun ini adalah contoh yang baik tentang bahaya melewatkan koleksi yang bisa berubah jika Anda tidak berniat melakukannya.
EdgeCaseBerg
@EdgeCaseBerg Tapi Anda tidak bisa meniru perilaku saya ArrayBuffer, dengan menggunakan Vector. Pertanyaan OP luas, tetapi mereka sedang mencari saran kapan harus menggunakan yang mana, jadi saya yakin jawaban saya berguna karena menggambarkan bahaya menyebarkan koleksi yang bisa berubah (fakta itu valtidak membantu); immutable varlebih aman dari mutable val.
Akavall