O serie de posibilități: un ghid pentru potrivirea modelelor Ruby
Publicat: 2022-03-11Potrivirea modelelor este noua caracteristică care vine în Ruby 2.7. A fost aplicat trunchiului, astfel încât oricine este interesat să poată instala Ruby 2.7.0-dev și să-l verifice. Vă rugăm să rețineți că niciunul dintre acestea nu este finalizat, iar echipa de dezvoltatori caută feedback, așa că, dacă aveți, puteți anunța cei care commit înainte ca funcția să fie disponibilă.
Sper că veți înțelege ce este potrivirea modelului și cum să o utilizați în Ruby după ce ați citit acest articol.
Ce este potrivirea modelelor?
Potrivirea modelelor este o caracteristică care se găsește în mod obișnuit în limbajele de programare funcționale. Conform documentației Scala, potrivirea modelului este „un mecanism pentru verificarea unei valori față de un model. O potrivire de succes poate, de asemenea, să deconstruiască o valoare în părțile sale constitutive.”
Acest lucru nu trebuie confundat cu Regex, potrivirea șirurilor sau recunoașterea modelelor. Potrivirea modelelor nu are nimic de-a face cu șirul, ci cu structura de date. Prima dată când am întâlnit potrivirea modelelor a fost acum aproximativ doi ani, când am încercat Elixir. Învățam Elixir și încercam să rezolv algoritmi cu el. Mi-am comparat soluția cu altele și mi-am dat seama că foloseau potrivirea modelelor, ceea ce a făcut codul lor mult mai succint și mai ușor de citit.
Din această cauză, potrivirea modelelor m-a impresionat. Iată cum arată potrivirea modelului în Elixir:
[a, b, c] = [:hello, "world", 42] a #=> :hello b #=> "world" c #=> 42
Exemplul de mai sus seamănă foarte mult cu o misiune multiplă în Ruby. Cu toate acestea, este mai mult decât atât. De asemenea, verifică dacă valorile se potrivesc sau nu:
[a, b, 42] = [:hello, "world", 42] a #=> :hello b #=> "world"
În exemplele de mai sus, numărul 42 din partea stângă nu este o variabilă care este atribuită. Este o valoare pentru a verifica dacă același element din acel index particular se potrivește cu cel din partea dreaptă.
[a, b, 88] = [:hello, "world", 42] ** (MatchError) no match of right hand side value
În acest exemplu, în loc să fie atribuite valori, MatchError
este ridicată. Acest lucru se datorează faptului că numărul 88 nu se potrivește cu numărul 42.
Funcționează și cu hărți (care este similar cu hash în Ruby):
%{"name": "Zote", "title": title } = %{"name": "Zote", "title": "The mighty"} title #=> The mighty
Exemplul de mai sus verifică dacă valoarea name
cheii este Zote
și leagă valoarea titlului cheii de title
variabilei.
Acest concept funcționează foarte bine atunci când structura datelor este complexă. Puteți să vă atribuiți variabilele și să verificați valorile sau tipurile, toate într-o singură linie.
În plus, permite, de asemenea, unui limbaj tatat dinamic, cum ar fi Elixir, să aibă supraîncărcare a metodei:
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
În funcție de cheia hash-ului argumentului, sunt executate diferite metode.
Sperăm că asta vă arată cât de puternică poate fi potrivirea modelelor. Există multe încercări de a aduce în Ruby potrivirea modelelor cu pietre prețioase precum noaidi, qo și egison-ruby.
Ruby 2.7 are, de asemenea, propria sa implementare, nu prea diferită de aceste pietre prețioase, și așa se face în prezent.
Sintaxa de potrivire a modelului Ruby
Potrivirea modelelor în Ruby se face printr-o declarație case
. Cu toate acestea, în loc să folosiți obișnuitul when
, este folosit cuvântul cheie in
. De asemenea, acceptă utilizarea declarațiilor if
sau unless
:
case [variable or expression] in [pattern] ... in [pattern] if [expression] ... else ... end
Declarația Case poate accepta o variabilă sau o expresie și aceasta va fi corelată cu modelele furnizate în clauza in . Dacă sau dacă nu pot fi furnizate declarații după model. Verificarea egalității de aici folosește și ===
ca declarația normală case. Aceasta înseamnă că puteți potrivi subseturi și instanțe de clase. Iată un exemplu despre cum îl folosiți:
Matrice de potrivire
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
În exemplul de mai sus, translation
variabilei este corelată cu două modele:
['th', orig_text, 'en', trans_text]
și ['th', orig_text, 'ja', trans_text]
. Ceea ce face este să verifice dacă valorile din model se potrivesc cu valorile din variabila de translation
în fiecare dintre indici. Dacă valorile se potrivesc, acesta atribuie valorile din variabila de translation
variabilelor din model în fiecare dintre indici.
Potrivirea hashurilor
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
În exemplul de mai sus, variabila de translation
este acum un hash. Se potrivește cu un alt hash din clauza in
. Ce se întâmplă este că instrucțiunea case verifică dacă toate cheile din model se potrivesc cu cheile din variabila de translation
. De asemenea, verifică dacă toate valorile pentru fiecare cheie se potrivesc. Apoi atribuie valorile variabilei din hash.
Subseturi de potrivire
Verificarea calității utilizată în potrivirea modelelor urmează logica ===
.
Modele multiple
-
|
poate fi folosit pentru a defini mai multe modele pentru un singur bloc.
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
În exemplul de mai sus, variabila de translation
se potrivește atât cu hash-ul {orig_lang: 'th', trans_lang: 'ja', orig_txt: orig_txt, trans_txt: trans_txt}
, cât și cu ['th', orig_text, 'ja', trans_text]
matrice.

