Polimorfisme parametrik (parametric polymorphism) memungkinkan satu proc ditulis secara generik, yaitu tanpa menyebut tipe konkret pada parameternya, kemudian dipakai ulang untuk banyak tipe tanpa disalin dengan tangan. Topik ini membahas penanda $ pada parameter proc dalam bentuk implisit dan eksplisit, mekanisme substitusi yang berlangsung saat kompilasi, serta klausul where yang membatasi tipe yang diterima. Contoh yang dijalankan mencakup proc penjumlahan elemen slice bertipe int dan f32, proc cetak generik, dan proc penukaran isi dua variabel melalui pointer. Program utuhnya memanggil proc yang sama dengan dua tipe berbeda, dan keluaran aslinya ditempel setelah kode.
Parameter polimorfik pada proc#
Odin mendukung polimorfisme parametrik, yaitu kemampuan menulis proc atau data secara generik sehingga satu deklarasi dapat menangani banyak tipe dengan pola yang sama. Dalam kode basis dan dokumentasi resminya, fitur ini dijuluki parapoly. Penandanya berupa tanda dolar $ yang diletakkan di depan nama tipe pada daftar parameter. Nama di belakang $, misalnya T pada []$T, disebut nama polimorfik (polymorphic name) dan berlaku sebagai nama tipe di dalam tubuh proc. Pada bentuk implisit, tipe konkret tidak diserahkan oleh pemanggil, melainkan disimpulkan oleh compiler dari tipe argumen yang diterima.
Proc jumlah pada fragmen berikut menerima slice yang elemennya bertipe apa pun, dan tipe itu diberi nama T untuk dipakai kembali di dalam proc. Variabel total dideklarasikan bertipe T, sehingga nilainya dimulai dari nol sesuai tipe instansinya. Bentuk ini disebut implisit karena tidak ada parameter khusus untuk tipe, sehingga pemanggilannya tidak berbeda dari pemanggilan proc biasa. Proc yang sama dapat menerima []int maupun []f32 tanpa deklarasi kedua.
// tidak-dijalankan
// T disimpulkan compiler dari tipe slice yang dikirim pemanggil
jumlah :: proc(values: []$T) -> T {
total: T
for v in values {
total += v
}
return total
}Tanda $ wajib ditulis pada setiap nama polimorfik. Tanpa tanda itu, T hanyalah nama yang belum dideklarasikan, dan compiler menolak deklarasi proc, sehingga program tidak dapat dibangun. Kesalahan ini beserta pesan errornya dibahas pada bagian Kesalahan umum di akhir topik.
Substitusi saat kompilasi#
Polimorfisme parametrik Odin bekerja melalui substitusi saat kompilasi. Compiler mengganti setiap nama polimorfik dengan tipe konkret, kemudian menyusun satu salinan proc untuk tipe tersebut. Salinan yang dihasilkan disebut instansi (instantiation), dan seluruh prosesnya selesai sebelum program dijalankan. Pemanggilan jumlah dengan []int menghasilkan satu instansi yang totalnya bertipe int, sedangkan pemanggilan berikutnya dengan []f32 menghasilkan instansi kedua yang totalnya bertipe f32.
Karena substitusinya lengkap pada saat kompilasi, pemanggilan proc generik tidak membawa pencarian tipe ataupun pembungkusan nilai tambahan saat program berjalan. Kode yang dihasilkan pun setara dengan versi yang ditulis khusus untuk setiap tipe. Konsekuensinya, nama polimorfik hanya ada pada saat kompilasi dan tidak berperilaku seperti variabel biasa. Tipe instansi tetap dapat dibaca melalui proc bawaan seperti typeid_of, dan proc cetak pada program berikut memanfaatkan typeid_of untuk menampilkan nama tipe setiap argumen.
Program utuh dengan dua instansi#
Program berikut memuat empat proc generik beserta pemanggilannya di main. Proc jumlah dan cetak memakai bentuk implisit, proc tambah memakai bentuk eksplisit dengan parameter typeid, sedangkan proc swap menukar isi dua variabel milik pemanggil melalui pointer. Proc jumlah dipanggil dua kali dengan tipe slice yang berbeda agar kedua instansinya tampak berdampingan pada keluaran yang sama. Proc cetak turut dipanggil dengan tiga tipe yang berbeda, sehingga program menghasilkan sembilan instansi yang seluruhnya berasal dari empat deklarasi.
package main
import "core:fmt"
// jumlah menjumlahkan seluruh elemen slice dengan tipe apa pun
// yang mendukung operator + pada waktunya masing-masing
jumlah :: proc(values: []$T) -> T {
total: T
for v in values {
total += v
}
return total
}
// tambah menjumlahkan dua nilai yang tipenya diserahkan secara eksplisit
tambah :: proc($T: typeid, a, b: T) -> T {
return a + b
}
// cetak menampilkan nilai beserta nama tipe instansinya
cetak :: proc(nilai: $T) {
fmt.printfln("nilai %v bertipe %v", nilai, typeid_of(T))
}
// swap menukar isi dua variabel melalui alamat memori keduanya
swap :: proc(a, b: ^$T) {
sementara := a^
a^ = b^
b^ = sementara
}
main :: proc() {
angka := []int{3, 5, 8, 13, 21}
suhu := []f32{28.5, 30.2, 27.9}
fmt.printfln("jumlah angka = %v", jumlah(angka))
fmt.printfln("jumlah suhu = %v", jumlah(suhu))
fmt.printfln("tambah int = %v", tambah(int, 12, 30))
fmt.printfln("tambah f64 = %v", tambah(f64, 1.5, 2.25))
cetak("teks uji")
cetak(42)
cetak(3.14)
a, b := 7, 9
swap(&a, &b)
fmt.printfln("setelah swap int: a = %v, b = %v", a, b)
x, y := "pertama", "kedua"
swap(&x, &y)
fmt.printfln("setelah swap string: x = %v, y = %v", x, y)
}# sumberkan lingkungan sandbox jalur odin bila belum tersedia
[ -f /home/kal/my-project/tools/env.sh ] && source /home/kal/my-project/tools/env.sh
# jalankan program dari dalam folder topik
odin run main.odin -filejumlah angka = 50
jumlah suhu = 86.6
tambah int = 42
tambah f64 = 3.75
nilai teks uji bertipe string
nilai 42 bertipe int
nilai 3.14 bertipe f64
setelah swap int: a = 9, b = 7
setelah swap string: x = kedua, y = pertamaKeluaran memperlihatkan bahwa substitusi bekerja pada setiap proc. Pemanggilan jumlah(angka) menghasilkan 50 dari lima bilangan int, sedangkan jumlah(suhu) menghasilkan 86.6 dari tiga nilai f32, dan kedua pemanggilan berasal dari deklarasi yang sama. Proc cetak menampilkan nama tipe setiap instansinya, yaitu string, int, dan f64, karena typeid_of(T) memberikan deskriptor tipe instansi kepada proc tersebut. Pemanggilan tambah(int, 12, 30) menghasilkan 42, sedangkan tambah(f64, 1.5, 2.25) menghasilkan 3.75. Proc swap menukar pasangan int menjadi 9 dan 7, lalu menukar pasangan string. Kedua penukaran memperlihatkan bahwa pola penerusan pointer yang sama bekerja untuk tipe yang berbeda.
Bentuk eksplisit dengan typeid#
Bentuk eksplisit menempatkan parameter tipe pada posisi terpisah di awal daftar parameter. Anotasi typeid menyatakan bahwa parameter itu menerima tipe, bukan nilai, sehingga pemanggil menyebut tipe sebagai argumen. Deklarasi tambah :: proc($T: typeid, a, b: T) -> T memperkenalkan T terlebih dahulu, kemudian dua parameter a dan b yang bertipe T. Pemanggilannya ditulis tambah(int, 12, 30) dan tambah(f64, 1.5, 2.25) sebagaimana pada program utuh, dan kedua pemanggilan itu menetapkan tipe instansinya secara langsung.
Bentuk eksplisit diperlukan apabila tipe tidak dapat disimpulkan dari argumen nilai mana pun. Kasus yang lazim adalah proc yang hanya menyebut T pada tipe kembaliannya, misalnya proc yang mengembalikan ^T hasil alokasi memori sebesar ukuran T. Dokumentasi resmi memakai pola ini pada proc my_new :: proc($T: typeid) -> ^T. Tanpa parameter typeid, compiler tidak memiliki petunjuk tipe apa yang dimaksud, sehingga proc semacam itu tidak dapat ditulis dengan bentuk implisit. Apabila tipe sudah dapat disimpulkan dari parameter nilai, bentuk implisit tetap lebih ringkas dan menjadi pilihan pertama.
Klausul where untuk pembatasan tipe#
Tanda $ menerima tipe apa pun secara bawaan, sedangkan klausul where membatasi tipe yang diterima proc atau tipe generik. Klausul ini ditulis pada baris sebelum kurung kurawal pembuka tubuh proc dan berisi satu kondisi atau lebih yang dinilai saat kompilasi. Apabila kondisinya bernilai false untuk tipe yang diserahkan pemanggil, compiler menolak pemanggilan itu dan proses kompilasi berhenti dengan pesan error.
// tidak-dijalankan
// proc ini hanya menerima tipe numerik; predikatnya berasal dari base:intrinsics
jumlah_pasangan :: proc(a, b: $T) -> T
where intrinsics.type_is_numeric(T) {
return a + b
}Pada contoh di atas, pemanggilan dengan int dan f64 diterima karena keduanya bertipe numerik, sedangkan pemanggilan dengan bool ditolak dengan pesan bahwa klausul where dinilai false. Predikat type_is_numeric berasal dari paket base:intrinsics yang berisi utilitas pemeriksaan tipe pada saat kompilasi. Klausul where juga dapat menguji panjang array, persamaan tipe, dan nilai konstanta lainnya. Klausul yang sama dapat ditempelkan pada deklarasi struct dan union generik, sehingga batas tipe berlaku pula pada data yang dibangun dengan parameter polimorfik. Pembahasan yang lebih lengkap tentang penilaian kondisi saat kompilasi ada pada topik Percabangan saat kompilasi.
Kesalahan umum#
Kesalahan pertama adalah lupa menuliskan $ di depan nama tipe parameter. Deklarasi jumlah :: proc(values: []T) -> T tanpa $ ditolak compiler dengan pesan Undeclared name: T pada setiap pemakaian T, karena T dianggap nama yang belum dideklarasikan dan bukan nama polimorfik. Perbaikannya berupa penambahan tanda $ sehingga deklarasinya menjadi []$T. Pesan error yang serupa muncul apabila proc memakai bentuk implisit padahal T hanya dipakai pada tipe kembaliannya. Penyebabnya adalah tidak ada parameter nilai yang bisa memberi petunjuk tipe T. Perbaikannya adalah mendeklarasikan T melalui bentuk eksplisit $T: typeid.
Kesalahan kedua adalah menyerahkan argumen yang tidak cocok dengan tipe yang disebut pada bentuk eksplisit. Pemanggilan tambah(int, 12, "teks") ditolak compiler dengan pesan Cannot convert '"teks"' to 'int' from 'untyped string', karena nilai string tidak dapat dijadikan int tanpa konversi yang sah. Perbaikannya berupa penyelarasan tipe, yaitu memanggil proc tersebut dengan seluruh argumen bertipe sama atau memilih tipe lain seperti f64 pada pemanggilannya.
Kesalahan ketiga adalah memanggil proc generik dengan nilai yang tidak memberi petunjuk tipe apa pun. Pemanggilan cetak(nil) ditolak compiler dengan pesan Cannot determine type from the parameter, got 'untyped nil', karena nilai nil tidak melekat pada tipe konkret mana pun. Perbaikannya berupa penyerahan nilai yang bertipe jelas, misalnya cetak("") yang menyimpulkan T sebagai string, atau pemanggilan bentuk eksplisit yang menyebut tipe secara langsung.
Ringkasan#
- Tanda
$pada tipe parameter proc menandai nama polimorfik. Pada bentuk implisit, tipe konkret disimpulkan dari argumen, sedangkan bentuk eksplisit$T: typeidmenyerahkan tipe sebagai argumen pertama pemanggilan. - Substitusi berlangsung seluruhnya saat kompilasi. Setiap tipe baru menghasilkan instansi proc tersendiri di dalam biner, tanpa pencarian tipe ataupun biaya tambahan saat program berjalan.
- Klausul
wheremembatasi tipe yang diterima dan dinilai saat kompilasi, sehingga pemanggilan dengan tipe di luar batas ditolak sebelum program dijalankan. - Nama polimorfik hanya ada saat kompilasi dan tidak berperilaku seperti variabel biasa, sedangkan tipe instansi tetap dapat dibaca melalui proc bawaan seperti
typeid_of.
Referensi#
- Overview — Odin Documentation — Bagian Parametric polymorphism menjelaskan bentuk implisit, bentuk eksplisit dengan typeid, instansi, dan klausul where.
- Paket core:fmt — Referensi paket Odin
— Dokumentasi proc cetak seperti
fmt.printflnbeserta verb%vyang dipakai pada contoh program.