Drugie spojrzenie na pattern matching w Ruby

Co nowego w dopasowaniu do wzorca w Ruby?

Gdy pojawiają się nowości w naszym języku programowania czasami jesteśmy z tego powodu zadowolone, a czasami nie. Dziś chciałabym porozmawiać o zmianach, z których ja osobiście bardzo się cieszę. Mam na myśli dopasowanie do wzorca w języku Ruby, czyli pattern matching. Jakiś czas temu napisałam artykuł na temat Pattern Matching-u w języku Ruby. Zachęcam do zapoznania się z nim, ponieważ będę odwoływała się do omawianych tam przykładów. A teraz zanurzmy się jeszcze bardziej w świat dopasowania do wzorca w języku Ruby. Zaczynamy!

1. Jednolinijkowe dopasowanie do wzorca w Ruby

Jest to jedna z tych rzeczy, których brakowało mi w dopasowaniu do wzorca w Ruby, a o istnieniu której nie wiedziałam. Oto jak wyglądają przykłady:

Jednolinijkowy Pattern Matching dla tablicy słownikowej (Hash)
{ foo: 1, bar: 2 } in { foo: f }
 => nil

2.7.1> f
 => 1

lub bez deklaracji zmiennej

{ foo: 1, bar: 2 } in { foo: }
 => nil

2.7.1> foo
 => 1
Jednolinikowy Pattern Matching dla tablicy
[1, 2, 3] in [a, 2, 3]
 => nil

2.7.1> a
 => 1

Uwaga! W Ruby 3.0 zamiast używać słowa kluczowego in dla jednolinikowego dopasowania do wzorca eksperymentalnie będzie można używać =>. Kod będzie wtedy wyglądał następująco:

{ a: '2', b: 5 } => { a: }

Niestety w wersji Ruby 3.0 preview 1, to podejście jeszcze nie działa, więc na jego przetestowanie trzeba będzie trochę poczekać.

2. Pattern matching dla dopasowania tablicy z zadeklarowanym początkiem i końcem

[1, 2, 3, 4, 5, 6] in [first, *middle, last]

2.7.1> first
 => 1

2.7.1> middle
 => [2, 3, 4, 5]

2.7.1> last
 => 6

lub w przypadku gdy nie interesuje nas środkowa część tablicy

[1, 2, 3, 4, 5, 6] in [first, *, last]

2.7.1> first
 => 1

2.7.1> last
 => 6

3. Dokładne dopasowanie dla tablicy słownikowej (Hash)

Jak wspominałam w moim poprzednim artykule na ten temat, dokładne dopasowanie dla tablic i tablic słownikowych różni się od siebie. W skrócie sprawa wygląda następująco.

Gdy szukamy dokładnego dopasowanie do wzorca dla tablicy i tego dopasowania nie ma, dostajemy błąd.

case [1, 2]
in [1]
  :no_match
end

Traceback (most recent call last):
        4: from /home/agnieszka/.rvm/rubies/ruby-2.7.1/bin/irb:23:in `<main>'
        3: from /home/agnieszka/.rvm/rubies/ruby-2.7.1/bin/irb:23:in `load'
        2: from /home/agnieszka/.rvm/rubies/ruby-2.7.1/lib/ruby/gems/2.7.0/gems/irb-1.0.0/exe/irb:11:in `<top (required)>'
        1: from (irb):33
NoMatchingPatternError ([1, 2])

W przypadku tablicy słownikowej częściej nazywanej hashem sprawa wygląda inaczej. Nie potrzebujemy dokładnego dopasowania, więc błąd też się nie pojawi.

case { foo: 1, bar: 2 }
in foo:
  :match
end
 => :match

2.7.1> foo
 => 1

Gdybyśmy jednak chciały dokładnego dopasowania trzeba to zrobić w następujący sposób:

case { foo: 1, bar: 2 }
in foo:, **rest if rest.empty?
  :no_match
end

Traceback (most recent call last):
        4: from /home/agnieszka/.rvm/rubies/ruby-2.7.1/bin/irb:23:in `<main>'
        3: from /home/agnieszka/.rvm/rubies/ruby-2.7.1/bin/irb:23:in `load'
        2: from /home/agnieszka/.rvm/rubies/ruby-2.7.1/lib/ruby/gems/2.7.0/gems/irb-1.0.0/exe/irb:11:in `<top (required)>'
        1: from (irb):37
