Casamento de Padrões em Elixir
Willian Frantz

Willian Frantz @wlsf

About: Elixir is love, Erlang is life!

Location:
Brazil
Joined:
Oct 5, 2020

Casamento de Padrões em Elixir

Publish Date: May 7
4 1

Você sabia que tanto Erlang como Elixir não possuem um operador de atribuição?
Ou seja, o que significa a seguinte expressão abaixo:

message = "Hello World"
Enter fullscreen mode Exit fullscreen mode

Mais conhecido como Pattern Matching, esse mecanismo te permite fazer associações de valores através
da comparação de símbolos entre o lado esquerdo do operador e o lado direito.

Na prática:

iex> hello_world = "Hello World"
iex> hello_world
> "Hello World"

iex> "Hello " <> world = "Hello World"
iex> world
> "World"
Enter fullscreen mode Exit fullscreen mode

Os 2 exemplos acima ilustram o funcionamento do casamento de padrões.
Onde a primeira execução aparenta ser uma simples atribuição, mas a partir da segunda, é possível perceber uma grande diferença conceitual.

No segundo exemplo a runtime irá tentar encontrar o valor "Hello " no valor do lado direito do operador, se esse valor for encontrado(deu match)
o resto do valor será armazenado na variável world.d

Quando essa assertiva não acontece, e o padrão não é encontrado, uma exception é gerada.

iex> "Hellu " <> world = "Hello World"
> ** (MatchError) no match of right hand side value: "Hello World"
Enter fullscreen mode Exit fullscreen mode

Essa é uma das minhas funcionalidades favoritas no ecossistema Erlang/Elixir, e neste texto vou tentar sintetizar um pouco do porquê.

Ex-1. Em Elixir, o casamento de padrões pode ser utilizado em Strings:

# Apenas uma comparação e nada acontece, pois o padrão é encontrado (caso contrário, geraria uma exception)
"Hello World" = "Hello World"

# Assimila o valor "World" à variável world
"Hello " <> world = "Hello World"

# Mesmo resultado do exemplo acima, porém escrito de uma forma diferente. (usando operadores de bitstring)
<<"Hello ", world::binary>> = "Hello World"

# Assimila os primeiros 5 caracteres na variável hello, valida um espaço " ", e joga o resto da string na variável world
<<hello::binary-size(5), " ", world::binary>> = "Hello World"

# Como não existe nenhum padrão a ser encontrado, meio que simula uma atribuição.
hello_world = "Hello World"
Enter fullscreen mode Exit fullscreen mode

É possível usar casamento de padrões com praticamente todos tipos, strings, listas, tuplas, maps, structs...

Ex-2. Testando outros tipos:

# Apenas procura o padrão do lado esquerdo no lado direito do operador, o mesmo com as strings.
[1, 2, 3] = [1, 2, 3]

# Assimila o valor 2 na variável second.
[1, second, 3] = [1, 2, 3]

# Assimila o valor 1 na variável first, e descarta os demais valores. (operadores importantes que usamos para listas `++` e `|`)
[first | _] = [1, 2, 3]

# Ignora apenas o primeiro valor da lista, e assimila os demais na variável rest. (2, 3)
[_ | rest] = [1, 2, 3]

# Simplesmente joga todos os valores da direita do operador na variável list.
list = [1, 2, 3]
Enter fullscreen mode Exit fullscreen mode

E o quão relevante é este tipo de operação no dia-a-dia?
Casamento de padrões pode ser utilizado em praticamente tudo, validação, polimorfia de funções, estruturas de controle, funções de ordem maior.

Ex-3. Em Elixir costumamos trabalhar bastante com o conceito de Tuplas de erro ou sucesso, e isso vale para requests,
comunicação externa, funções impuras, e por ai adiante...

Digamos que a execução de uma função qualquer do nosso código irá retornar uma tupla de sucesso, contendo {status, person}
Podemos tirar vantagem do casamento de padrões nesse cenário para criar uma tomada de decisão no nosso código.

Ilustração:

{:ok, %{name: "Willian"}} = {:ok, %{name: "Willian"}}

# Assimila o valor "Willian" na variável name.
{:ok, %{name: name}} = {:ok, %{name: "Willian"}}

# Assimila todo o map da pessoa na variável person
{:ok, person} = {:ok, %{name: "Willian"}}

# Assimila :ok no status e o map pessoa na variável person
{status, person} = {:ok, %{name: "Willian"}}

# Joga todo o valor da direita do operador na variável tuple_result
tuple_result = {:ok, %{name: "Willian"}}
Enter fullscreen mode Exit fullscreen mode

Criando uma tomada de decisão a partir desse valor:

defmodule Person do
  def print_name(result) do
    case result do
      {:ok, %{name: name}} ->
        IO.puts(name)

      _ ->
        IO.puts("Houve um erro ao processar o resultado")
    end
  end
end

# se executarmos este código passando nosso resultado do exemplo anterior teríamos algo como:
iex> Person.print_name({:ok, %{name: "Willian"}})
> "Willian"
Enter fullscreen mode Exit fullscreen mode

Agora com polimorfismo:

defmodule Person do
  def print_name({:ok, %{name: name}}),
    do: IO.puts(name)

  def print_name(_),
    do: IO.puts("Houve um erro ao processar o resultado")
end
Enter fullscreen mode Exit fullscreen mode

Em funções de ordem maior:

list_results = [
    {:ok, %{name: "Willian"}},
    {:ok, %{name: "Willian"}},
    {:error, "Algo deu ruim"},
    {:ok, %{name: "Willian"}},
    {:ok, %{name: "Willian"}},
    {:ok, %{name: "Willian"}},
    {:error, "Algo deu ruim"}
]

res = Enum.filter(list_results, fn
  {:ok, %{name: name}} -> true
  {:error, _reason} -> false
end)
Enter fullscreen mode Exit fullscreen mode

No caso acima, iremos filtrar da lista de resultados somente as tuplas de sucesso, e ignorar as que tiveram erro.

Concluindo, essa funcionalidade me permiti flexibilidade na hora de manipular dados e basicamente escrever toda estrutura do meu código,
tratamento de erros, estruturas condicionais, polimorfismo, e muito mais que não consigo nem lembrar direito.

Comments 1 total

Add comment