Serangkaian Kemungkinan: Panduan untuk Pencocokan Pola Ruby
Diterbitkan: 2022-03-11Pencocokan pola adalah fitur besar baru yang datang ke Ruby 2.7. Itu telah dikomit ke trunk sehingga siapa pun yang tertarik dapat menginstal Ruby 2.7.0-dev dan memeriksanya. Harap diingat bahwa tidak satu pun dari ini yang diselesaikan dan tim pengembang sedang mencari umpan balik sehingga jika Anda memilikinya, Anda dapat memberi tahu pembuat komitmen sebelum fitur tersebut benar-benar keluar.
Saya harap Anda akan mengerti apa itu pencocokan pola dan bagaimana menggunakannya di Ruby setelah membaca artikel ini.
Apa itu Pencocokan Pola?
Pencocokan pola adalah fitur yang umum ditemukan dalam bahasa pemrograman fungsional. Menurut dokumentasi Scala, pencocokan pola adalah “mekanisme untuk memeriksa nilai terhadap suatu pola. Pertandingan yang sukses juga dapat mendekonstruksi nilai menjadi bagian-bagian penyusunnya.”
Ini tidak menjadi bingung dengan Regex, pencocokan string, atau pengenalan pola. Pencocokan pola tidak ada hubungannya dengan string, melainkan struktur data. Pertama kali saya menemukan pencocokan pola sekitar dua tahun lalu ketika saya mencoba Elixir. Saya sedang mempelajari Elixir dan mencoba memecahkan algoritma dengannya. Saya membandingkan solusi saya dengan yang lain dan menyadari bahwa mereka menggunakan pencocokan pola, yang membuat kode mereka jauh lebih ringkas dan lebih mudah dibaca.
Karena itu, pencocokan pola sangat berkesan bagi saya. Seperti inilah pencocokan pola di Elixir:
[a, b, c] = [:hello, "world", 42] a #=> :hello b #=> "world" c #=> 42
Contoh di atas terlihat sangat mirip dengan beberapa tugas di Ruby. Namun, ini lebih dari itu. Itu juga memeriksa apakah nilainya cocok atau tidak:
[a, b, 42] = [:hello, "world", 42] a #=> :hello b #=> "world"
Dalam contoh di atas, angka 42 di sisi kiri bukanlah variabel yang ditugaskan. Ini adalah nilai untuk memeriksa bahwa elemen yang sama dalam indeks tertentu cocok dengan yang ada di sisi kanan.
[a, b, 88] = [:hello, "world", 42] ** (MatchError) no match of right hand side value
Dalam contoh ini, alih-alih nilai yang ditetapkan, MatchError
dimunculkan sebagai gantinya. Ini karena angka 88 tidak cocok dengan angka 42.
Ini juga berfungsi dengan peta (yang mirip dengan hash di Ruby):
%{"name": "Zote", "title": title } = %{"name": "Zote", "title": "The mighty"} title #=> The mighty
Contoh di atas memeriksa bahwa nilai name
kunci adalah Zote
, dan mengikat nilai title
variabel.
Konsep ini bekerja sangat baik ketika struktur datanya kompleks. Anda dapat menetapkan variabel Anda dan memeriksa nilai atau tipe semua dalam satu baris.
Selain itu, Ini juga memungkinkan bahasa yang diketik secara dinamis seperti Elixir untuk memiliki metode yang berlebihan:
def process(%{"animal" => animal}) do IO.puts("The animal is: #{animal}") end def process(%{"plant" => plant}) do IO.puts("The plant is: #{plant}") end def process(%{"person" => person}) do IO.puts("The person is: #{person}") end
Bergantung pada kunci hash argumen, metode yang berbeda dapat dieksekusi.
Mudah-mudahan, itu menunjukkan betapa kuatnya pencocokan pola. Ada banyak upaya untuk membawa pencocokan pola ke dalam Ruby dengan permata seperti noaidi, qo, dan egison-ruby.
Ruby 2.7 juga memiliki implementasinya sendiri yang tidak jauh berbeda dengan permata ini, dan inilah yang sedang dilakukan saat ini.
Sintaks Pencocokan Pola Ruby
Pencocokan pola di Ruby dilakukan melalui pernyataan case
. Namun, alih-alih menggunakan when
yang biasa, kata kunci in
digunakan sebagai gantinya. Ini juga mendukung penggunaan pernyataan if
atau unless
:
case [variable or expression] in [pattern] ... in [pattern] if [expression] ... else ... end
Pernyataan kasus dapat menerima variabel atau ekspresi dan ini akan dicocokkan dengan pola yang disediakan dalam klausa in . Jika atau kecuali pernyataan juga dapat diberikan setelah pola. Pemeriksaan kesetaraan di sini juga menggunakan ===
seperti pernyataan kasus normal. Ini berarti Anda dapat mencocokkan subset dan instance dari kelas. Berikut adalah contoh bagaimana Anda menggunakannya:
Array yang Cocok
translation = ['th', 'เต้', 'ja', 'テイ'] case translation in ['th', orig_text, 'en', trans_text] puts "English translation: #{orig_text} => #{trans_text}" in ['th', orig_text, 'ja', trans_text] # this will get executed puts "Japanese translation: #{orig_text} => #{trans_text}" end
Dalam contoh di atas, translation
variabel dicocokkan dengan dua pola:
['th', orig_text, 'en', trans_text]
dan ['th', orig_text, 'ja', trans_text]
. Apa yang dilakukan adalah memeriksa apakah nilai dalam pola cocok dengan nilai dalam variabel translation
di setiap indeks. Jika nilainya cocok, ia menetapkan nilai dalam variabel translation
ke variabel dalam pola di setiap indeks.
Pencocokan Hash
translation = {orig_lang: 'th', trans_lang: 'en', orig_txt: 'เต้', trans_txt: 'tae' } case translation in {orig_lang: 'th', trans_lang: 'en', orig_txt: orig_txt, trans_txt: trans_txt} puts "#{orig_txt} => #{trans_txt}" end
Pada contoh di atas, variabel translation
sekarang menjadi hash. Itu dicocokkan dengan hash lain dalam klausa in
. Apa yang terjadi adalah bahwa pernyataan kasus memeriksa apakah semua kunci dalam pola cocok dengan kunci dalam variabel translation
. Itu juga memeriksa bahwa semua nilai untuk setiap kunci cocok. Kemudian memberikan nilai ke variabel dalam hash.
Subset yang cocok
Pemeriksaan kualitas yang digunakan dalam pencocokan pola mengikuti logika ===
.
Beberapa Pola
-
|
dapat digunakan untuk mendefinisikan beberapa pola untuk satu blok.
translation = ['th', 'เต้', 'ja', 'テイ'] case array in {orig_lang: 'th', trans_lang: 'ja', orig_txt: orig_txt, trans_txt: trans_txt} | ['th', orig_text, 'ja', trans_text] puts orig_text #=> เต้ puts trans_text #=> テイend
Dalam contoh di atas, variabel translation
cocok dengan {orig_lang: 'th', trans_lang: 'ja', orig_txt: orig_txt, trans_txt: trans_txt}
dan hash ['th', orig_text, 'ja', trans_text]
Himpunan.