NoMatchingPatternError ({:foo=>1, :bar=>2})

lub w trochę prostszy sposób:

case { foo: 1, bar: 2 }
in foo:, **nil
  :no_match
end

Traceback (most recent call last):
        5: from /home/agnieszka/.rvm/rubies/ruby-2.7.1/bin/irb:23:in `<main>'
        4: from /home/agnieszka/.rvm/rubies/ruby-2.7.1/bin/irb:23:in `load'
        3: from /home/agnieszka/.rvm/rubies/ruby-2.7.1/lib/ruby/gems/2.7.0/gems/irb-1.2.3/exe/irb:11:in `<top (required)>'
        2: from (irb):49
        1: from (irb):50:in `rescue in irb_binding'
NoMatchingPatternError ({:foo=>1, :bar=>2})

4. Pominięcie dokładnego dopasowania dla tablicy

W Ruby 2.7 nie było możliwości częściowego dopasowania zwykłej tablicy tak jak w przypadku tablicy słownikowej. Mogłyśmy dopasować zaczynając od pierwszego lub ostatniego elementy tablicy. Jak na poniższych przykładach:

case [1, 2, 3]
in [1, *]
  :match
end
 => :match

case [1, 2, 3]
in [*, 3]
  :match
end
 => :match

Ale od wersji Ruby 3.0 możemy także szukać dopasowania dowolnego elementu w tablicy.

case [1, 2, 3, 4]
in [*, 2, a, *]
  :match
end
 => :match

3.0.0-preview1> a
 => 3

Przydatność tego rozwiązania jest na pewno bardziej widoczna na danych z przykładu poniżej:

json = {
  name: "Woman on Rails",
  friends: [{ name: "Alex", age: 24 }, { name: "Tom", age: 25 }]
}
json in { name: "Woman on Rails", friends: [*, { name: "Alex", age: age }, *] }

3.0.0-preview1> age
 => 24

5. Wzór alternatywny (ang. alternative pattern) dla zmiennych

Wiemy z poprzedniego artykułu, że w przypadku alternative pattern nie możemy używać zmiennych

case [1, 2]
in [1, 3] | [1, c]
  :match
end

Traceback (most recent call last):
        1: from (irb)
SyntaxError ((irb):55: illegal variable in alternative pattern (c))

jest jednak jeden wyjątek. Możemy użyć podkreślenia _:

case [1, 2]
in [1, 3] | [1, _]
  :match
end
 => :match

2.7.1> _
 => :match

6. Kilkukrotne przypisanie tej samej zmiennej we wzorcu

Dzięki ^ możemy sprawdzać dopasowanie używając tej samej zmiennej kilkukrotnie we wzorcu. W naszym przykładzie jest to name użyte dwa razy.

case { name: "Woman on Rails", people: [{ name: "Alex", age: 24 }, { name: "Woman on Rails", age: 25 }] }
in name:, people: [*, {age:, name: ^name}]
  :match
end

 => :match
2.7.1> name
 => "Woman on Rails"
2.7.1> age
 => 25

7. Nieskończone zakresy dla dopasowania do wzorca

Jest to funkcjonalność bardziej związana ze zmianami w samym Ruby, ale myślę że warto o niej wspomnieć. Od niedawana mamy dostęp do nieskończonych zakresów w Ruby, które można wykorzystać w dopasowaniu do wzorca.

case { a: 1, b: 2 }
in a: 0.. => first
  :match
end

:match
2.7.1> first
 => 1

case { a: 1, b: 2 }
in b: ..3 => first
  :match
end

 => :match
2.7.1> first
 => 2

8. Wyrażenia regularne w dopasowaniu do wzorca

Na koniec zostawiłam możliwość wykorzystania wyrażeń regularnych jako wzorca w pattern matching-u:

website = 'womanonrails.com'

case website
  in /\w*\.com/ => favorite_website
end

2.7.1> favorite_website
 => "womanonrails.com"

To wszystko co przygotowałam na dzisiaj. Znasz jeszcze więcej ciekawostek dotyczących dopasowania do wzorca w języku Ruby? Podziel się nimi w komentarzach.

Bibliografia