val dan var

Pengikatan immutable sebagai pilihan utama dan mutasi sebagai keputusan yang disengaja.

Daftar isi

Tiga kata yang sangat sering muncul pada kode Scala adalah val, var, dan def. Topik ini membahas dua yang pertama, yaitu val untuk pengikatan nilai yang tidak berubah dan var untuk nilai yang boleh diisi ulang. Hasil yang diperoleh setelah membacanya adalah kebiasaan mengikat data dengan val secara bawaan, keputusan yang disengaja ketika var benar-benar diperlukan, serta kemampuan membaca pesan error dari compiler ketika aturan pengikatan dilanggar.

val: pengikatan yang tidak berubah#

Kata kunci val mengikat sebuah nilai ke satu nama secara permanen, sehingga nama tersebut tidak dapat diikat ulang ke nilai lain. Sifat seperti ini disebut immutable, yaitu tidak dapat diubah setelah diciptakan. Banyak bahasa memperlakukan variabel sebagai kotak yang isinya dapat diganti kapan saja, sedangkan val menyerupai label yang ditempel sekali pada sebuah nilai lalu tidak pernah dipindahkan.

Simpan program berikut sebagai pengikatan.scala.

scala pengikatan.scala
// Mengikat nilai dengan val
@main def pengikatan(): Unit =
  val namaLayanan = "arsip-cadangan"
  val batasWaktuDetik = 30
  val aktif = true

  println("Layanan     : " + namaLayanan)
  println("Status      : " + aktif)
  println("Batas waktu : " + batasWaktuDetik + " detik")

Jalankan program tersebut dengan Scala CLI.

bash
# mengompilasi dan menjalankan program pengikatan
scala-cli run pengikatan.scala
text Keluaran
Layanan     : arsip-cadangan
Status      : true
Batas waktu : 30 detik

Tiga pengikatan pada program di atas tidak ditulis tipenya, tetapi tipe setiap nilai tetap diketahui secara pasti. Compiler menentukan tipe tersebut secara otomatis, sebuah mekanisme yang disebut type inference, sehingga namaLayanan bertipe String, batasWaktuDetik bertipe Int, dan aktif bertipe Boolean. Anotasi tipe tetap dapat ditulis secara eksplisit, misalnya val batasWaktuDetik: Int = 30, dan praktik ini lazim dilakukan pada tanda tangan fungsi yang akan digunakan orang lain.

Alasan val menjadi pilihan bawaan bersifat teknis. Ketika sebuah nama terikat sekali dan tidak pernah berubah, pembacaan kode tidak perlu mempertimbangkan keadaan nama tersebut pada setiap titik program. Potongan kode menjadi mudah dipindahkan karena perilakunya tidak bergantung pada urutan eksekusi yang terjadi sebelumnya. Pustaka koleksi bawaan Scala sendiri terdiri atas struktur immutable, sehingga gaya pengikatan val selaras dengan lingkungan sekitarnya.

var: perubahan yang disengaja#

Kata kunci var mengikat nama ke sebuah tempat penyimpanan yang isinya boleh diganti. Pengisian ulang dilakukan dengan tanda sama dengan, sehingga nilai baru menggantikan nilai lama pada tempat yang sama. Sifat ini disebut mutable, yaitu dapat berubah selama program berjalan.

Simpan program berikut sebagai mutasi.scala.

scala mutasi.scala
// Menghitung mundur dengan var
@main def mutasi(): Unit =
  var sisaPercobaan = 3

  while sisaPercobaan > 0 do
    println("Sisa percobaan: " + sisaPercobaan)
    sisaPercobaan -= 1

  println("Selesai")

Jalankan program tersebut.

bash
# mengompilasi dan menjalankan program mutasi
scala-cli run mutasi.scala
text Keluaran
Sisa percobaan: 3
Sisa percobaan: 2
Sisa percobaan: 1
Selesai

