case dan in

Pencocokan pola (pattern matching) dengan destructuring pada pernyataan case dan in.

Daftar isi

Pernyataan case memilih satu cabang eksekusi dengan mencocokkan sebuah nilai terhadap daftar pola secara berurutan. Crystal menyediakan dua bentuknya, yaitu case ... when untuk pencocokan nilai bebas dan case ... in untuk pencocokan pola (pattern matching) yang kelengkapannya diperiksa compiler. Topik ini membahas kedua bentuk tersebut, pola yang didukung setiap bentuk, pengikatan elemen tuple dengan destructuring, serta perbedaan perilaku keduanya. Semua contoh dijalankan dengan Crystal 1.22.1.

Pencabangan case dengan when#

Cabang when menerima ekspresi apa pun sebagai polanya. Subjek case dievaluasi sekali, lalu dicocokkan ke setiap cabang dari atas ke bawah, dan hanya cabang pertama yang cocok yang dijalankan. Rentang dapat digunakan langsung sebagai pola, sementara beberapa nilai yang diperlakukan setara ditulis dipisah koma pada satu cabang. Cabang else menampung nilai yang tidak cocok dengan cabang mana pun.

crystal Contoh: rentang dan beberapa nilai pada when
kode = 7
kategori = case kode
           when 1..3
             "rendah"
           when 4..9
             "sedang"
           else
             "tinggi"
           end
puts kategori

warna = "hijau"
grup = case warna
       when "merah", "kuning"
         "hangat"
       when "hijau", "biru"
         "dingin"
       else
         "lain"
       end
puts grup
text Keluaran
sedang
dingin

Pada pencocokan pertama, nilai 7 tidak termasuk rentang 1..3 tetapi termasuk 4..9, sehingga kategori bernilai “sedang”. Pada pencocokan kedua, string “hijau” cocok dengan salah satu nilai pada cabang keduanya. Urutan cabang menentukan hasil ketika satu nilai masuk dalam beberapa pola, sehingga pola yang paling spesifik ditulis lebih dahulu.

Pencocokan tipe dengan when#

Objek tipe juga dapat digunakan sebagai pola when. Pencocokan tipe lazim diterapkan terhadap tipe union, yaitu gabungan beberapa kemungkinan tipe pada satu variabel. Di dalam cabang yang cocok, compiler menyempitkan tipe subjeknya, sehingga metode dan operator milik tipe tersebut boleh dipanggil tanpa konversi.

crystal Contoh: pencocokan tipe dengan when
def deskripsikan(nilai : Int32 | String) : String
  case nilai
  when Int32
    "bilangan bulat #{nilai + 1}"
  when String
    "teks #{nilai.upcase}"
  else
    "lain"
  end
end

puts deskripsikan(7)
puts deskripsikan("halo")
text Keluaran
bilangan bulat 8
teks HALO

Pada cabang when Int32, subjek diketahui bertipe Int32 sehingga nilai + 1 dapat dihitung. Pada cabang when String, metode upcase milik String dapat dipanggil langsung. Pemanggilan metode yang tidak dimiliki tipe cabangnya akan ditolak compiler, sehingga penyempitan tipe ini menjadi jaminan keamanan, bukan sekadar penanda. Fungsi dengan tanda tangan Int32 | String menjadi contoh yang tepat karena setiap cabangnya mengurus satu kemungkinan tipe secara terpisah.

Pernyataan case tanpa subjek#

case dapat ditulis tanpa subjek. Bentuk ini mengevaluasi setiap kondisi pada when sebagai ekspresi boolean dan menjalankan cabang pertama yang bernilai benar. Pola seperti ini praktis ketika keputusan bergantung pada perbandingan yang berbeda-beda dan tidak ada satu nilai tunggal untuk dicocokkan. Kondisi yang melibatkan beberapa variabel sekaligus juga dapat ditulis pada bentuk ini.

crystal Contoh: case tanpa subjek
suhu = 31
pesan = case
        when suhu < 20
          "dingin"
        when suhu < 35
          "sedang"
        else
          "panas"
        end
puts pesan
text Keluaran
sedang

