30 de abril de 2022 • 4 min de leitura
Pattern Matching no Ruby 3
Inline|Block Expressions
O Pattern matching(ou correspondência de padrões), foi introduzida, de forma experimental, a partir do Ruby 2.7.
Como de costume, no natal do ano de 2021, o time do Ruby o papai Noel nos deu uma nova versão do Ruby 3 que trouxe o pattern matching com algumas melhorias e NÃO mais de forma experimental. 🎉
Agora que o pattern matching foi adicionado à linguagem podemos usar sem medo.
Existem basicamente duas formas de usar o pattern matching no Ruby. Expressões inline e em bloco.
Expressões em uma linha(inline expressions)
Para expressões inline, você pode usar o operador =>
ou in
.
Usando em array
s.
Neste exemplo, os valores, "Rex", "Lola", "Sardinha", que estão do lado esquerdo do operador =>
estão sendo atribuídos às variáveis(first, second e last) do direito do operador.
# Dog list
["Rex", "Lola", "Sardinha"] => [first, second, last]
# => nil
p first
# => "Rex"
p last
# => "Sardinha"
O operador in
funciona de maneira similar, com a diferença que retorna true
ou false
.
[1, 2, 3] in [first, second, last]
# => true
p second
# => 2
p first
# => 1
Além de "atribuições" também é possível fazer comparações. O operador in
é ideal pra isso.
[1, "2", 3] in [1, "2", 3]
# => true
["Rex", "Lola", "Sardinha"] in [first, second, last]
# => nil
["Rex", second, 1] in [first, "Lola", 1]
# => true
["Rex", second, 1] in [1, "Lola", first]
# => false
Lembrando que a comparação é feita usando o operador ===
, então, embora esteja usando Array, o "Value Pattern" também funcionaria normalmente. Então fazer coisas como: "Rex" in first
, naturalmente, também funcionaria.
Também é possível fazer combinação com classes auxiliares.
["Rex", "Lola"] => [String => first, String => last]
# => nil
p first
# => "Rex"
p last
# => "Lola"
Caso não haja correspondência de padrões, uma exceção NoMatchingPatternError
será lançada.
["Rex", "Lola"] => [Integer => first, String => last]
# => ["Rex", "Lola"]: Integer === "Rex" does not return true (NoMatchingPatternError)
Também é possível usar o pattern matching com Hashs.
{ name: "Aristóteles" } => { name: }
p name
# => "Aristóteles"
Outro presente do Ruby 3, é que podemos omitir o valor de uma hash, caso esse valor, tenha o mesmo nome da key mas também é possível renomear caso queira.
{ name: "Aristóteles" } => { name: philosopher }
p philosopher
# => "Aristóteles"
Mais açúcar sintático(syntax sugar) é que podemos omitir as chaves {}
:
{ name: "Aristóteles" } => name:
p name
# => "Aristóteles"
Trabalhar com Valores aninhados também é possível.
{
programmer: {
name: "Aristóteles",
favorite_languages: ["Ruby", "Elixir", "Clojure", "Rust"]
}
} in { programmer: { favorite_languages: Array => langs }}
p langs
# => ["Ruby", "Elixir", "Clojure", "Rust"]
Arrays e Hashs também suportam o operador rest *
:
["Ruby", "Elixir", "Clojure", "Rust"] => [head, *tail]
p head
# => "Ruby"
p tail
# => ["Elixir", "Clojure", "Rust"]
{ name: "Naruto", age: 17 } in { name: String => name, ** }
# => true
p name
# => "Naruto"
O operador |
(Alternative Pattern) que serve como uma espécie de "ou".
{ invoice_number: 1001 } in { invoice_number: Integer | String }
# => true
{ code: ["123"] } in { code: String | Array }
# => true
Dada uma coleção de dados, o Finder Pattern irá procurar pela correspondencia de padrão de forma NÃO posicional dentro do Array.
[
{
value: 181.99,
product: "Polished Ruby Programming",
date: "2021-12-30T00:22:00.000-00:00"
},
{
value: 19.9,
product: "Practical Object-Oriented Design",
date: "2021-12-30T00:22:00.000-00:00"
},
{
value: 19.9,
product: "Practical Object-Oriented Design",
date: "2021-12-30T00:22:00.000-00:00"
}
] in [*, { value: Float, product: "Polished Ruby Programming", date: String } => book, *]
# warning: Find pattern is experimental, and the behavior may change in future versions of Ruby!
# => true
p book
# => {:value=>181.99, :product=>"Polished Ruby Programming", :date=>"2021-12-30T00:22:00.000-00:00"}
Como disse anteriormente, simples, porém poderoso. No entanto, não recomendo usar o Find Pattern nesse momento por se tratar de algo que ainda está em fase experimental. 😉
Expressões em bloco(block expressions)
Agora que aprendemos a sintaxe para expressões inline, vamos estender o conhecimento à instrução case
.
case [1, 2]
in [Integer, Integer]
"Match!"
else
"Not match :("
end
# => "Match!"
Basicamente, é possível fazer tudo que fizemos como nas expressões inline com a possibilidade de ter mais combinações para casamento de padrão.
case [1, 2, 3, 4, 5]
in Integer => head, *tail
"Head element #{head} and tail -> #{tail}"
end
# => "Head element 1 and tail -> [2, 3, 4, 5]"
O dry-monds pode tornar o uso do pattern matching muito interessante em controllers, POROs e etc. Por exemplo:
class Auth
include Dry::Monads[:result]
def call(params)
case login(params)
in Success[:ok, { name: String, token: String => token } => result]
puts "only token: #{token}"
result
in Failure[:user_not_found, String => msg]
{ error: msg }
in Failure[:password_does_not_match, String => msg]
{ error: msg }
end
end
private
# ...
# ...
# def login
# login_code_stuff
# end
end
No próximo post irei falar mais sobre os métodos deconstruct
e deconstruct_keys
; como estender o pattern matching em objetos não primitivos e um pouco sobre o pin operator ^
.
Até a próxima.