Bir Olasılıklar Dizisi: Ruby Model Eşleştirme Kılavuzu
Yayınlanan: 2022-03-11Desen eşleştirme, Ruby 2.7'ye gelen büyük yeni özelliktir. İlgilenen herkesin Ruby 2.7.0-dev'i yükleyip kontrol edebilmesi için bagaja adanmıştır. Lütfen bunların hiçbirinin kesinleşmediğini ve geliştirme ekibinin geri bildirim aradığını unutmayın; bu nedenle, varsa, taahhüt edenlere özellik gerçekten çıkmadan önce bildirebilirsiniz.
Umarım bu makaleyi okuduktan sonra desen eşleştirmenin ne olduğunu ve Ruby'de nasıl kullanılacağını anlayacaksınız.
Desen Eşleştirme Nedir?
Kalıp eşleştirme, işlevsel programlama dillerinde yaygın olarak bulunan bir özelliktir. Scala belgelerine göre, kalıp eşleştirme “bir kalıba karşı bir değeri kontrol etmek için bir mekanizmadır. Başarılı bir eşleşme aynı zamanda bir değeri onu oluşturan parçalara ayırabilir.”
Bu, Regex, dize eşleştirme veya örüntü tanıma ile karıştırılmamalıdır. Model eşleştirmenin dizge ile ilgisi yoktur, bunun yerine veri yapısı vardır. Model eşleştirme ile ilk kez yaklaşık iki yıl önce Elixir'i denediğimde karşılaştım. Elixir'i öğreniyordum ve onunla algoritmaları çözmeye çalışıyordum. Çözümümü başkalarıyla karşılaştırdım ve kodlarını çok daha özlü ve okunması daha kolay hale getiren kalıp eşleştirme kullandıklarını fark ettim.
Bu nedenle desen eşleştirme beni gerçekten etkiledi. Elixir'de desen eşleştirme şuna benzer:
[a, b, c] = [:hello, "world", 42] a #=> :hello b #=> "world" c #=> 42
Yukarıdaki örnek, Ruby'deki çoklu atamaya çok benziyor. Ancak, bundan daha fazlasıdır. Ayrıca değerlerin eşleşip eşleşmediğini de kontrol eder:
[a, b, 42] = [:hello, "world", 42] a #=> :hello b #=> "world"
Yukarıdaki örneklerde sol taraftaki 42 sayısı atanan bir değişken değildir. Belirli bir dizindeki aynı öğenin sağ taraftakiyle eşleşip eşleşmediğini kontrol eden bir değerdir.
[a, b, 88] = [:hello, "world", 42] ** (MatchError) no match of right hand side value
Bu örnekte, atanan değerler yerine MatchError
yükseltilir. Bunun nedeni, 88 sayısının 42 sayısıyla eşleşmemesidir.
Ayrıca haritalarla da çalışır (Ruby'deki karmaya benzer):
%{"name": "Zote", "title": title } = %{"name": "Zote", "title": "The mighty"} title #=> The mighty
Yukarıdaki örnek, anahtar name
değerinin Zote
olup olmadığını kontrol eder ve anahtar title
değerini değişken başlığına bağlar.
Bu kavram, veri yapısı karmaşık olduğunda çok iyi çalışır. Değişkeninizi atayabilir ve değerleri veya türleri tek bir satırda kontrol edebilirsiniz.
Ayrıca, Elixir gibi dinamik olarak yazılmış bir dilin yöntem aşırı yüklemesine sahip olmasına da izin verir:
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
Argümanın karma anahtarına bağlı olarak, farklı yöntemler yürütülür.
Umarım, bu size desen eşleştirmenin ne kadar güçlü olabileceğini gösterir. Noaidi, qo ve egison-ruby gibi değerli taşlarla Ruby'ye desen eşleştirmesi getirmek için birçok girişim var.
Ruby 2.7'nin de bu taşlardan çok farklı olmayan kendi uygulaması vardır ve şu anda bu şekilde yapılmaktadır.
Ruby Model Eşleştirme Sözdizimi
Ruby'de örüntü eşleştirmesi case
deyimi ile yapılır. Ancak, olağan when
kullanmak yerine, bunun yerine in
anahtar sözcüğü kullanılır. Ayrıca if
veya unless
ifadelerinin kullanımını da destekler:
case [variable or expression] in [pattern] ... in [pattern] if [expression] ... else ... end
Case deyimi bir değişkeni veya bir ifadeyi kabul edebilir ve bu, in yan tümcesinde sağlanan kalıplarla eşleştirilecektir. If veya not ifadeleri kalıptan sonra da sağlanabilir. Buradaki eşitlik kontrolü de normal durum ifadesi gibi ===
kullanır. Bu, alt kümeleri ve sınıf örneklerini eşleştirebileceğiniz anlamına gelir. İşte nasıl kullandığınıza dair bir örnek:
Eşleşen Diziler
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
Yukarıdaki örnekte, değişken translation
iki modelle eşleştirilir:
['th', orig_text, 'en', trans_text]
ve ['th', orig_text, 'ja', trans_text]
. Yaptığı şey, kalıptaki değerlerin her bir endeksteki translation
değişkenindeki değerlerle eşleşip eşleşmediğini kontrol etmektir. Değerler eşleşirse, translation
değişkenindeki değerleri her bir endeksteki desendeki değişkenlere atar.
Eşleşen Hash'ler
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
Yukarıdaki örnekte, translation
değişkeni artık bir karmadır. Maddede başka bir karma in
eşleştirilir. Olan, case ifadesinin, modeldeki tüm anahtarların translation
değişkenindeki anahtarlarla eşleşip eşleşmediğini kontrol etmesidir. Ayrıca, her bir anahtar için tüm değerlerin eşleştiğini kontrol eder. Daha sonra değerleri karmadaki değişkene atar.
eşleşen alt kümeler
Model eşleştirmede kullanılan kalite kontrolü, ===
mantığını takip eder.
Çoklu Modeller
-
|
bir blok için birden fazla desen tanımlamak için kullanılabilir.
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
Yukarıdaki örnekte, translation
değişkeni hem {orig_lang: 'th', trans_lang: 'ja', orig_txt: orig_txt, trans_txt: trans_txt}
karma değeri ve ['th', orig_text, 'ja', trans_text]
dizi.

Bu, aynı şeyi temsil eden biraz farklı veri yapılarınız olduğunda ve her iki veri yapısının da aynı kod bloğunu yürütmesini istediğinizde kullanışlıdır.
Ok Atama
Bu durumda, bir değişkene eşleşen değer atamak için =>
kullanılabilir.
case ['I am a string', 10] in [Integer, Integer] => a # not reached in [String, Integer] => b puts b #=> ['I am a string', 10] end
Bu, veri yapısı içindeki değerleri kontrol etmek ve aynı zamanda bu değerleri bir değişkene bağlamak istediğinizde kullanışlıdır.
Pin Operatörü
Burada pin operatörü, değişkenlerin yeniden atanmasını engeller.
case [1,2,2] in [a,a,a] puts a #=> 2 end
Yukarıdaki örnekte, desendeki a değişkeni 1, 2 ve ardından 2 ile eşleştirilir. 1'e, ardından 2'ye ve ardından 2'ye atanacaktır. dizideki değerler aynıdır.
case [1,2,2] in [a,^a,^a] # not reached in [a,b,^b] puts a #=> 1 puts b #=> 2 end
Pin operatörü kullanıldığında, değişkeni yeniden atamak yerine değerlendirir. Yukarıdaki örnekte [1,2,2], [a,^a,^a] ile eşleşmez çünkü birinci dizinde a, 1'e atanır. İkinci ve üçüncüde a, 1 olarak değerlendirilir, ancak 2 ile eşleşir.
Ancak [a,b,^b] [1,2,2] ile eşleşir, çünkü a birinci dizinde 1'e, b ikinci dizinde 2'ye atanır, ardından şimdi 2 olan ^b ile eşleştirilir Üçüncü dizinde 2 böylece geçer.
a = 1 case [2,2] in [^a,^a] #=> not reached in [b,^b] puts b #=> 2 end
Case deyiminin dışından gelen değişkenler de yukarıdaki örnekte gösterildiği gibi kullanılabilir.
Alt çizgi ( _
) Operatörü
Alt çizgi ( _
) değerleri yok saymak için kullanılır. Bir iki örnekte görelim:
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
Yukarıdaki iki örnekte, _
ile eşleşen herhangi bir değer geçer. İkinci durum ifadesinde, =>
operatörü, yoksayılan değeri de yakalar.
Ruby'de Model Eşleştirme İçin Kullanım Örnekleri
Aşağıdaki JSON verilerine sahip olduğunuzu hayal edin:
{ nickName: 'Tae' realName: {firstName: 'Noppakun', lastName: 'Wongsrinoppakun'} username: 'tae8838' }
Ruby projenizde, bu verileri ayrıştırmak ve adı aşağıdaki koşullarla görüntülemek istiyorsunuz:
- Kullanıcı adı varsa, kullanıcı adını döndürün.
- Takma ad, ad ve soyadı varsa, takma adı, adı ve ardından soyadını döndürün.
- Takma ad yoksa, ancak ad ve soyadı varsa, önce adı ve ardından soyadını döndürün.
- Koşullardan hiçbiri geçerli değilse, "Yeni Kullanıcı" döndürün.
Bu programı şu anda Ruby'de şöyle yazardım:
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
Şimdi desen eşleştirme ile nasıl göründüğüne bakalım:
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
Sözdizimi tercihi biraz öznel olabilir, ancak kalıp eşleştirme sürümünü tercih ederim. Bunun nedeni, desen eşleştirmenin, hash değerlerini tanımlamak ve kontrol etmek yerine, beklediğimiz hash'i yazmamıza izin vermesidir. Bu, hangi verilerin bekleneceğini görselleştirmeyi kolaylaştırır:
`{nickname: nickname, realname: {first: first, last: last}}`
Onun yerine:
`name_hash[:nickname] && name_hash[:realname] && name_hash[:realname][:first] && name_hash[:realname][:last]`.
Deconstruct ve Deconstruct_keys
Ruby 2.7'de tanıtılan iki yeni özel yöntem vardır: deconstruct
ve deconstruct_keys
. Bir sınıfın bir örneği bir dizi veya karma ile eşleştirildiğinde, sırasıyla deconstruct
veya deconstruct_keys
çağrılır.
Bu yöntemlerden elde edilen sonuçlar, kalıplarla eşleştirmek için kullanılacaktır. İşte bir örnek:
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
Kod, Coordinate
adlı bir sınıfı tanımlar. Nitelikleri olarak x ve y vardır. Ayrıca deconstruct
ve deconstruct_keys
yöntemleri tanımlanmıştır.
c = Coordinates.new(32,50) case c in [a,b] pa #=> 32 pb #=> 50 end
Burada, bir Coordinate
örneği tanımlanıyor ve bir diziyle desen eşleştiriliyor. Burada olan, Coordinate#deconstruct
çağrılması ve sonucun, modelde tanımlanan [a,b]
dizisiyle eşleşmek için kullanılmasıdır.
case c in {x:, y:} px #=> 32 py #=> 50 end
Bu örnekte, aynı Coordinate
örneği, bir karma ile desen eşleştiriliyor. Bu durumda, Coordinate#deconstruct_keys
sonucu, kalıpta tanımlanan {x: x, y: y}
karma değeriyle eşleşmek için kullanılır.
Heyecan Verici Deneysel Özellik
Elixir'de ilk kez desen eşleştirme deneyimine sahip olduğumdan, bu özelliğin yöntem aşırı yüklemesini içerebileceğini ve yalnızca bir satır gerektiren bir sözdizimi ile uygulanabileceğini düşünmüştüm. Ancak Ruby, kalıp eşleştirme akılda tutularak oluşturulmuş bir dil değildir, bu nedenle bu anlaşılabilir bir durumdur.
Case deyimi kullanmak muhtemelen bunu uygulamanın çok yalın bir yoludur ve aynı zamanda mevcut kodu etkilemez ( deconstruct
ve deconstruct_keys
yöntemleri dışında). Case ifadesinin kullanımı aslında Scala'nın model eşleştirme uygulamasına benzer.
Şahsen, kalıp eşleştirmenin Ruby geliştiricileri için heyecan verici yeni bir özellik olduğunu düşünüyorum. Kodu çok daha temiz hale getirme ve Ruby'yi biraz daha modern ve heyecan verici hissettirme potansiyeline sahiptir. İnsanların bundan ne anladığını ve bu özelliğin gelecekte nasıl gelişeceğini görmeyi çok isterim.