Suhu 31 gagal pada kondisi suhu < 20 dan lulus pada suhu < 35, sehingga pesan yang dihasilkan adalah “sedang”. Cabang else tetap berlaku sebagai penampung kondisi yang tidak terpenuhi. Bentuk tanpa subjek ini setara dengan rantai if ... elsif ... else, tetapi menonjolkan daftar kondisinya secara berurutan. Pembagian kategori terhadap rentang nilai seperti di atas adalah penggunaan bentuk ini yang lazim.

Pencocokan pola dengan in#

Bentuk case ... in adalah pencocokan pola dengan ragam pola yang lebih terbatas tetapi kelengkapannya diperiksa compiler. Pola yang sah berupa konstanta, tipe generik seperti Array(Int32), nilai boolean true atau false, literal nil, dan question method, yaitu pemanggilan metode predikat berawalan titik seperti .merah?. Bentuk ini dirancang agar setiap kemungkinan nilai subjek memiliki cabangnya. Contoh berikut mencakup kedua tipe pada union-nya dengan pola tipe.

crystal Contoh: case in yang mencakup dua tipe
def deskripsikan(nilai : Int32 | String) : String
  case nilai
  in Int32
    "bilangan bulat #{nilai + 1}"
  in String
    "teks #{nilai.upcase}"
  end
end

puts deskripsikan(7)
puts deskripsikan("halo")
text Keluaran
bilangan bulat 8
teks HALO

Sama seperti when, tipe subjek di dalam cabang ikut disempitkan sehingga nilai + 1 dan upcase sah dipanggil. Perbedaannya ada pada pemeriksaan yang dilakukan compiler terhadap cabang-cabangnya, sebagaimana ditunjukkan bagian berikutnya.

Pemeriksaan kelengkapan oleh compiler#

Cabang yang hilang dilaporkan compiler apabila tipe yang bersangkutan benar-benar mengalir ke subjek. Program berikut sama dengan sebelumnya, tetapi cabang in String dihapus dan fungsinya dipanggil dengan String.

crystal Contoh: case in tanpa cabang String
def deskripsikan(nilai : Int32 | String) : String
  case nilai
  in Int32
    "bilangan bulat #{nilai + 1}"
  end
end

puts deskripsikan("halo")
text Keluaran
Showing last frame. Use --error-trace for full trace.

In error_tidak_lengkap.cr:2:3

 2 | case nilai
     ^
Error: case is not exhaustive.

Missing types:
 - String

Pesan case is not exhaustive menyatakan tipe yang belum tercakup, yaitu String. Pemeriksaan ini mengikuti tipe yang benar-benar digunakan, karena fungsi yang hanya pernah dipanggil dengan Int32 tidak menuntut cabang String. Untuk enum, pemeriksaannya diterapkan sepenuhnya tanpa melihat pemanggilannya. Program berikut mencakup kedua anggota enum dengan pola question method.

crystal Contoh: enum lengkap dengan question method
enum Warna
  Merah
  Hijau
end

def nama(w : Warna) : String
  case w
  in .merah?
    "merah"
  in .hijau?
    "hijau"
  end
end

puts nama(Warna::Merah)
puts nama(Warna::Hijau)
text Keluaran
merah
hijau

Apabila salah satu anggota enum tidak tercakup, compiler menolak programnya dengan pesan case is not exhaustive for enum Warna. yang lalu mencantumkan anggota yang hilang, yaitu Hijau. Pernyataan else juga tidak diperbolehkan pada bentuk in dan ditolak dengan pesan exhaustive case (case ... in) doesn't allow an 'else'. Kedua pesan ini memperlihatkan sifat kelengkapan yang menjadi ciri bentuk in.

Destructuring tuple dan pola tipe#

Elemen tuple dapat diikat ke variabel dengan destructuring, yaitu penulisan beberapa variabel sekaligus pada sisi kiri penugasan. Pencocokan struktur tuple juga dapat dilakukan pada when dengan pola tuple literal. Keduanya digunakan bersama pada program berikut.

crystal Contoh: destructuring assignment dan pola tuple pada when
pasangan = {1, "satu"}

a, b = pasangan
puts a + 1
puts b

