Klausa when melakukan percabangan pada saat kompilasi, yaitu memilih blok kode yang ikut dikompilasi berdasarkan kondisi yang nilainya sudah pasti sebelum program dijalankan. Topik ini membahas pemakaian when untuk melakukan dispatch (pemilihan cabang kode) berdasarkan tipe parameter polimorfik di dalam proc generik, perbedaannya terhadap pernyataan if, serta penetapan konstanta saat kompilasi melalui #config dan bendera -define. Contoh yang dijalankan mencakup proc yang mengembalikan label sesuai tipe, proc yang memilih kapasitas buffer menurut ukuran tipe, dan program yang berubah perilakunya melalui define pada baris perintah.
Klausa when dan kondisi saat kompilasi#
Pernyataan when menilai kondisinya pada saat kompilasi dan hanya mengompilasi blok yang kondisinya bernilai true, sedangkan blok yang tidak dipenuhi tidak dikompilasi sama sekali. Sifat ini berbeda dari pernyataan if yang mengompilasi seluruh cabangnya terlebih dahulu, lalu memilih salah satunya ketika program berjalan. Karena keputusannya diambil lebih dahulu, when tidak menambahkan pekerjaan apa pun pada saat program berjalan, dan kode di dalam cabang yang terbuang tidak pernah mencapai tahap pemeriksaan tipe maupun tahap pembangkitan kode.
Kondisi when wajib berupa ekspresi konstanta. Sumber kondisi yang lazim adalah konstanta bawaan compiler, yaitu nama seperti ODIN_OS, ODIN_ARCH, dan ODIN_DEBUG yang tersedia di seluruh berkas serta menyatakan target sistem operasi, arsitektur target, dan kehadiran mode debug. Bentuk pemakaiannya terlihat pada fragmen berikut.
// tidak-dijalankan
when ODIN_OS == .Linux {
fmt.println("program dibangun untuk target Linux")
} else when ODIN_OS == .Windows {
fmt.println("program dibangun untuk target Windows")
} else {
fmt.println("target sistem operasi lain")
}Pada sandbox yang dipakai di jalur ini, kondisi ODIN_OS == .Linux bernilai true, sehingga hanya cabang pertama yang dikompilasi dan programnya menampilkan pesan cabang tersebut. Kondisi yang menyertakan nilai yang baru diketahui ketika program berjalan ditolak compiler pada saat kompilasi, dan bentuk pesan errornya dibahas pada bagian Kesalahan umum. Pernyataan when juga dapat berada di lingkup berkas, sehingga satu kondisi memilih deklarasi konstanta yang berbeda untuk seluruh paket. Pustaka core memakai pola ini untuk menyesuaikan diri dengan platform target.
Dispatch tipe parameter di dalam proc parapoly#
Di dalam proc parapoly, nama polimorfik T berlaku sebagai nilai saat kompilasi, sehingga T dapat dibandingkan dengan tipe lain di dalam kondisi when. Perbandingan seperti T == int sah di dalam proc parapoly dan menjadi cara baku untuk menyusun tubuh proc menurut tipe instansinya. Rantai when, else when, dan else terbaca seperti percabangan biasa, tetapi setiap instansi hanya memuat satu jalur, yaitu jalur yang tipe instansinya cocok.
Perbandingan yang sama juga sah di dalam pernyataan if, tetapi akibatnya berbeda. Pernyataan if mengompilasi seluruh cabangnya untuk setiap instansi, sehingga kode yang hanya sah untuk tipe tertentu gagal dikompilasi ketika instansi lain dibuat. Contoh berikut memperlihatkan dispatch tipe dengan if.
// tidak-dijalankan
pilih :: proc($T: typeid) -> T {
if T == f32 {
return 1.5
} else if T == int {
return 1
} else {
panic("tipe tidak didukung")
}
}Pemanggilan proc di atas dengan int ditolak compiler dengan pesan '1.5' truncated to 'int', got 3.0/2, karena cabang return 1.5 tetap diperiksa tipe meskipun kondisi T == f32 bernilai false untuk instansi tersebut. Versi when di bawah ini menyelesaikan masalah yang sama.
// tidak-dijalankan
pilih :: proc($T: typeid) -> T {
when T == f32 {
return 1.5
} else when T == int {
return 1
} else {
panic("tipe tidak didukung")
}
}Versi when di atas lulus kompilasi dan berjalan untuk kedua pemanggilan, karena setiap instansi hanya memuat cabang yang dipenuhinya dan cabang f32 tidak pernah ada pada instansi int. Kemampuan when tidak berhenti pada pemilihan pernyataan. Konstanta yang dideklarasikan di dalam cabang when tetap terlihat pada sisa tubuh proc, sehingga satu kondisi dapat memilih konstanta yang berbeda per instansi. Pustaka core:strconv menerapkan pola ini ketika memilih konstanta Bits bertipe u64 untuk f64 dan u32 untuk f32.
// tidak-dijalankan
panjang_bit :: proc($T: typeid) -> int {
when T == f64 {
Bits :: u64
} else {
Bits :: u32
}
return size_of(Bits)
}Proc semacam itu mengembalikan 8 untuk f64 dan 4 untuk f32, yaitu ukuran konstanta yang terpilih pada masing-masing instansi.
Program utuh dengan dispatch per tipe#
Program berikut memuat dua proc yang menyusun tubuhnya melalui when beserta pemanggilannya di main. Proc deskripsi_tipe membandingkan T terhadap int, f32, dan string, lalu mengembalikan label yang berbeda untuk setiap tipe dan label cadangan melalui else untuk tipe lainnya. Proc ukuran_buffer memakai kondisi size_of(T) >= 8 dan size_of(T) >= 4, yaitu nilai yang sudah pasti saat kompilasi, sehingga kapasitas batch dipilih menurut ukuran memori setiap tipe. Kedua proc menerima tipe secara eksplisit melalui parameter typeid sebagaimana bentuk eksplisit yang dibahas pada topik Polimorfisme melalui $.
package main
import "core:fmt"
// konstanta konfigurasi; nilainya dapat ditimpa lewat bendera -define saat kompilasi
MODE_DETAIL :: #config(MODE_DETAIL, false)
// deskripsi_tipe memilih label saat kompilasi berdasarkan tipe parameternya
deskripsi_tipe :: proc($T: typeid) -> string {
when T == int {
return "bilangan bulat bertanda"
} else when T == f32 {
return "bilangan pecahan presisi tunggal"
} else when T == string {
return "rangkaian karakter"
} else {
return "tipe lain"
}
}
// ukuran_buffer memilih kapasitas batch sesuai ukuran memori tipe;
// tipe yang lebih besar menyimpan lebih sedikit nilai per batch
ukuran_buffer :: proc($T: typeid) -> int {
when size_of(T) >= 8 {
return 64
} else when size_of(T) >= 4 {
return 128
} else {
return 256
}
}
main :: proc() {
fmt.printfln("int : %v", deskripsi_tipe(int))
fmt.printfln("f32 : %v", deskripsi_tipe(f32))
fmt.printfln("string : %v", deskripsi_tipe(string))
fmt.printfln("bool : %v", deskripsi_tipe(bool))
fmt.printfln("buffer f64 = %v nilai per batch", ukuran_buffer(f64))
fmt.printfln("buffer f32 = %v nilai per batch", ukuran_buffer(f32))
fmt.printfln("buffer u8 = %v nilai per batch", ukuran_buffer(u8))
when MODE_DETAIL {
fmt.println("mode detail: aktif")
} else {
fmt.println("mode detail: nonaktif")
}
}# 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 -fileint : bilangan bulat bertanda
f32 : bilangan pecahan presisi tunggal
string : rangkaian karakter
bool : tipe lain
buffer f64 = 64 nilai per batch
buffer f32 = 128 nilai per batch
buffer u8 = 256 nilai per batch
mode detail: nonaktifKeluaran memperlihatkan dispatch yang bekerja pada kedua proc. Pemanggilan deskripsi_tipe(int), deskripsi_tipe(f32), dan deskripsi_tipe(string) mengembalikan label yang berbeda-beda dari satu deklarasi yang sama, sedangkan deskripsi_tipe(bool) jatuh ke cabang else karena tidak ada cabang yang cocok. Kapasitas buffer turut mengikuti ukuran tipe, yaitu 64 nilai untuk f64 yang berukuran 8 byte, 128 nilai untuk f32 yang berukuran 4 byte, dan 256 nilai untuk u8 yang berukuran 1 byte. Setiap pemanggilan menghasilkan instansi yang tubuhnya sudah disusun ulang saat kompilasi, tanpa penilaian kondisi apa pun ketika program berjalan.
Konfigurasi saat kompilasi dengan #config#
Direktif #config mendeklarasikan konstanta yang nilainya dapat ditimpa dari baris perintah melalui bendera -define. Deklarasi MODE_DETAIL :: #config(MODE_DETAIL, false) pada program utuh menetapkan nilai bawaan false, sedangkan pemanggilan compiler dengan -define:MODE_DETAIL=true menggantinya dengan true. Nilai define dapat berupa boolean, bilangan bulat, atau string. Konstanta yang dihasilkan memenuhi syarat kondisi when, sehingga blok yang bergantung padanya dikompilasi atau dibuang seluruhnya sebelum program dibangun. Nama di dalam #config juga boleh berbeda dari nama konstantanya, sehingga define pada baris perintah dan konstanta di dalam kode dapat diberi nama masing-masing.
Menjalankan program yang sama dengan bendera tersebut mengubah baris terakhir keluarannya, sedangkan baris lainnya tetap identik karena kode lain tidak menyentuh konstanta itu.
# jalankan program dengan menimpa konstanta konfigurasi MODE_DETAIL
odin run main.odin -file -define:MODE_DETAIL=trueint : bilangan bulat bertanda
f32 : bilangan pecahan presisi tunggal
string : rangkaian karakter
bool : tipe lain
buffer f64 = 64 nilai per batch
buffer f32 = 128 nilai per batch
buffer u8 = 256 nilai per batch
mode detail: aktifSeluruh perbedaan keluaran berada pada baris mode detail, dan programnya tidak menerima argumen apa pun pada saat berjalan. Penetapan nilainya selesai ketika compiler dijalankan, sehingga biner hasil -define:MODE_DETAIL=true berbeda dari biner tanpa bendera tersebut. Pola ini lazim dipakai untuk menyertakan log debug, memilih varian pustaka, atau menetapkan parameter build yang berlaku di seluruh proyek.
Kesalahan umum#
Kesalahan pertama adalah menilai kondisi when terhadap nilai yang baru diketahui saat program berjalan. Kondisi when batas > 64 yang menyertakan variabel batas ditolak compiler, dan bentuk pesannya bergantung pada letak variabel tersebut. Variabel yang dideklarasikan di dalam proc dilaporkan Undeclared name: batas, sedangkan variabel tingkat paket dilaporkan Non-constant condition in 'when' statement. Perbaikannya berupa penempatan keputusan pada waktu yang tepat, yaitu menggunakan if apabila keputusan memang harus menunggu nilai saat program berjalan, atau menjadikan kondisinya konstanta melalui parameter polimorfik seperti $N: int maupun konstanta yang dideklarasikan dengan ::.
Kesalahan kedua adalah menulis proc bertipe kembalian yang cabang when tidak mencakup semua tipe. Proc bernama label yang tubuhnya hanya memuat when T == int { return "bilangan bulat" } tanpa else ditolak compiler dengan pesan Missing return statement at the end of the procedure 'label', karena terdapat instansi yang tidak memiliki jalur kembali. Perbaikannya berupa penambahan cabang else yang mengembalikan nilai, atau pemanggilan panic pada cabang terakhir untuk menyatakan bahwa tipe tersebut tidak didukung.
Kesalahan ketiga adalah menggunakan if untuk dispatch tipe yang kodenya tidak sah pada semua instansi. Cabang if tetap diperiksa tipe untuk setiap instansi, sehingga pemanggilan dengan tipe lain menghasilkan error konversi seperti '1.5' truncated to 'int', got 3.0/2. Perbaikannya berupa penggantian if dengan when, sebagaimana dibahas pada bagian Dispatch tipe parameter di dalam proc parapoly.
Ringkasan#
- Pernyataan
whenmenilai kondisinya saat kompilasi dan hanya mengompilasi cabang yang dipenuhi, sehingga cabang yang terbuang tidak mencapai pemeriksaan tipe maupun biner. - Di dalam proc parapoly,
when T == intmenyusun tubuh proc per instansi tipe, dan rantaielse whenmenambahkan tipe pilihan berikutnya sebelum cabangelsemencakup sisanya. - Pernyataan
ifmengompilasi seluruh cabangnya untuk setiap instansi, sehingga kode yang hanya sah untuk tipe tertentu wajib dibungkuswhen, bukanif. - Direktif
#configbeserta bendera-define:nama=nilaimenetapkan konstanta dari baris perintah dengan nilai boolean, bilangan bulat, atau string, dan konstanta itu memenuhi syarat kondisiwhen.
Referensi#
- Overview — Odin Documentation — Bagian when statements, Command-line defines, dan Parametric polymorphism menetapkan perilaku kondisi saat kompilasi, konstanta bawaan compiler, serta dispatch tipe di dalam proc parapoly.
- Repositori Odin — odin-lang/Odin
— Kode sumber pustaka core, termasuk core:strconv yang menerapkan
whenuntuk memilih konstanta per tipe.