Ini berguna ketika Anda memiliki tipe struktur data yang sedikit berbeda yang mewakili hal yang sama dan Anda ingin kedua struktur data tersebut mengeksekusi blok kode yang sama.
Tugas Panah
Dalam hal ini, =>
dapat digunakan untuk menetapkan nilai yang cocok ke variabel.
case ['I am a string', 10] in [Integer, Integer] => a # not reached in [String, Integer] => b puts b #=> ['I am a string', 10] end
Ini berguna ketika Anda ingin memeriksa nilai di dalam struktur data tetapi juga mengikat nilai-nilai ini ke variabel.
Pin Operator
Di sini, operator pin mencegah variabel agar tidak dipindahkan.
case [1,2,2] in [a,a,a] puts a #=> 2 end
Dalam contoh di atas, variabel a dalam pola dicocokkan dengan 1, 2, dan kemudian 2. Ini akan ditugaskan ke 1, lalu 2, lalu ke 2. Ini bukan situasi yang ideal jika Anda ingin memeriksa bahwa semua nilai dalam array adalah sama.
case [1,2,2] in [a,^a,^a] # not reached in [a,b,^b] puts a #=> 1 puts b #=> 2 end
Ketika operator pin digunakan, ia mengevaluasi variabel alih-alih menugaskannya kembali. Pada contoh di atas, [1,2,2] tidak cocok dengan [a,^a,^a] karena pada indeks pertama, a ditetapkan ke 1. Pada indeks kedua dan ketiga, a dievaluasi menjadi 1, tetapi dicocokkan dengan 2.
Namun [a,b,^b] cocok dengan [1,2,2] karena a ditetapkan ke 1 di indeks pertama, b ditugaskan ke 2 di indeks kedua, lalu ^b, yang sekarang 2, dicocokkan dengan 2 di indeks ketiga sehingga lolos.
a = 1 case [2,2] in [^a,^a] #=> not reached in [b,^b] puts b #=> 2 end
Variabel dari luar pernyataan kasus juga dapat digunakan seperti yang ditunjukkan pada contoh di atas.
Garis bawah ( _
) Operator
Garis bawah ( _
) digunakan untuk mengabaikan nilai. Mari kita lihat dalam beberapa contoh:
case ['this will be ignored',2] in [_,a] puts a #=> 2 end
case ['a',2] in [_,a] => b puts a #=> 2 Puts b #=> ['a',2] end
Dalam dua contoh di atas, nilai apa pun yang cocok dengan _
lolos. Dalam pernyataan kasus kedua, =>
operator menangkap nilai yang telah diabaikan juga.
Gunakan Kasus untuk Pencocokan Pola di Ruby
Bayangkan Anda memiliki data JSON berikut:
{ nickName: 'Tae' realName: {firstName: 'Noppakun', lastName: 'Wongsrinoppakun'} username: 'tae8838' }
Di proyek Ruby Anda, Anda ingin mengurai data ini dan menampilkan nama dengan ketentuan berikut:
- Jika nama pengguna ada, kembalikan nama pengguna.
- Jika nama panggilan, nama depan, dan nama belakang ada, kembalikan nama panggilan, nama depan, dan kemudian nama belakang.
- Jika nama panggilan tidak ada, tetapi nama depan dan belakang ada, kembalikan nama depan dan kemudian nama belakang.
- Jika tidak ada ketentuan yang berlaku, kembalikan "Pengguna Baru."
Ini adalah bagaimana saya akan menulis program ini di Ruby sekarang:
def display_name(name_hash) if name_hash[:username] name_hash[:username] elsif name_hash[:nickname] && name_hash[:realname] && name_hash[:realname][:first] && name_hash[:realname][:last] "#{name_hash[:nickname]} #{name_hash[:realname][:first]} #{name_hash[:realname][:last]}" elsif name_hash[:first] && name_hash[:last] "#{name_hash[:first]} #{name_hash[:last]}" else 'New User' end end
Sekarang, mari kita lihat seperti apa pencocokan pola:
def display_name(name_hash) case name_hash in {username: username} username in {nickname: nickname, realname: {first: first, last: last}} "#{nickname} #{first} #{last}" in {first: first, last: last} "#{first} #{last}" else 'New User' end end
Preferensi sintaks bisa sedikit subjektif, tetapi saya lebih suka versi pencocokan pola. Ini karena pencocokan pola memungkinkan kita untuk menulis hash yang kita harapkan, alih-alih mendeskripsikan dan memeriksa nilai hash. Ini membuatnya lebih mudah untuk memvisualisasikan data apa yang diharapkan:
`{nickname: nickname, realname: {first: first, last: last}}`
Dari pada:
`name_hash[:nickname] && name_hash[:realname] && name_hash[:realname][:first] && name_hash[:realname][:last]`.
Deconstruct dan Deconstruct_keys
Ada dua metode khusus baru yang diperkenalkan di Ruby 2.7: deconstruct
dan deconstruct_keys
. Ketika sebuah instance dari kelas sedang dicocokkan dengan array atau hash, deconstruct
atau deconstruct_keys
dipanggil, masing-masing.
Hasil dari metode ini akan digunakan untuk mencocokkan dengan pola. Berikut ini contohnya:
class Coordinate attr_accessor :x, :y def initialize(x, y) @x = x @y = y end def deconstruct [@x, @y] end def deconstruct_key {x: @x, y: @y} end end
Kode mendefinisikan kelas yang disebut Coordinate
. Ini memiliki x dan y sebagai atributnya. Ini juga memiliki metode deconstruct
dan deconstruct_keys
yang ditentukan.
c = Coordinates.new(32,50) case c in [a,b] pa #=> 32 pb #=> 50 end
Di sini, sebuah instance dari Coordinate
sedang didefinisikan dan pola dicocokkan dengan sebuah array. Apa yang terjadi di sini adalah bahwa Coordinate#deconstruct
dipanggil dan hasilnya digunakan untuk mencocokkan dengan array [a,b]
yang didefinisikan dalam pola.
case c in {x:, y:} px #=> 32 py #=> 50 end
Dalam contoh ini, instance Coordinate
yang sama sedang dicocokkan pola dengan hash. Dalam hal ini, hasil Coordinate#deconstruct_keys
digunakan untuk mencocokkan dengan hash {x: x, y: y}
yang ditentukan dalam pola.
Fitur Eksperimental yang Menyenangkan
Setelah pertama kali mengalami pencocokan pola di Elixir, saya pikir fitur ini mungkin menyertakan metode yang berlebihan dan diimplementasikan dengan sintaks yang hanya membutuhkan satu baris. Namun, Ruby bukanlah bahasa yang dibangun dengan pola pencocokan dalam pikiran, jadi ini bisa dimengerti.
Menggunakan pernyataan kasus mungkin merupakan cara yang sangat ramping untuk mengimplementasikan ini dan juga tidak mempengaruhi kode yang ada (selain dari metode deconstruct
dan deconstruct_keys
). Penggunaan pernyataan kasus sebenarnya mirip dengan penerapan pencocokan pola Scala.
Secara pribadi, saya pikir pencocokan pola adalah fitur baru yang menarik bagi pengembang Ruby. Ini memiliki potensi untuk membuat kode jauh lebih bersih dan membuat Ruby terasa sedikit lebih modern dan menarik. Saya ingin melihat apa yang orang-orang lakukan tentang ini dan bagaimana fitur ini berkembang di masa depan.