Apa perbedaan antara definisi var dan val di Scala?

301

Apa perbedaan antara a vardan valdefinisi dalam Scala dan mengapa bahasa membutuhkan keduanya? Mengapa Anda memilih vallebih dari satu vardan sebaliknya?

Derek Mahar
sumber

Jawaban:

331

Seperti yang dikatakan oleh banyak orang lain, objek yang ditugaskan untuk valtidak dapat diganti, dan objek yang ditugaskan ke varkaleng. Namun, objek tersebut dapat memiliki keadaan internal yang dimodifikasi. Sebagai contoh:

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

Jadi, meskipun kita tidak bisa mengubah objek yang ditugaskan x, kita bisa mengubah keadaan objek itu. Pada akar itu, bagaimanapun, ada a var.

Sekarang, kekekalan adalah hal yang baik karena berbagai alasan. Pertama, jika suatu objek tidak mengubah keadaan internal, Anda tidak perlu khawatir jika ada bagian lain dari kode Anda yang mengubahnya. Sebagai contoh:

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

Ini menjadi sangat penting dengan sistem multithreaded. Dalam sistem multithreaded, hal berikut dapat terjadi:

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

Jika Anda menggunakan valsecara eksklusif, dan hanya menggunakan struktur data yang tidak dapat diubah (yaitu, hindari array, semuanya masuk scala.collection.mutable, dll.), Anda dapat yakin bahwa ini tidak akan terjadi. Yaitu, kecuali ada beberapa kode, mungkin bahkan kerangka kerja, melakukan trik refleksi - sayangnya, refleksi dapat mengubah nilai-nilai yang "tidak berubah".

Itu satu alasan, tetapi ada alasan lain untuk itu. Saat Anda menggunakan var, Anda dapat tergoda untuk menggunakan kembali yang sama varuntuk berbagai tujuan. Ini memiliki beberapa masalah:

  • Akan lebih sulit bagi orang yang membaca kode untuk mengetahui berapa nilai variabel dalam bagian tertentu dari kode.
  • Anda mungkin lupa untuk menginisialisasi ulang variabel di beberapa jalur kode, dan akhirnya melewatkan nilai-nilai yang salah di kode.

Sederhananya, menggunakan vallebih aman dan mengarah ke kode yang lebih mudah dibaca.

Maka, kita bisa pergi ke arah lain. Jika valitu lebih baik, mengapa harus varsama sekali? Ya, beberapa bahasa memang mengambil rute itu, tetapi ada banyak situasi di mana sifat berubah-ubah meningkatkan kinerja.

Sebagai contoh, ambil yang abadi Queue. Ketika Anda baik enqueueatau dequeuesesuatu di dalamnya, Anda mendapatkan Queueobjek baru . Lalu bagaimana, apakah Anda akan memproses semua item di dalamnya?

Saya akan membahasnya dengan sebuah contoh. Katakanlah Anda memiliki antrian angka, dan Anda ingin membuat angka dari mereka. Misalnya, jika saya memiliki antrian dengan 2, 1, 3, dalam urutan itu, saya ingin mendapatkan kembali nomor 213. Mari kita selesaikan dulu dengan mutable.Queue:

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

Kode ini cepat dan mudah dimengerti. Kelemahan utamanya adalah bahwa antrian yang dilewati dimodifikasi oleh toNum, jadi Anda harus membuat salinannya terlebih dahulu. Itulah jenis manajemen objek yang membuat Anda bebas dari kekekalan.

Sekarang, mari kita tutupi dengan 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)
}

Karena saya tidak dapat menggunakan kembali beberapa variabel untuk melacak saya num, seperti pada contoh sebelumnya, saya perlu menggunakan rekursi. Dalam hal ini, ini adalah rekursi ekor, yang memiliki kinerja yang cukup bagus. Tapi itu tidak selalu terjadi: kadang-kadang tidak ada solusi rekursi ekor yang baik (mudah dibaca, sederhana).

Namun, perhatikan bahwa saya dapat menulis ulang kode itu untuk menggunakan immutable.Queuedan varsekaligus! Sebagai contoh:

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
}

Kode ini masih efisien, tidak memerlukan rekursi, dan Anda tidak perlu khawatir apakah Anda harus membuat salinan antrian Anda atau tidak sebelum menelepon toNum. Secara alami, saya menghindari menggunakan kembali variabel untuk tujuan lain, dan tidak ada kode di luar fungsi ini yang melihatnya, jadi saya tidak perlu khawatir tentang nilai-nilai mereka berubah dari satu baris ke yang berikutnya - kecuali ketika saya secara eksplisit melakukannya.