Acest lucru este util atunci când aveți tipuri ușor diferite de structuri de date care reprezintă același lucru și doriți ca ambele structuri de date să execute același bloc de cod.
Atribuire săgeată
În acest caz, =>
poate fi folosit pentru a atribui o valoare potrivită unei variabile.
case ['I am a string', 10] in [Integer, Integer] => a # not reached in [String, Integer] => b puts b #=> ['I am a string', 10] end
Acest lucru este util atunci când doriți să verificați valorile din structura de date, dar și să legați aceste valori la o variabilă.
Operator PIN
Aici, operatorul pin previne reatribuirea variabilelor.
case [1,2,2] in [a,a,a] puts a #=> 2 end
În exemplul de mai sus, variabila a din model este corelată cu 1, 2 și apoi 2. Va fi atribuită la 1, apoi la 2, apoi la 2. Aceasta nu este o situație ideală dacă doriți să verificați că toate valorile din matrice sunt aceleași.
case [1,2,2] in [a,^a,^a] # not reached in [a,b,^b] puts a #=> 1 puts b #=> 2 end
Când este utilizat operatorul pin, acesta evaluează variabila în loc să o reatribuie. În exemplul de mai sus, [1,2,2] nu se potrivește cu [a,^a,^a] deoarece în primul index, a este atribuit la 1. În al doilea și al treilea, a este evaluat ca fiind 1, dar este egalat cu 2.
Cu toate acestea, [a,b,^b] se potrivește cu [1,2,2], deoarece a este atribuit la 1 în primul index, b este atribuit la 2 în al doilea index, apoi ^b, care este acum 2, este asociat cu 2 în al treilea indice deci trece.
a = 1 case [2,2] in [^a,^a] #=> not reached in [b,^b] puts b #=> 2 end
Variabilele din afara instrucțiunii case pot fi, de asemenea, utilizate, așa cum se arată în exemplul de mai sus.
Subliniere ( _
) Operator
Litere de subliniere ( _
) este folosită pentru a ignora valori. Să o vedem în câteva exemple:
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
În cele două exemple de mai sus, orice valoare care se potrivește cu _
trece. În al doilea caz, operatorul =>
captează și valoarea care a fost ignorată.
Cazuri de utilizare pentru potrivirea modelelor în Ruby
Imaginați-vă că aveți următoarele date JSON:
{ nickName: 'Tae' realName: {firstName: 'Noppakun', lastName: 'Wongsrinoppakun'} username: 'tae8838' }
În proiectul dvs. Ruby, doriți să analizați aceste date și să afișați numele cu următoarele condiții:
- Dacă numele de utilizator există, returnați numele de utilizator.
- Dacă porecla, prenumele și numele de familie există, returnați porecla, prenumele și apoi numele de familie.
- Dacă porecla nu există, dar numele și prenumele există, returnați prenumele și apoi numele de familie.
- Dacă nu se aplică niciuna dintre condiții, returnați „Utilizator nou”.
Iată cum aș scrie acest program în Ruby chiar acum:
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
Acum, să vedem cum arată cu potrivirea modelelor:
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
Preferința de sintaxă poate fi puțin subiectivă, dar prefer versiunea de potrivire a modelelor. Acest lucru se datorează faptului că potrivirea modelelor ne permite să scriem hash-ul pe care îl așteptăm, în loc să descriem și să verificăm valorile hash-ului. Acest lucru face mai ușor să vizualizați la ce date să vă așteptați:
`{nickname: nickname, realname: {first: first, last: last}}`
În loc de:
`name_hash[:nickname] && name_hash[:realname] && name_hash[:realname][:first] && name_hash[:realname][:last]`.
Deconstruct și Deconstruct_keys
În Ruby 2.7 sunt introduse două metode speciale noi: deconstruct
și deconstruct_keys
. Atunci când o instanță a unei clase este comparată cu un array sau hash, sunt apelate deconstruct
sau deconstruct_keys
, respectiv.
Rezultatele acestor metode vor fi utilizate pentru a se potrivi cu modele. Iată un exemplu:
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
Codul definește o clasă numită Coordinate
. Are x și y drept atribute. De asemenea, are definite metodele deconstruct
și deconstruct_keys
.
c = Coordinates.new(32,50) case c in [a,b] pa #=> 32 pb #=> 50 end
Aici, o instanță de Coordinate
este definită și modelul se potrivește cu o matrice. Ceea ce se întâmplă aici este că Coordinate#deconstruct
este apelată și rezultatul este folosit pentru a se potrivi cu matricea [a,b]
definită în model.
case c in {x:, y:} px #=> 32 py #=> 50 end
În acest exemplu, aceeași instanță a Coordinate
este corelată cu un model cu un hash. În acest caz, rezultatul Coordinate#deconstruct_keys
este folosit pentru a se potrivi cu hash {x: x, y: y}
definit în model.
O caracteristică experimentală incitantă
După ce am experimentat prima potrivire a modelelor în Elixir, m-am gândit că această caracteristică ar putea include supraîncărcarea metodei și implementată cu o sintaxă care necesită doar o linie. Cu toate acestea, Ruby nu este un limbaj care este construit având în vedere potrivirea modelelor, așa că acest lucru este de înțeles.
Utilizarea unei declarații case este probabil o modalitate foarte simplă de implementare a acestui lucru și, de asemenea, nu afectează codul existent (în afară de metodele deconstruct
și deconstruct_keys
). Utilizarea declarației case este de fapt similară cu cea a implementării de către Scala a potrivirii modelelor.
Personal, cred că potrivirea modelelor este o nouă caracteristică interesantă pentru dezvoltatorii Ruby. Are potențialul de a face codul mult mai curat și de a face pe Ruby să se simtă puțin mai modern și mai interesant. Mi-ar plăcea să văd ce fac oamenii despre asta și cum evoluează această caracteristică în viitor.