Como no texto anterior, hoje vou falar sobre o uso de
pattern matching
em Elixir de uma forma mais detalhada.
Pattern matching é um recurso muito poderoso (quase mágico!). Vou começar demonstrando simples exemplos, até chegar aos mais complexos.
1) Associando valores
Em Elixir não existe o conceito de variáveis como vemos nas linguagens de programação “convencionais”. Isso occore porque em Elixir não temos estado (na verdade temos, mas é um assunto pra outro post): temos valores que são transformados de função em função.
Quando se cria uma expressão de associação (ex.: x = 10), na verdade o que
ocorre é uma tentativa de “apelidar” um valor a um nome (10 foi apelidado de x).
Essa tentativa é feita através de pattern matching
.
Exemplo:
1 2 3 4 5 6 7 8 9 10 |
|
A linguagem vai tentar sempre “casar” o valor da esquerda com o da direita.
2) Um pouco mais complicado: Associando valores dentro de listas
Agora queremos capturar valores dentro de uma lista simples:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
|
Como você pode ver, Elixir vai entender que o valor do elemento do meio é igual nos dois lados. Mas caso esse valor seja diferente, não há match.
Exemplo com um erro de match
:
1 2 3 |
|
Os mesmos exemplos podem ser reproduzidos usando tuplas:
1 2 3 4 5 6 7 8 9 10 11 |
|
É possível até repetir o nome da “variável”:
1 2 3 4 5 6 7 |
|
3) Para pegar o primeiro item de uma lista e o restante da lista
O conceito de head
e tail
é muito usado em linguagens funcionais,
e não é exceção em Elixir. Com ele é possível pegar o primeiro item
da lista e o restante da lista em uma só expressão de associação.
Li pela primeira vez sobre o assunto em um tutorial de haskell, que recomendo!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
|
4) Para pegar o segundo, ou n-itens do começo de uma lista
Seguindo o exemplo anterior, para dar match nos dois primeiros itens de uma lista:
1 2 3 4 5 6 7 8 9 10 11 12 |
|
5) Para filtrar alguns resultados de uma lista usando list comprehensions
Aqui começa a complicar. Quem já programou em Python deve ter usado um recurso bem interessante: list comprehensions.
Em Elixir também temos comprehensions
que podemos usar em listas, tuplas, binaries
e streams.
Primeiro, um exemplo usando associação simples:
1 2 |
|
Agora, um exemplo usando um padrão mais complicado:
1 2 3 4 5 6 7 8 9 10 |
|
Como o exemplo sugere, comprehensions retornam somente os itens que deram match no padrão. Isso possibilita o uso de um filtro sem ter que escrever muito código.
6) Para dividir uma string
em partes
É comum percorrer uma string (ou binary
) por cada caracter. Podemos fazer isso
usando um match
de binary
, como no exemplo:
1 2 3 4 5 6 7 8 9 10 11 12 |
|
É interessante notar que estou especificando o encoding do binary. Isso é necessário para que a linguagem entenda aquele caracter como sendo parte de uma string.
Algo a se notar também é que usei uma vírgula ao invés de um pipe (|) para separar head e tail da string. Para que a representação de h seja a correta, tenho que dizer que aquele binary é UTF8.
7) Outra forma para dar match em uma string
Há uma maneira mais simples de dar match em uma string usando um operador de concatenação de
strings/binaries: o <>
.
Sem matches, o usamos da seguinte forma:
1 2 |
|
Agora com o match:
1 2 3 4 5 |
|
É bem simples e útil quando se sabe o conteúdo do início de uma string, e queremos pegar o restante dela que é variável, como estou usando nesse parser e leitor de HTML.
8) Usando o case
Pattern matching é usado com frequência em um bloco case
que serve justamente
para fazer alguma ação de acordo com o padrão de entrada.
É como o case
de C, no sentido de ter vários branches de decisão,
mas ao invés de tomar decisões por condições booleanas, o case
da Elixir toma
decisões baseado em padrões.
Exemplo:
1 2 3 4 5 6 7 |
|
O case
é a provavelmente a estrutura de controle mais usada em Elixir.
Faz parte da filosofia da linguagem escrever os programas levando em conta
situações adversas, como o erro que está sendo considerado no exemplo.
Ao invés de usar um tratamento de exceção, a linguagem propõe que os erros
sejam tratados como caminhos “normais” do programa. Sendo assim, o case
é
bem prático para casar um padrão em situações onde um erro é esperado.
Podemos também usar o casamento de padrão em funções, como demonstrei no post anterior.
9) Para dar match em qualquer coisa
É possível ignorar um determinado valor dentro
de um match
usando o underscore
.
Ele serve como um coringa, e é muito útil em determinadas
ocasiões.
Exemplo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
10) Usando em blocos de comunicação entre processos
Uma das features mais legais de Elixir é o modelo de comunicação entre atores (ou processos). Com ela é possível enviar todo o tipo de mensagem para um processo e processar essa mensagem através de pattern matching. Esse assunto é bem complicado de início, por isso recomendo que leiam um pouco mais sobre isso.
Exemplos:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
Perceba que o bloco de receive
é bem parecido com o
case
, porém está trabalhando com uma mensagem recebida.
Conclusão
Nesse artigo mostrei alguns exemplos de pattern matching em Elixir. Para entender mais e melhor, recomendo a leitura do livro Programming Elixir do Dave Thomas que é bem completo. Recomendo também dar uma fuçada em projetos abertos e tentar entender o código.
Deixo aqui meu convite à comunidade brasileira de Elixir, que é bem pequena ainda, a escrever mais sobre a linguagem. Em português ou inglês, o importante é trazer o máximo de devs que puder! :)