Angka 30.0 dapat berarti meter, kaki, atau satuan lain yang menyusulnya, dan kompilator tidak punya alasan menolaknya selama semuanya bertipe float. Topik ini membahas distinct type, yaitu tipe baru yang dibuat dari tipe dasar dengan satu kata kunci, sehingga nilai dari satuan yang berbeda tidak dapat saling dipertukarkan. Setelah membacanya, kesalahan pencampuran satuan tertangkap saat kompilasi, bukan saat program sudah berjalan dengan data sungguhan.
distinct sebagai tipe baru#
Kata kunci distinct mendeklarasikan tipe baru dari sebuah tipe dasar. Manual bahasa menetapkan sifat intinya, yaitu tipe distinct tidak berimplikasi pada hubungan subtipe dengan tipe dasarnya, sehingga keduanya tidak saling menggantikan di tempat mana pun. Konversi eksplisit dari distinct ke tipe dasarnya dan sebaliknya tetap diperbolehkan, sedangkan seluruh perilaku lainnya harus didefinisikan sendiri oleh penulis tipe tersebut.
Penamaan variabel seperti jarakMeter tidak memberikan perlindungan yang sama karena tidak diperiksa oleh siapa pun, sedangkan tipe distinct dipaksakan oleh kompilator pada setiap tempat nilai digunakan. Pencarian pengenal Meter juga menemukan seluruh titik pemakaian satuan tersebut, sesuatu yang tidak dapat dilakukan terhadap komentar.
Program berikut mendeklarasikan dua satuan panjang dari float. Konstruksi Meter(30.0) melabeli nilai float sebagai meter, float(jarak) mengembalikannya ke float biasa, dan proc keMeter memuat konversi kaki ke meter dengan faktor 0.3048.
# Deklarasi distinct dan konversi eksplisit
type
Meter = distinct float
Kaki = distinct float
proc keMeter(k: Kaki): Meter =
Meter(float(k) * 0.3048)
var jarak: Meter = Meter(30.0)
var tinggi: Kaki = Kaki(9.8)
var tinggiMeter = keMeter(tinggi)
echo float(jarak)
echo float(tinggiMeter)30.0
2.9870400000000004Keluaran kedua memperlihatkan perilaku float yang sebaiknya diantisipasi sejak awal. Nilai 9.8 kaki dikalikan 0.3048 secara matematis menghasilkan 2.98704, tetapi kedua bilangan itu tidak persis terwakili dalam biner, sehingga Nim mencetaknya dengan digit penuh sebagai 2.9870400000000004. Angka yang dicetak berasal dari perhitungan yang benar, dan pembulatan tampilan dilakukan terpisah apabila diperlukan. Di sinilah letak nilai distinct, yaitu label satuan melekat pada tipe sehingga jarak tidak mungkin tercampur dengan tinggi tanpa konversi yang disengaja.
Penugasan tanpa label juga ditolak. Deklarasi var jarak: Meter = 30.0 dianggap mencampur float64 dengan Meter, dan kompilator menjawab dengan Error: type mismatch: got 'float64' for '30.0' but expected 'Meter = distinct float'. Konversi Meter(30.0) itulah yang menyatakan niat secara eksplisit. Sifat ini menutup celah klasik yang membiarkan angka mentah masuk ke variabel bersatuan tanpa jejak, karena setiap masuknya nilai wajib melewati konversi yang dapat ditemukan melalui pencarian.
Operator dan pembaruan yang diperlukan#
Tipe distinct tidak mewarisi operator tipe dasarnya, sehingga +, <, ==, sampai $ untuk pencetakan semuanya belum tersedia. Pemanggilan echo jarak pada tipe tanpa $ ditolak kompilator dengan pesan type mismatch, dan penjumlahan dua Meter juga ditolak karena belum ada + yang menerimanya. Pragma {.borrow.} menyelesaikan sebagian besar kasus dengan meminjam implementasi proc milik tipe dasar tanpa menghasilkan kode baru.
# Operator untuk distinct dengan {.borrow.}
type
Meter = distinct float
proc `+`(a, b: Meter): Meter {.borrow.}
proc `<`(a, b: Meter): bool {.borrow.}
proc `==`(a, b: Meter): bool {.borrow.}
proc `$`(m: Meter): string {.borrow.}
var jarak = Meter(30.0)
var batas = Meter(35.0)
echo jarak + Meter(5.0)
echo jarak < batas
echo jarak == Meter(30.0)35.0
true
trueKeempat proc di atas cukup dideklarasikan dengan {.borrow.} karena isinya persis perilaku tipe dasar, sebagaimana dijelaskan manual bahwa pragma borrow menggunakan implementasi proc yang menangani tipe dasarnya. Apabila perilakunya memang berbeda, proc ditulis biasa seperti keMeter pada bagian “distinct sebagai tipe baru”. Manual sendiri menggunakan skema ini untuk memodelkan mata uang, yaitu Dollar dan Euro yang sama-sama berasal dari int tetapi tidak boleh dijumlahkan satu sama lain, dan satuan fisik seperti Meter dan Kaki mengikuti pola yang sama.
Karena distinct dari tipe ordinal tetap terhitung ordinal, penggunaannya tidak terbatas pada satuan fisik. Pola yang sama dapat diterapkan pada nomor peserta ujian yang dibuat dari int, sehingga nomor peserta tidak tertukar dengan nomor kursi, dan keduanya tidak tertukar dengan bilangan biasa. Apabila banyak tipe satuan didefinisikan sekaligus, template dapat mengotomatiskan pendefinisian operator yang berulang, sebagaimana ditunjukkan contoh mata uang pada manual.
Kesalahan umum#
Kesalahan pertama berupa mencampur dua satuan tanpa konversi, dan inilah kasus yang justru ingin dideteksi.
# tidak-dijalankan
# penjumlahan Meter dan Kaki ditolak saat kompilasi
var jarak: Meter = Meter(30.0)
var tinggi: Kaki = Kaki(9.8)
echo jarak + tinggiKompilator menolak program di atas dengan pesan Error: type mismatch untuk Expression: jarak + tinggi, lalu mencantumkan [1] jarak: Meter dan [2] tinggi: Kaki. Pesan tersebut menunjukkan bahwa tidak ada proc + yang menerima pasangan tipe itu, dan perbaikannya berupa konversi yang disengaja, misalnya menyeragamkan satuan melalui proc seperti keMeter. Kesalahan kedua berupa lupa mendefinisikan $, sehingga echo jarak ditolak meskipun semua operator aritmetika sudah lengkap. Perbaikannya berupa satu proc $ dengan {.borrow.}, atau konversi eksplisit float(jarak) di tempat pencetakan.
Kesalahan ketiga berupa mengira konversi eksplisit dapat digantikan penugasan biasa. Pesan errornya selalu menyebut tipe yang diharapkan, misalnya expected 'Meter = distinct float', sehingga letak kelirunya langsung terbaca. Kebiasaan yang menjaga satuan tetap utuh adalah menempatkan konversi hanya di tepi modul, yaitu saat nilai masuk dari luar, lalu menyimpan seluruh perhitungan internal dalam tipe bersatuannya.
Ringkasan#
distinctmembuat tipe baru dari tipe dasar tanpa hubungan subtipe, sehingga nilai dari dua distinct tidak saling dipertukarkan.- Konversi eksplisit dua arah tetap tersedia, yaitu
Meter(30.0)danfloat(jarak). - Tipe distinct tidak mewarisi operator apa pun, dan
{.borrow.}meminjam implementasi tipe dasar untuk operator yang perilakunya sama. - Kesalahan pencampuran satuan tertangkap saat kompilasi sebagai type mismatch, bukan saat program berjalan.
Referensi#
- Nim Manual — nim-lang.org — Bagian distinct type menetapkan aturan konversi eksplisit dan pragma borrow beserta contoh mata uangnya.
- Nim Tutorial (Part I) — nim-lang.org — Bagian distinct type merangkum sifat tipe baru yang tidak berhubungan subtipe dengan dasarnya.