Bir Olasılıklar Dizisi: Ruby Model Eşleştirme Kılavuzu

Yayınlanan: 2022-03-11

Desen 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.

Ruby Model Eşleştirme Animasyonu: Dizileri Eşleştirme

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.

Ruby Model Eşleştirme Animasyonu: Dizileri Eşleştirme

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:

  1. Kullanıcı adı varsa, kullanıcı adını döndürün.
  2. Takma ad, ad ve soyadı varsa, takma adı, adı ve ardından soyadını döndürün.
  3. Takma ad yoksa, ancak ad ve soyadı varsa, önce adı ve ardından soyadını döndürün.
  4. 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.