Objek dan ref

Objek bernilai dengan semantik salin, ref di heap, dan tuple sebagai struktur ringkas.

Daftar isi

Program yang semakin besar jarang bertahan hanya dengan int dan string yang terpisah-pisah. Topik ini membahas tiga wadah data untuk nilai yang saling berkaitan, yaitu object bernilai dengan semantik salin, ref object yang hidup di heap, dan tuple sebagai struktur ringkas tanpa nama tipe. Setelah membacanya, perilaku assignment pada ketiganya dapat diprediksi dengan tepat, dan pemilihan wadah untuk sebuah data berlangsung sebagai keputusan yang disertai alasan.

Objek bernilai dan semantik salin#

Tipe object mengelompokkan beberapa field bernama di bawah satu nama tipe. Secara bawaan object adalah tipe bernilai, yaitu nilai utuh yang disalin setiap kali variabel baru diisi darinya, sehingga operator assignment untuk object menyalin setiap komponennya. Deklarasi tipe diletakkan di dalam bagian type, dan konstruktor Titik(x: 3, y: 4) mengisi fieldnya secara lengkap.

nim titik_salin.nim
# Semantik salin objek bernilai
type
  Titik = object
    x, y: int

var a = Titik(x: 3, y: 4)
var b = a          # assignment membuat salinan penuh
b.x = 100          # hanya salinan yang berubah

echo a
echo b
text Keluaran
(x: 3, y: 4)
(x: 100, y: 4)

Pada program di atas, pernyataan var b = a membuat objek kedua yang isinya persis sama, bukan mengaitkan b dengan objek milik a. Karena itu b.x = 100 hanya menyentuh salinannya, dan echo menampilkan kedua nilai yang sudah berbeda itu. Representasi (x: 3, y: 4) berasal dari proc $ yang dihasilkan otomatis untuk object yang dideklarasikan pada modul yang sama. Salin penuh ini terasa murah untuk objek kecil, tetapi perlu dipertimbangkan ulang apabila fieldnya berisi data besar, karena setiap assignment menduplikasi seluruhnya.

Sifat salin ini juga berlaku ketika objek dilewatkan ke proc. Parameter proc yang tidak diberi var bersifat tidak dapat diubah, sehingga penugasan ke fieldnya ditolak saat kompilasi, sedangkan parameter var menyatakan bahwa proc boleh mengubah objek milik pemanggilnya. Parameter bertipe ref object berperilaku berbeda, yaitu selalu mengarah ke objek aslinya, sehingga perubahan field di dalam proc terlihat oleh pemanggil tanpa memandang var.

ref object di heap#

Apabila beberapa variabel harus mengarah ke data yang sama, wadahnya adalah ref object. Kata kunci ref menjadikan variabel sebagai referensi terlacak (traced reference) yang menunjuk ke objek di heap, yaitu wilayah memori untuk alokasi dinamis yang dikelola pengelola memori Nim. Manual bahasa mendeskripsikan referensi sebagai cara memperkenalkan hubungan banyak-ke-satu, sehingga beberapa referensi dapat menunjuk ke satu lokasi memori yang sama dan mengubahnya, perilaku yang dikenal sebagai aliasing. Konstruksi TitikRef(x: 3, y: 4) mengalokasikan objek di heap lalu mengembalikan referensinya.

nim titik_ref.nim
# ref object dibagi referensinya
type
  TitikRef = ref object
    x, y: int

var p = TitikRef(x: 3, y: 4)  # objek dialokasikan di heap
var q = p                      # q menunjuk objek yang sama
q.x = 100                      # terlihat juga melalui p

var kosong: TitikRef
echo kosong == nil

echo p[]
echo q[]
text Keluaran
true
(x: 100, y: 4)
(x: 100, y: 4)

Baris var q = p menyalin alamatnya saja, bukan objeknya, sehingga q.x = 100 langsung terlihat melalui p. Akses field tidak memerlukan dereferensi yang ditulis secara eksplisit karena kompilator melepasnya secara otomatis, dan bentuk p[] tetap tersedia bila dereferensi penuh memang diperlukan. Referensi yang belum menunjuk ke objek mana pun bernilai nil, dan keluaran pertama memperlihatkan variabel kosong yang memang sama dengan nil.

