Tipe data nyata sering berupa pilihan di antara beberapa bentuk, misalnya bentuk geometri yang bisa berupa lingkaran atau persegi. Topik ini membahas case object, yaitu object yang fieldnya bergantung pada varian yang dipilih melalui sebuah enum. Struktur semacam ini dikenal sebagai union terdiskriminasi (discriminated union), dan setelah membacanya, data yang bentuknya bergantian dapat dimodelkan tanpa dipecah ke variabel-variabel yang saling terlepas.
Objek bervarian dengan enum pemilih#
Deklarasinya memasukkan pernyataan case ke dalam bagian type. Field pertama berupa diskriminan, yaitu field bertipe enum yang menentukan varian aktifnya, lalu setiap cabang of menuliskan field yang hanya berlaku untuk varian tersebut. Bentuk ini menjanjikan bahwa field cabang tertentu hanya terbaca apabila diskriminan memang menunjuk cabang itu.
Contoh berikut memodelkan bentuk geometri dengan dua varian. Diskriminan jenis bertipe BentukJenis, cabang bjLingkaran memiliki field radius, dan cabang bjPersegi memiliki field sisi.
# Objek bervarian untuk bentuk geometri
import std/math
type
BentukJenis = enum
bjLingkaran
bjPersegi
Bentuk = object
case jenis: BentukJenis
of bjLingkaran:
radius: float
of bjPersegi:
sisi: float
proc luas(b: Bentuk): float =
case b.jenis
of bjLingkaran:
PI * b.radius * b.radius
of bjPersegi:
b.sisi * b.sisi
var lingkaran = Bentuk(jenis: bjLingkaran, radius: 7.0)
var persegi = Bentuk(jenis: bjPersegi, sisi: 5.0)
echo luas(lingkaran)
echo luas(persegi)153.93804002589985
25.0Field pada varian aktif digunakan seperti field object biasa, sedangkan proc luas menyeleksi perhitungannya melalui case b.jenis. Keluaran pertama berupa PI * 7 * 7 yang dicetak dengan seluruh presisi float-nya, dan keluaran kedua berupa 5 * 5. Karena BentukJenis hanya memiliki dua nilai, kedua cabang of menutup semua kemungkinan dan cabang else tidak diperlukan. Apabila varian baru ditambahkan ke enum, setiap case yang memilih berdasarkan diskriminan itu gagal dikompilasi sampai cabang barunya ditulis, sehingga perubahan model tidak lolos tanpa disadari.
Tata letak memori objek bervarian digambarkan pada diagram berikut: diskriminan selalu tersimpan, sedangkan area cabang hanya menampung field varian yang aktif.
Memodelkan tipe data aljabar#
Union terdiskriminasi adalah cara Nim menyusun tipe data aljabar bagian sum type, yaitu tipe yang nilainya berupa satu pilihan di antara beberapa alternatif dengan muatan yang berbeda. Setiap varian membawa data miliknya sendiri, dan label enum-nya menjamin muatan itu tidak tertukar. Pola ini menggantikan kombinasi field opsional yang mudah keliru, misalnya pasangan field nilai dan pesan yang seharusnya diisi bergantian tanpa ada yang mengawasinya.
Contoh berikut memodelkan hasil pembacaan angka dari masukan yang bisa gagal. Varian spSukses membawa nilai berupa int, sedangkan varian spGagal membawa pesan berupa string.
# Union terdiskriminasi untuk hasil parsing
type
StatusParsing = enum
spSukses
spGagal
HasilParsing = object
case status: StatusParsing
of spSukses:
nilai: int
of spGagal:
pesan: string
proc sukses(n: int): HasilParsing =
HasilParsing(status: spSukses, nilai: n)
proc gagal(pesan: string): HasilParsing =
HasilParsing(status: spGagal, pesan: pesan)
proc tampilkan(h: HasilParsing): string =
case h.status
of spSukses:
"nilai: " & $h.nilai
of spGagal:
"gagal: " & h.pesan
echo tampilkan(sukses(42))
echo tampilkan(gagal("bukan angka"))nilai: 42
gagal: bukan angkaKedua proc pembangun, sukses dan gagal, menjamin bahwa diskriminan dan fieldnya selalu konsisten. Proc tampilkan membaca hasil hanya melalui case h.status, sehingga tidak ada jalur yang membaca field milik varian lain. Menambahkan varian ketiga, misalnya spKosong untuk masukan yang kosong, menuntut cabang baru pada setiap case yang memilih berdasarkan diskriminan, dan kompilator mengingatkan sampai semuanya lengkap.
Keunggulan lainnya terasa ketika model berkembang. Dokumentasi tutorial Nim bagian kedua mencatat bahwa hierarki object sering kali berlebihan untuk kebutuhan varian sederhana, sedangkan case object tidak menuntut konversi antar tipe karena semua varian tetap satu tipe yang sama. Satu seq[Bentuk] dapat menampung lingkaran dan persegi sekaligus, dan setiap elemennya diolah dengan pola case yang sama.
Akses varian yang salah dan FieldDefect#
Kompilator tidak menolak akses ke field varian yang tidak aktif, sehingga kesalahan berikut baru terlihat saat program dijalankan.
# tidak-dijalankan
# membaca field milik cabang lain memicu FieldDefect saat runtime
var b = Bentuk(jenis: bjLingkaran, radius: 7.0)
echo b.sisiProgram di atas dikompilasi tanpa keluhan, tetapi saat dijalankan Nim memicu pengecualian FieldDefect dengan pesan field 'sisi' is not accessible for type 'Bentuk' using 'jenis = bjLingkaran'. Pesan itu menyebutkan field yang diakses, tipe object-nya, dan nilai diskriminan saat itu, sehingga penyebabnya mudah dilacak. Aturan yang sama berlaku untuk penugasan diskriminan, yaitu memberi b.jenis = bjPersegi pada objek yang varian aktifnya lingkaran, karena perubahan itu mengganti cabang yang sedang aktif.
Kebiasaan yang menjaga kode tetap aman adalah membaca case object selalu melalui case pada diskriminan, sebagaimana proc luas dan tampilkan pada contoh di atas. Pola ini memindahkan pemeriksaan varian dari kewaspadaan penulis kode ke tugas kompilator. Konsumsi data yang terikat cabang menjadi eksplisit, dan pembaca berikutnya dapat melihat batasnya tanpa menelusuri seluruh proc.
Kesalahan umum#
Kesalahan pertama berupa mengisi konstruktor dengan field milik cabang yang tidak cocok. Konstruksi Bentuk(jenis: bjPersegi, radius: 7.0) ditolak saat kompilasi dengan pesan a case selecting discriminator 'jenis' with value 'bjPersegi' appears in the object construction, but the field(s) 'radius' are in conflict with this value, sehingga ketidaksesuaian label dan muatan tertangkap lebih awal. Perbaikannya menyamakan kelompok field dengan nilai diskriminan pada konstruktor yang sama.
Kesalahan kedua berupa lupa menambah cabang pada proc lama setelah varian baru dideklarasikan. Kompilator menolak setiap case yang belum menutup seluruh nilai diskriminan, dan pesannya menunjuk nilai yang belum tertangani. Perbaikannya menulis cabang untuk varian tersebut, karena jalan pintas berupa else yang membungkam pemeriksaan justru menghilangkan jaminan utama union terdiskriminasi.
Ringkasan#
- Case object menempatkan pernyataan
casedi dalam bagiantype, dengan enum sebagai diskriminan dan field milik tiap varian di cabangof-nya. - Struktur ini menyusun union terdiskriminasi, yaitu tipe data aljabar bagian sum type yang label dan muatannya tidak dapat tertukar.
- Akses ke field varian yang tidak aktif memicu
FieldDefectsaat runtime, dan penugasan diskriminan dibatasi agar cabang aktif tidak berubah diam-diam. - Menambah varian baru membuat setiap
caseyang cabangnya belum lengkap gagal dikompilasi, sehingga perubahan model terpandu oleh kompilator.
Referensi#
- Nim Manual — nim-lang.org — Bagian object variants mengatur diskriminan, pembatasan penugasannya, dan FieldDefect.
- Nim Tutorial (Part II) — nim-lang.org — Bagian object variants memperkenalkan varian objek dan membandingkannya dengan hierarki object.