Pattern matching (case/in)

Pencocokan pola case/in Ruby 3: destructuring array dan hash beserta penggunaan guard.

Daftar isi

Pattern matching (pencocokan pola) memperluas pencocokan nilai menjadi pencocokan pola terhadap struktur data. Konstruksi case/in yang diperkenalkan pada Ruby 3 dapat membongkar isi array dan hash ke dalam variabel, menyaring cabang dengan syarat tambahan, dan menguji kesetaraan dengan nilai di luar pola. Topik ini membahas pola dasar, destructuring array dan hash, guard, dan pin, lalu menutupnya dengan penggolongan entri antrean yang memadukan seluruh pola tersebut.

Pencocokan pola dengan case/in#

Konstruksi case/in berbentuk seperti case/when, tetapi cabang in memuat pola yang diuji terhadap struktur nilai, bukan sekadar pembanding tunggal. Pola paling sederhana berupa literal atau kelas, sedangkan pola array dan hash memeriksa isi sekaligus mengikatnya ke variabel baru. Contoh berikut menguji sebuah array terhadap pola [Integer, String] dan terhadap pola String.

ruby Pola array dan pola kelas pada case/in
ukuran = [27, "nyaman"]
case ukuran
in [Integer, String]
  puts "Pengukuran lengkap: #{ukuran.join(" ")}"
in String
  puts "Hanya label: #{ukuran}"
end
text Keluaran
Pengukuran lengkap: 27 nyaman

Potongan berikut membalik urutan jenis pada polanya, sehingga tidak ada cabang yang cocok dengan nilainya.

ruby Pola yang tidak cocok memunculkan NoMatchingPatternError
# tidak-dijalankan
case [27, "nyaman"]
in [String, Integer]
  puts "cocok"
end
text Pesan error yang muncul
salah-pola.rb:2:in '<main>': [27, "nyaman"]: String === 27 does not return true (NoMatchingPatternError)

Pesan error menunjukkan nilai yang gagal dicocokkan beserta pembanding yang gagal lebih dulu, dan nama berkasnya menyesuaikan dengan berkas yang dijalankan. Cabang else mencegah error ini dengan menampung nilai yang tidak cocok dengan pola mana pun. Program berikut menggunakan pola lengkap tanpa else karena keempat bentuk datanya sudah tercakup.

ruby status-antrian.rb
# Penggolongan entri antrean tiket dengan pattern matching.
entri = [
  {nomor: 1, nama: "Ali"},
  {nomor: 2, nama: "Budi", prioritas: true},
  [3, "Citra"],
  "selesai"
]

entri.each do |e|
  pesan =
    case e
    in {nomor: Integer => nomor, prioritas: true}
      "Nomor #{nomor} dilayani lebih dulu"
    in {nomor: Integer => nomor}
      "Nomor #{nomor} menunggu"
    in [Integer => nomor, String => nama]
      "Nomor #{nomor} atas nama #{nama} menunggu"
    in "selesai"
      "Antrean ditutup"
    end
  puts pesan
end
text Keluaran
Nomor 1 menunggu
Nomor 2 dilayani lebih dulu
Nomor 3 atas nama Citra menunggu
Antrean ditutup

Urutan pola menentukan hasil sebagaimana pada case/when. Entri pertama tidak membawa kunci prioritas, sehingga jatuh ke pola hash sederhana, sedangkan entri kedua ditangkap pola prioritas yang ditulis lebih dulu. Dua baris terakhir memperlihatkan pola array dan pola literal string yang diuji dengan kesetaraan nilai.

Destructuring array#

Destructuring, yaitu pembongkaran struktur data ke dalam beberapa variabel sekaligus, dilakukan oleh pola array dengan menuliskan bentuk array yang diharapkan di dalam in. Setiap posisi pola dapat berupa kelas, literal, atau nama variabel baru yang langsung terikat nilainya. Tanda bintang menampung sisa elemen ke dalam satu array, sehingga panjang data yang berubah-ubah tetap tertangani.

ruby Pola array dengan penampung sisa elemen
barisan = [1, 2, 3, 4, 5]
case barisan
in [pertama, *tengah, terakhir]
  puts "Pertama #{pertama}, terakhir #{terakhir}, tengah #{tengah.length} elemen"
end
text Keluaran
Pertama 1, terakhir 5, tengah 3 elemen

