Banyak operasi tidak selalu menghasilkan nilai, misalnya pencarian kunci hash yang ternyata tidak ada. Crystal memodelkan ketiadaan nilai sebagai tipe Nil dan menggabungkannya dengan tipe kemungkinan lain menjadi union type, yaitu tipe gabungan yang ditulis dengan tanda |. Topik ini membahas bentuk union yang memuat Nil, bukti bahwa compiler menegakkan union tersebut saat compile, dan pengecilan tipe yang membuat pemeriksaannya tetap ringkas.
Nil sebagai tipe#
Nil adalah tipe yang hanya memiliki satu nilai, yaitu nil, dan nil ditulis sebagai kata kunci yang menyatakan ketiadaan nilai. Variabel yang diisi nil bertipe Nil, dan metode nil? mengembalikan true untuk nilai tersebut serta false untuk nilai lain, sehingga pemeriksaan eksplisit dapat dilakukan di mana pun.
kosong = nil
p typeof(kosong)
p kosong.nil?Nil
trueTipe yang dilaporkan typeof adalah Nil, bukan tipe lain yang mirip, karena nil memang satu-satunya nilai milik tipe itu. Metode nil? pada contoh mengembalikan true sesuai harapan, dan pemanggilan yang sama pada nilai apa pun selain nil mengembalikan false. Tipe Nil juga menjadi tipe balik metode yang tidak membawa nilai kembali, sehingga variabel yang menampung hasil pemanggilan seperti puts bertipe Nil juga.
Union yang memuat Nil#
Union type menggabungkan beberapa kemungkinan tipe menjadi satu, dan bentuknya muncul sendiri ketika compiler menyimpulkan sebuah nilai yang asalnya lebih dari satu tipe. Ekspresi ternary yang satu cabangnya Int32 dan cabang lainnya nil menghasilkan gabungan keduanya, yaitu Int32 | Nil. Program berikut menunjukkan penyimpulan tersebut pada dua sumber yang umum.
# Dua sumber union yang umum
kondisi = 7 > 3
x = kondisi ? 3 : nil
p typeof(x)
p x
kamus = {"buku" => 7}
hasil = kamus["pensil"]?
p typeof(hasil)
p hasilJalankan berkasnya dengan perintah berikut.
# jalankan program union-dasar.cr
crystal run union-dasar.cr(Int32 | Nil)
3
(Int32 | Nil)
nilVariabel kondisi hanya diketahui compiler sebagai Bool, sehingga kedua cabang ternary tetap dihitung dan tipenya digabung menjadi Int32 | Nil. Pemanggilan kamus["pensil"]? menghasilkan union yang sama karena kunci yang dicari memang mungkin tidak ada, dan pada contoh ini hasilnya memang nil. Pola yang sama berlaku pada kontainer yang isinya tidak seragam, karena Nil ikut menjadi anggota tipenya tanpa perlakuan khusus.
baris = [1, nil, "dua"]
p typeof(baris)
p baris[1]Array(Int32 | String | Nil)
nilTipe Array(Int32 | String | Nil) menyatakan bahwa setiap elemen dapat berupa Int32, String, atau Nil. Nil dengan demikian menjadi anggota union yang setara dengan tipe lainnya, bukan penanda khusus yang diperlakukan berbeda oleh sistem tipe.
Compiler menegakkan union saat compile#
Variabel bertipe Int32 | Nil tidak boleh langsung digunakan pada operasi yang hanya dimiliki Int32, karena tidak semua anggota union menjamin operasi itu tersedia. Penegakan ini terjadi saat compile, sehingga program yang melanggar tidak pernah dijalankan. Program berikut memanggil penjumlahan pada x yang tipenya masih union.
# Program ini ditolak compiler
kondisi = 7 > 3
x = kondisi ? 3 : nil
p x + 1Jalankan berkasnya dengan perintah berikut, lalu amati pesan errornya.
# jalankan program gagal-nil.cr
crystal run gagal-nil.crShowing last frame. Use --error-trace for full trace.
In gagal-nil.cr:5:5
5 | p x + 1
^
Error: undefined method '+' for Nil (compile-time type is (Int32 | Nil))Pesan error menyebut compile-time type is (Int32 | Nil), yang berarti compiler mengetahui tipe lengkap variabel tersebut dan menolak pemanggilan karena anggota Nil tidak memiliki metode +. Pada bahasa yang pemeriksaannya ditunda sampai program berjalan, kegagalan semacam ini baru terlihat saat eksekusi, sedangkan compiler Crystal menangkapnya sebelum program sempat dijalankan.
Pengecilan tipe dengan if#
Pemeriksaan kebenaran if x sekaligus mengecilkan tipe x di dalam bloknya, teknik yang disebut pengecilan tipe (type narrowing). Apabila kondisinya benar, x pasti bukan nil, sehingga tipenya tinggal Int32 dan operasi apa pun milik Int32 menjadi sah. Apabila kondisinya salah, x pasti nil.
# if x mengecilkan tipe x di dalam bloknya
kondisi = 7 > 3
x = kondisi ? 3 : nil
if x
p x + 1
else
p "x bernilai nil"
end4Di dalam blok if, penjumlahan x + 1 diterima tanpa konversi tipe apa pun karena compiler telah mengecilkan tipenya menjadi Int32. Kondisi pada contoh ini benar, sehingga hasilnya 4, tetapi mekanisme yang sama berlaku apabila kondisinya salah dan cabang else yang berjalan.
kondisi = 3 > 5
x = kondisi ? 3 : nil
if x
p x + 1
else
p typeof(x)
endNilCabang else melaporkan Nil karena di sanalah satu-satunya kemungkinan x. Bentuk unless x menerapkan pengecilan yang sama dengan urutan terbalik, yaitu tubuhnya mengecilkan x menjadi Nil dan cabang else-nya mengecilkan x menjadi Int32. Pengecilan juga bekerja pada bentuk postfix, sehingga pemeriksaan dan operasinya dapat ditulis satu baris.
kondisi = 7 > 3
x = kondisi ? 3 : nil
p x + 1 if x4Kesalahan umum#
Mengoperasikan hasil yang mungkin nil tanpa pemeriksaan. Kebiasaan ini ditolak compiler sebagaimana ditunjukkan pada bagian Compiler menegakkan union saat compile. Selain memeriksa dengan if atau unless, nilai cadangan dapat disediakan dengan operator ||, yaitu nilai kedua digunakan apabila nilai pertama bernilai nil atau false.
kamus = {"buku" => 7}
stok = kamus["pensil"]? || 0
p stok0Mengira pengecilan berlaku di luar blok pemeriksaannya. Pengecilan tipe mengikuti alur program, sehingga di luar blok if yang mengecilkannya, tipe x kembali menjadi Int32 | Nil. Pindahkan operasi yang bergantung pada pengecilan itu ke dalam blok if, atau tampung hasil operasinya ke variabel baru di dalam blok.
Ringkasan#
- Nil adalah tipe dengan satu nilai, yaitu
nil, dan menjadi anggota union yang setara dengan tipe lainnya. - Ekspresi yang cabangnya memuat
nilmenghasilkan union sepertiInt32 | Nil, dan[]?mengembalikan union serupa karena kuncinya mungkin tidak ada. - Compiler menolak pemanggilan metode yang tidak dimiliki seluruh anggota union, sehingga kekurangan nilai tertangkap saat compile dan bukan saat program berjalan.
if xmengecilkan tipe di dalam bloknya, bentuk postfix menerapkan pengecilan yang sama, sedangkan||menyediakan nilai cadangan apabilanilditemukan.
Referensi#
- Referensi bahasa Crystal — Crystal Language Reference — Dokumentasi union type, nilai nil, serta if dan truthiness yang digunakan pengecilan tipe.
- Dokumentasi API Crystal 1.22.1 — Dokumentasi API Crystal
— Rincian tipe Nil dan metode
nil?yang digunakan pemeriksaan eksplisit.