Elixir no dia a dia - Pattern Matching
Pattern matching é uma poderosa parte de Elixir que nos permite procurar padrões simples em valores, estruturas de dados, e até funções
Agenda
- O Básico
- Extração de valores
- Pattern matching em tudo!!!
- head and tail
- Pin operator
- Outras operações
- Conclusão
- Ref
ref: https://elixirschool.com/pt/lessons/basics/pattern_matching
O Básico
O operador =
no elixir é tratado de forma difente comparado a outras linguagens. Chamamos de match operator
esse carinha que além de extrair valores, pode até ser usado como “substituto” de estruturas de condições em alguns casos, além de armazenar valores.
Então tenha em mente que quando usamos o operador, estamos fazendo um match: se a operação do lado esquerdo dá match com a do lado direito, nós temos uma operação válida.
pra descomplicar… ou complicar mais
Isso quer dizer que, podemos fazer “comparações” e armazenar valores:
|
|
1 - As duas primeiras expressões são válidas pq literalmente: 1 é igual a 1 e a string “rodrigo” é igual a string “rodrigo”
2 - Nas expressões seguintes, o match operator atua como um “bindador” de valores, já que não temos um valor literal do lado esquerdo e sim uma varíavel, então o elixir sabe que deve associar o valor do lado direito ao esquerdo
Extração de valores
Para fazer um paralelo, vale lembrar de funcionalidades de outras linguagem como o Destructuring do js ou até msm o list do php (nas novas versões do php tbm temos um operador match: match php) para depois voltarmos ao pattern matching:
|
|
List do php:
|
|
Por outro lado com o match operator podemos fazer extrações com qualquer tipo de dado da linguagem, vamos aos exemplos:
|
|
Além de fazer extrações temos um cenário interessante no seguinte trecho:
|
|
Como estamos fazendo um match, a expressão é valida pq além do tipo do dado ser compatível (lista) temos o primeiro valor igual dos dois lados.
Podemos validar que os lados são comparados se passarmos valores diferentes, veja:
|
|
Na primeira expressão, ok, temos o número 1 do lado direito e do esquerdo. Na segunda temos um erro de match porque os valores não condizem dos dois lados.
Vale lembrar que o erro de match irá ocorrer literalmente em qualquer falha de comparação:
|
|
na segunda expressão, conseguimos armazenar o valor “Rodrigo” na variável name
mas na terceira não é possível, já que do lado direito temos o ano 2022 e do lado esquerdo temos o valor 2021.
Pattern matching em tudo!!!
Na medida que nos habituamos com a linguagem percebemos que patterning matching está presente em muitas operações, porque a legibilidade do código aumenta e deixamos nossas intenções mais claras:
Funções
|
|
É válido observar e já introduzir um ponto importante: podemos fazer matches parciais em mapas. Veja que definimos um mapa com 2 chaves: name
e year
mas na função extract_name
conseguimos dar match somente na chave name. Nós literalmente falamos:
- função espere um mapa que tenha a chave name e guarde o valor dela na variável
name_var
.
Agora imagine que além de querer extrair essa variável, queremos também ter certeza que o ano é 2022.
Poderíamos implementar a função da seguinte forma:
|
|
agora passando um mapa que não casa com o padrão:
|
|
Dito isso podemos ter um fallback pra qualquer outro valor que não seja o esperado no match, já que caso não exista essa implementação vamos sempre tomar o erro como no exemplo acima. No exemplo a seguir utilizamos outra funcionalidade da linguagem que é o multi clause function
que nos permite junto com pattern matching redeclarar uma função com argumentos diferentes:
|
|
A ordem das funções aqui importa, então o primeiro match que tem um padrão específico irá ser executado. Caso queira testar mude a ordem das funções e verá que mesmo que exista um match específico sempre a primeira instrução será executado
Caso não tenha reparado de início, o multi clause function
nos permite por exemplo remover um if desnecessário (um para ano 2022 e qualquer outra coisa para ano != 2022), nós literalmente definimos funções para cada situação. No mundo real é mto comum encontrar funções com essas características, ex:
|
|
O exemplo acima vem da lib floki. Perceba que temos a função find
declara para vários cenários:
- Quando o segundo argumento é uma string
- Quando o segundo argumento é uma lista
- Ou quando o segundo argumento é um tipo específico
Para complementar a parte de funções, veja esse exemplo:
|
|
head and tail
Uma operação comum em listas e que aparece sempre em recursão é a utilização do head and tail, que basicamente é extrair o primeiro valor de uma lista (head) e ter o resto dela (tail), ex:
|
|
O mesmo resultado pode ser obtido da seguinte forma:
|
|
Pin operator
Variáveis no elixir podem ser reatribuídas/atualizadas e caso você não queira que isso aconteça (comum em comparações e fluxos específicos baseados em decisões (case/cond/etc)), o pin operator (^
) pode ser utilizado:
|
|
na segunda expressão nós evitamos que a variável seja reatribuida, nós estamos expressando: “Essa expressão só é valida se o valor for igual ao atribuido anteriormente”
Em outras operações:
|
|
Na última expressão o erro aparece porque já definimos o x = 1 e queremos apenas uma comparação e não uma atribuição
Outras operações
O pattern matching aparece em muitas operações, e sabendo como funciona é possível executar fluxos complexos ou simplificados de várias formas:
case
|
|
|
|
maps
|
|
funções / funções anônimas
|
|
tuplas
|
|
Conclusão
Quanto mais a vontade se sentir com pattern matching, mais seu código ficará legível / pragmático :)