Scala memilih untuk membiarkan programmer melakukan itu, jika programmer menganggapnya sebagai solusi terbaik. Bahasa lain telah memilih untuk membuat kode seperti itu sulit. Harga yang dibayar Scala (dan bahasa apa pun dengan mutabilitas luas) adalah bahwa kompiler tidak memiliki banyak kelonggaran dalam mengoptimalkan kode sebagaimana seharusnya. Jawaban Java untuk itu adalah mengoptimalkan kode berdasarkan pada profil run-time. Kita bisa terus berbicara tentang pro dan kontra untuk masing-masing pihak.

Secara pribadi, saya pikir Scala menemukan keseimbangan yang tepat, untuk saat ini. Sejauh ini tidak sempurna. Saya pikir Clojure dan Haskell memiliki gagasan yang sangat menarik yang tidak diadopsi oleh Scala, tetapi Scala memiliki kekuatannya sendiri juga. Kita akan melihat apa yang muncul di masa depan.

Daniel C. Sobral
sumber
Sedikit terlambat, tapi ... Apakah var qr = qmembuat salinannya q?
5
@davips Itu tidak membuat salinan dari objek yang dirujuk oleh q. Itu memang membuat salinan - di stack, bukan heap - dari referensi ke objek itu. Adapun kinerja, Anda harus lebih jelas tentang apa "itu" yang Anda bicarakan.
Daniel C. Sobral
Ok, dengan bantuan Anda dan beberapa info ( (x::xs).drop(1)tepatnya xs, bukan "salinan" dari xs) tautan di sini yang dapat saya mengerti. tnx!
"Kode ini masih efisien" - kan? Karena qrmerupakan antrian yang tidak dapat diubah, setiap kali ekspresi qr.dequeuedipanggil, ia membuat new Queue(lihat < github.com/scala/scala/blob/2.13.x/src/library/scala/collection/… ).
Owen
@Wen Ya, tetapi perhatikan bahwa itu adalah objek yang dangkal. Kode masih O (n) apakah bisa berubah, jika Anda menyalin antrian, atau tidak berubah.
Daniel C. Sobral
61

valbersifat final, yaitu, tidak dapat diatur. Pikirkan finaldalam java.

Jackson Davis
sumber
3
Tetapi jika saya mengerti dengan benar (bukan ahli Scala), val variabel tidak berubah, tetapi objek yang dirujuk tidak harus. Menurut tautan yang diposting Stefan: "Di sini referensi nama tidak dapat diubah untuk mengarah ke Array yang berbeda, tetapi array itu sendiri dapat dimodifikasi. Dengan kata lain isi / elemen array dapat dimodifikasi." Jadi seperti bagaimana cara finalkerjanya di Jawa.
Daniel Pryden
3
Persis mengapa saya mempostingnya apa adanya. Aku bisa menelepon +=pada hashmap mutabled didefinisikan sebagai valbaik-baik saja-saya percaya persis bagaimana finalbekerja di java
Jackson Davis
Ack, saya pikir tipe scala bawaan bisa melakukan lebih baik daripada sekadar mengizinkan penugasan ulang. Saya perlu memeriksa fakta.
Stefan Kendall
Saya bingung jenis Sequence abadi scala dengan gagasan umum. Pemrograman fungsional membuat saya semua berbalik.
Stefan Kendall
Saya menambahkan dan menghapus karakter dummy dalam jawaban Anda sehingga saya dapat memberi Anda upvote.
Stefan Kendall
56

Secara sederhana:

var = var iable

val = v ariable + fin al

Ajay Gupta
sumber
20

Perbedaannya adalah bahwa a vardapat ditugaskan kembali sedangkan yang valtidak bisa. Mutabilitas, atau selain dari apa pun yang sebenarnya ditugaskan, adalah masalah sampingan:

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.

Sedangkan:

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

Dan karenanya:

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

Jika Anda sedang membangun struktur data dan semua bidangnya adalah vals, maka struktur data itu tidak dapat diubah, karena statusnya tidak dapat berubah.

oxbow_lakes
sumber
1
Itu hanya benar jika kelas-kelas dari bidang-bidang itu juga tidak berubah.
Daniel C. Sobral
Ya - saya akan memasukkan itu tetapi saya pikir itu mungkin langkah terlalu jauh! Saya juga akan mengatakan hal yang bisa diperdebatkan; dari satu perspektif (meskipun bukan yang fungsional) negaranya tidak berubah bahkan jika keadaan negaranya berubah.
oxbow_lakes
Mengapa masih sangat sulit untuk membuat objek yang tidak bisa diubah dalam bahasa JVM? Selain itu, mengapa Scala tidak membuat objek berubah secara default?
Derek Mahar
19

valberarti kekal dan varbisa berubah.

Diskusi penuh.