case pasangan
when {1, "satu"}
  puts "pasangan cocok"
end
text Keluaran
2
satu
pasangan cocok

Pengikatan a, b = pasangan menarik elemen pertama ke a dan elemen kedua ke b, sedangkan pola {1, "satu"} pada when mencocokkan struktur dan nilai tuple sekaligus. Bentuk pengikatan nama ini tidak tersedia pada in. Pola yang berisi variabel baru ditolak compiler dengan pesan yang menjelaskan bentuk pola yang sah.

crystal Contoh: pengikatan nama pada pola in
pasangan = {1, "satu"}
case pasangan
in {a, b}
  puts a + 1
  puts b
end
text Keluaran
In error_pola_tuple.cr:3:4

 3 | in {a, b}
        ^
Error: expression of exhaustive case (case ... in) must be a constant (like `IO::Memory`), a generic (like `Array(Int32)`), a bool literal (true or false), a nil literal (nil) or a question method (like `.red?`)

Struktur tuple tetap dapat dicocokkan pada in melalui pola tipe generik, yaitu penulisan tipe tuple secara lengkap. Pola Tuple(Int32, String) mencocokkan tuple dua elemen dengan tipe yang sama persis, dan pola serupa tersedia untuk NamedTuple melalui NamedTuple(nama: String, umur: Int32).

crystal Contoh: pola tipe Tuple(Int32, String)
pasangan = {1, "satu"}
case pasangan
in Tuple(Int32, String)
  puts "tuple Int32 dan String"
end
text Keluaran
tuple Int32 dan String

Perbedaan when dan in#

Perbedaan kedua bentuk terletak pada pemeriksaan compiler dan ragam pola yang diterima. Tabel berikut merangkumnya.

Aspekwhenin
Pemeriksaan kelengkapan cabangTidak dilakukanDilakukan menurut tipe yang mengalir, anggota enum, dan Bool
Cabang elseTersediaDitolak compiler
Ragam polaEkspresi apa pun, termasuk rentang, beberapa nilai, dan tuple literalKonstanta, tipe generik, true, false, nil, dan question method
Penyempitan tipe di dalam cabangDilakukanDilakukan

Pada praktiknya, when digunakan untuk pencocokan nilai yang bebas, sedangkan in digunakan ketika setiap kemungkinan ingin dijamin tercakup. Fungsi berikut memperlihatkan case ... in yang mencakup tiga tipe union sekaligus dengan tiga pola tipe.

crystal deskripsi_nilai.cr
def deskripsikan(nilai : Int32 | String | Array(Int32)) : String
  case nilai
  in Int32
    "bilangan bulat"
  in String
    "teks"
  in Array(Int32)
    "larik bilangan bulat"
  end
end

puts deskripsikan(7)
puts deskripsikan("halo")
puts deskripsikan([1, 2, 3])
text Keluaran
bilangan bulat
teks
larik bilangan bulat

Tiga pola tipe menutup tiga kemungkinan tipe pada tanda tangan fungsi. Pola ketiga sekaligus menunjukkan bahwa pola tipe generik menerima parameter tipenya juga, sehingga Array(Int32) dapat dipisahkan dari array dengan tipe elemen lain. Apabila satu pola dihapus dan fungsi dipanggil dengan tipe tersebut, compiler menolak programnya seperti pada contoh bagian Pemeriksaan kelengkapan oleh compiler. Struktur ini membuat pergantian tanda tangan fungsi langsung menuntut pembaruan daftar cabangnya.

Ringkasan#

  • case ... when mencocokkan nilai, rentang, beberapa nilai sekaligus, tipe, dan tuple literal, tanpa pemeriksaan kelengkapan dan dengan else tersedia.
  • case ... in menerima pola terbatas berupa konstanta, tipe generik, true, false, nil, dan question method, serta menolak cabang else.
  • Compiler menuntut cabang untuk tipe yang mengalir ke subjek in, dan untuk enum menuntut semua anggota tercakup.
  • Elemen tuple diikat dengan destructuring assignment, sedangkan struktur tuple pada in dicocokkan dengan pola tipe generik seperti Tuple(Int32, String).

Referensi#