Perbedaan kedua semantik ini dirangkum pada diagram berikut: objek bernilai terduplikasi saat assignment, sedangkan ref object tetap satu objek yang dibagi.

Objek bernilai disalin, ref object dibagi referensiObjek bernilai: assignment menyalinvar ax: 3, y: 4var bx: 100, y: 4dua objek yang sepenuhnya terpisahref object: satu objek di heapvar pvar qx: 100, y: 4heapdua variabel menunjuk objek yang sama
Objek bernilai disalin saat assignment, sedangkan ref object dibagi referensinya

Tuple sebagai struktur ringkas#

Tuple mengelompokkan beberapa nilai tanpa menuntut deklarasi tipe yang bernama. Tipe tuple dinyatakan dengan bentuk tuple[x: int, y: int] atau bentuk singkat (x: int, y: int), dan nilainya dibangun dengan konstruktor yang sama bentuknya, misalnya (x: 3, y: 4). Karena tidak membawa nama tipe, tuple cocok untuk struktur sementara seperti nilai kembalian gabungan atau data yang hanya lewat sekali.

nim tuple_titik.nim
# Tuple anonim dan unpacking
var titik: tuple[x: int, y: int] = (x: 3, y: 4)
var siswa = (nama: "Ali", nilai: 87)

let (x, y) = titik

echo x
echo y
echo siswa.nama
echo siswa.nilai
echo titik == (x: 3, y: 4)
text Keluaran
3
4
Ali
87
true

Field tuple dibaca lewat titik, misalnya siswa.nama, atau lewat indeks konstan seperti titik[0]. Pola let (x, y) = titik melakukan unpacking, yaitu menyalin isi tuple ke beberapa variabel sekaligus. Kesetaraannya bersifat struktural, sehingga titik == (x: 3, y: 4) bernilai benar tanpa memedulikan di mana tipenya dideklarasikan.

Tuple juga menjadi jawaban bawaan untuk proc yang mengembalikan lebih dari satu nilai. Pola let (a, b) = t membongkar hasilnya sekali jalan, dan nama field yang jelas menjaga pembacaannya tetap mudah. Untuk data yang hidup sebentar dan strukturnya tetap, pendekatan ini menghindarkan deklarasi tipe yang hanya digunakan satu kali.

Kesalahan umum#

Kesalahan pertama berupa menganggap objek bernilai dibagi seperti referensi. Gejalanya perubahan pada satu variabel tidak muncul pada variabel lainnya, lalu kode disambung dengan salinan manual yang tidak perlu. Perbaikannya memilih wadah sesuai kebutuhan, yaitu ref object apabila data memang harus dibagi, atau tetap objek bernilai apabila salinan yang terpisah justru diharapkan.

Kesalahan kedua berupa lupa bahwa referensi dapat bernilai nil. Program yang membaca field melalui referensi kosong berhenti tanpa penanganan apa pun, dan pesannya muncul sebagai SIGSEGV di terminal, bukan pengecualian Nim yang dapat ditangani. Kebiasaan amannya memeriksa referensi dengan kosong != nil terlebih dahulu, atau memastikan konstruktor selalu dipanggil sebelum referensi digunakan.

Kesalahan ketiga berupa memilih tuple untuk data yang hidup lama dan terus berkembang. Tuple tidak membawa nama tipe, sehingga perubahan strukturnya ikut merambat ke setiap pemakaian secara diam-diam. Untuk data yang menjadi bagian tetap program, object dengan nama tipenya yang eksplisit lebih mudah dipertahankan.

Ringkasan#

  • Object adalah tipe bernilai, dan assignment menyalin seluruh fieldnya sehingga kedua variabel memegang data yang terpisah.
  • ref object mengalokasikan satu objek di heap, membagi referensinya ke semua variabel, dan bernilai nil sebelum diisi.
  • Tuple adalah tipe struktural tanpa nama tipe, lengkap dengan field bernama, unpacking let (a, b) = t, dan kesetaraan struktural.
  • Pemilihan wadah mengikuti kebutuhan, yaitu salinan terpisah untuk object, data bersama untuk ref object, dan struktur sementara untuk tuple.

Referensi#