Stefan Kendall
sumber
1
Ini tidak benar. Artikel yang ditautkan memberikan array yang bisa berubah dan menyebutnya tidak bisa diubah. Tidak ada sumber yang serius.
Klaus Schulz
Memang tidak benar. Coba itu val b = Array [Int] (1,2,3) b (0) = 4 println (b.mkString ("")) println ("")
Guillaume
13

Berpikir dalam hal C ++,

val x: T

analog dengan pointer konstan ke data tidak konstan

T* const x;

sementara

var x: T 

analog dengan penunjuk tidak konstan ke data tidak konstan

T* x;

Mendukung vallebih varmeningkatkan immutabilitas basis kode yang dapat memfasilitasi kebenaran, konkurensi, dan pemahaman.

Untuk memahami arti memiliki pointer konstan ke data non-konstan pertimbangkan cuplikan Scala berikut:

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

Di sini "pointer" val m konstan sehingga kita tidak dapat menetapkan ulang untuk menunjuk ke hal lain seperti itu

m = n // error: reassignment to val

namun kita memang dapat mengubah data yang tidak konstan itu sendiri yang mmenunjuk seperti itu

m.put(2, "worf")
m // res1: scala.collection.mutable.Map[Int,String] = HashMap(1 -> picard, 2 -> worf)
Mario Galic
sumber
1
Saya pikir Scala tidak mengambil kekekalan ke kesimpulan akhir: pointer konstan dan data konstan Scala melewatkan kesempatan untuk membuat objek tidak berubah secara default. Akibatnya, Scala tidak memiliki gagasan nilai yang sama dengan Haskell.
Derek Mahar
@DerekMahar Anda benar, tetapi suatu objek dapat menampilkan dirinya sebagai sangat sempurna, sementara masih menggunakan kemampuan berubah dalam implementasinya, misalnya untuk alasan kinerja. Bagaimana kompiler dapat memisahkan antara mutabilitas sejati dan hanya mutabilitas internal?
francoisr
8

"val berarti tidak berubah dan var berarti bisa berubah."

Mengutip, "val berarti nilai dan var berarti variabel".

Perbedaan yang terjadi menjadi sangat penting dalam komputasi (karena kedua konsep tersebut mendefinisikan esensi dari semua pemrograman itu), dan bahwa OO telah berhasil mengaburkan hampir sepenuhnya, karena dalam OO, satu-satunya aksioma adalah bahwa "semuanya adalah obyek". Dan sebagai konsekuensinya, banyak programmer saat ini cenderung tidak memahami / menghargai / mengenali, karena mereka telah dicuci otak untuk "berpikir dengan cara OO" secara eksklusif. Seringkali mengarah ke objek variabel / bisa berubah digunakan seperti di mana - mana , ketika nilai / objek berubah mungkin / akan sering lebih baik.

Erwin Smout
sumber
Inilah alasan mengapa saya lebih suka Haskell daripada Java, misalnya.
Derek Mahar
6

val berarti tidak berubah dan var berarti bisa berubah

Anda dapat berpikir valsebagai finaldunia kunci bahasa pemrograman java atau c ++ bahasa constkunci dunia。

Rollen Holt
sumber
1

Valberarti final , tidak dapat dipindahkan

Padahal, Varbisa dipindahkan nanti .

Crime_Master_GoGo
sumber
1
Bagaimana jawaban ini berbeda dari 12 jawaban yang sudah dikirimkan?
jwvh
0

Ini sesederhana namanya.

var berarti dapat bervariasi

val berarti tidak berubah

pengguna105003
sumber
0

Nilai - nilai diketikkan sebagai konstanta penyimpanan. Setelah dibuat nilainya tidak dapat ditugaskan kembali. nilai baru dapat didefinisikan dengan kata kunci val.

misalnya. val x: Int = 5

Di sini tipe adalah opsional karena scala dapat mengambilnya dari nilai yang diberikan.

Var - variabel adalah unit penyimpanan yang diketik yang dapat diberi nilai lagi selama ruang memori dicadangkan.

misalnya. var x: Int = 5

Data yang disimpan di kedua unit penyimpanan secara otomatis dialokasikan oleh JVM setelah ini tidak lagi diperlukan.

Dalam nilai scala lebih disukai daripada variabel karena stabilitas ini membawa ke kode terutama dalam kode bersamaan dan multithreaded.

SunTech
sumber
0

Padahal banyak yang sudah menjawab perbedaan antara Val dan var . Tetapi satu hal yang perlu diperhatikan adalah bahwa val tidak persis seperti kata kunci akhir .

Kita dapat mengubah nilai val menggunakan rekursi tetapi kita tidak pernah bisa mengubah nilai akhir. Final lebih konstan daripada Val.

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

Parameter metode secara default val dan pada setiap nilai panggilan sedang diubah.

Mahesh Chand
sumber