Baris sisaPercobaan -= 1 mengurangi nilai sebesar satu dan setara dengan sisaPercobaan = sisaPercobaan - 1. Perulangan while membaca nilai terkini pada setiap putaran, sehingga hitungan mundur berhenti tepat ketika nilai mencapai nol. Inilah penggunaan var yang wajar: perubahan terjadi pada satu nama, di dalam satu prosedur kecil, dan berumur pendek.

Justifikasi penggunaan var biasanya bersifat lokal, misalnya penghitung perulangan, indeks penulisan ke buffer, atau akumulasi hasil pada satu rutinitas. Apabila perubahan keadaan melebar ke banyak bagian program, penalaran tentang perilaku kode menjadi sulit karena setiap fungsi perlu dipertimbangkan dalam semua kemungkinan keadaan variabel tersebut.

Ketika val diisi ulang, compiler menolak#

Aturan pengikatan val bukan sekadar konvensi gaya, melainkan aturan bahasa yang ditegakkan compiler. Program berikut sengaja memuat pelanggaran tersebut. Simpan sebagai val-rusak.scala, lalu jalankan dan amati pesan yang dihasilkan.

scala val-rusak.scala
// Program ini sengaja memuat kesalahan: val diisi ulang
@main def valRusak(): Unit =
  val skor = 80
  skor = 90
  println("Skor: " + skor)
bash
# program ini memang gagal dikompilasi
scala-cli run val-rusak.scala
text Keluaran
Compiling project (Scala 3.9.0, JVM (17))
[error] ./val-rusak.scala:4:3
[error] Reassignment to val skor
[error]   skor = 90
[error]   ^^^^^^^^^
Error compiling project (Scala 3.9.0, JVM (17))
Compilation failed

Perintah berakhir dengan kode keluar bukan nol dan tidak ada satu baris pun program yang dijalankan. Compiler menunjuk lokasi kesalahan ./val-rusak.scala:4:3, yaitu baris 4 kolom 3, kemudian memberikan pesan Reassignment to val skor. Pesan tersebut menyatakan intinya secara langsung: skor terikat dengan val, sehingga baris skor = 90 merupakan pengisian ulang yang dilarang. Deretan tanda ^ pada baris berikutnya menunjuk persis ke ekspresi yang bermasalah.

Penolakan seperti ini terjadi pada saat kompilasi, jauh sebelum program berjalan. Kesalahan mutasi yang tidak disengaja tertangkap sejak awal, bukan muncul sebagai perilaku aneh di tengah operasi. Apabila pengisian ulang memang diperlukan, perbaikannya tegas, yaitu mengganti kata kunci pada deklarasi menjadi var.

Memilih antara val dan var#

Perbedaan keduanya dirangkum pada tabel berikut.

Aspekvalvar
Pengisian ulangDitolak compilerDiizinkan
Keadaan namaTetap dari awal hingga akhirBisa berubah di banyak titik
Posisi sebagai pilihan bawaanYaHanya dengan alasan yang jelas
Lingkup penggunaan yang lazimHampir semua pengikatanState lokal berumur pendek

Kebijakan yang digunakan banyak tim Scala sederhana. Setiap pengikatan dimulai dengan val, lalu diganti menjadi var hanya apabila kebutuhan perubahan sudah nyata dan tetap berada dalam lingkup yang sempit. Dengan kebijakan ini, keputusan mutasi menjadi eksplisit pada kode, mudah dicari, dan mudah ditinjau ulang saat perancangan berkembang.

Ringkasan#

  • val mengikat nama ke satu nilai secara permanen dan tidak menerima pengisian ulang.
  • var mengizinkan pengisian ulang dan paling tepat digunakan untuk state lokal yang berumur pendek.
  • Pengisian ulang val ditolak compiler dengan pesan Reassignment to val ... sebelum program berjalan.
  • Tipe pada pengikatan dapat ditentukan compiler melalui type inference atau ditulis secara eksplisit.

Referensi#