Pola [pertama, *tengah, terakhir] mengikat elemen pertama dan terakhir ke variabelnya, lalu menampung tiga elemen sisanya ke variabel tengah. Tanda bintang hanya boleh muncul sekali dalam satu pola dan boleh tanpa nama, ditulis * apabila sisanya tidak diperlukan. Pola kosong [] hanya cocok untuk array tanpa isi, dan pola dapat disarangkan seperti [Integer, [String]] untuk data bersarang.

Destructuring hash#

Pola hash ditulis dengan sintaks pembuatan hash dan dicocokkan menurut kuncinya. Hanya kunci yang disebutkan yang diwajibkan ada, sedangkan kunci lain dibiarkan lewat, sehingga pola tetap cocok ketika datanya membawa informasi tambahan. Nilai di belakang kunci berupa pola apa pun, termasuk pola kelas yang diikuti => untuk mengikat nilainya ke variabel.

ruby Pola hash dengan pengikatan nilai bertipe
tiket = {nomor: 12, tujuan: "Loket B", nama: "Ali"}
case tiket
in {nomor: Integer => nomor, tujuan: String => tujuan, **}
  puts "Tiket #{nomor} menuju #{tujuan}"
end
text Keluaran
Tiket 12 menuju Loket B

Bagian Integer => nomor melakukan dua hal sekaligus, yaitu memeriksa bahwa nilai kunci nomor berupa Integer dan mengikatnya ke variabel nomor. Tanda ** di ujung pola menyatakan sisa kunci boleh ada dengan bentuk apa pun. Apabila sisa kunci justru harus ditolak, tuliskan **nil sebagai penggantinya, sehingga hash yang membawa kunci tak dikenal tidak cocok dengan pola tersebut.

Guard dan pin#

Syarat tambahan setelah pola, yang disebut guard, mempersempit cabang tanpa menambah tingkat sarang. Guard ditulis sebagai if di akhir pola dan dinilai setelah pola terikat, sehingga variabel hasil ikatan dapat digunakan di dalamnya. Cabang yang polanya cocok tetapi guardnya palsu dilompati, dan pencocokan berlanjut ke pola berikutnya.

ruby Guard if menyaring cabang menurut nilai hasil ikatan
pengukuran = [38, "tinggi"]
case pengukuran
in [Integer => suhu, String => label] if suhu >= 35
  puts "Peringatan: suhu #{suhu} derajat #{label}"
in [Integer => suhu, String => label]
  puts "Suhu normal: #{suhu} derajat #{label}"
end
text Keluaran
Peringatan: suhu 38 derajat tinggi

Pola pin menggunakan tanda sirkumfleks ^ untuk menguji kesetaraan dengan nilai yang sudah ada di luar pola. Tanpa pin, nama variabel di dalam pola selalu membuat ikatan baru, sehingga target akan menimpa nilai lamanya. Pin mencegah hal itu dengan membandingkan nilai pola dengan variabel luar.

ruby Pin ^ menguji kesetaraan dengan nilai luar
target = 27
case [27, 30]
in [^target, Integer => berikutnya]
  puts "Pengukuran pertama sesuai target, berikutnya #{berikutnya}"
end
text Keluaran
Pengukuran pertama sesuai target, berikutnya 30

Pada contoh di atas, ^target menuntut elemen pertama sama dengan 27, sedangkan berikutnya tetap menjadi ikatan baru. Pin juga dapat membungkus ekspresi dengan tanda kurung, misalnya ^(suhu + 1), sehingga ambang yang dihitung saat program berjalan tetap dapat diuji. Dengan pin, guard, dan destructuring yang dipadukan, satu cabang in dapat menyatakan bentuk, jenis, dan syarat nilai sekaligus.

Ringkasan#

  • case/in mencocokkan pola terhadap struktur data dan mengikat bagian yang cocok ke variabel baru, dengan else sebagai penangkap nilai tak dikenal.
  • Pola array membongkar elemen menurut posisi dengan tanda bintang untuk sisa elemen, sedangkan pola hash membongkar menurut kunci dan membiarkan kunci lain lewat.
  • Guard if menyaring cabang setelah pola terikat, dan pin ^ menguji kesetaraan dengan nilai variabel di luar pola.
  • Tanpa pola yang cocok dan tanpa else, case/in memunculkan NoMatchingPatternError.

Referensi#