Pattern Matching
Pattern matching em variáveis
Toda linguagem de programação tem a capacidade de criar variáveis. Você define um nome (talvez um tipo, dependendo da linguagem) e assina um valor a ela.
Elixir é um pouco diferente, mesmo parecendo igual a primeira vista, seu conceito muda.
Vamos a um exemplo simples onde criaremos uma map chamado pessoa e dentro dele teremos o campo name e genre. Popularemos os valores do map com My Name e :no_binary.
Vamos ao teste.
defmodule SimpleTest do
use ExUnit.Case
test "simple tests about pattern matching" do
person = Person.create("My Name", :no_binary)
assert person.name == "My Name"
assert person.genre == :no_binary
end
endRodando esse teste, obtemos o relatório de erro:
$ mix test
warning: Person.create/2 is undefined (module Person is not available or is yet to be defined)
test/phrases_test.exs:6: SimpleTest."test simple tests about pattern matching"/1
.
1) test simple tests about pattern matching (SimpleTest)
test/phrases_test.exs:4
** (UndefinedFunctionError) function Person.create/2 is undefined (module Person is not available)
code: person = Person.create("My Name", :no_binary)defmodule Document do
def render(_id, _type) do
"Rendering txt"
end
end
stacktrace:
Person.create("My Name", :no_binary)
test/phrases_test.exs:6: (test)
.
Finished in 0.04 seconds (0.00s async, 0.04s sync)
3 tests, 1 failureVamos criar nosso módulo para passar o teste
defmodule Person do
def create(name, genre) do
%{
name: name,
genre: genre
}
end
endEssa função apenas retorna um map com os valores que enviamos.
Se rodarmos novamente, os testes terão passados.
mix test
...
Finished in 0.02 seconds (0.00s async, 0.02s sync)
3 tests, 0 failuresAnalisando nosso teste, a definição de person parece uma assinatura comum de variável, certo? chave = valor. Mas por traz dos panos, elixir utiliza pattern matching para fazer isso. O valor a direita, apenas será adicionado a esquerda, caso de o match.
Em nosso exemplo, o requisito para matching é: se possuir algo ao lado direito, ele conseguira ser atribuído a variável person. Ele é bem aberto e pode receber qualquer tipo de dado.
Sei que parece confuso. Mas vamos continuar, logo tudo fará sentido.
Imagine agora que voce precisa pegar apenas name e genre que estão dentro do map retornado da função e coloca-los em uma variável cada. Para poder fazer as confiramações isoladas.
Algo assim:
use ExUnit.Case
test "simple tests about pattern matching" do
# ...
assert new_name == "My Name"
assert new_genre == :no_binary
end
endSem pattern matching, voce precisaria continuar com o valor person.name ou atribuir ele a uma nova variável new_name = person.name. O que por fim, daria na utilização do mesmo.
Com pattern matching é diferente, você pode obter diretamente o valor que quer, sem precisar de uma etapa intermediária.
defmodule PersonTest do
use ExUnit.Case
test "simple tests about pattern matching" do
%{
name: new_name,
genre: new_genre
} = Person.create("My Name", :no_binary)
assert new_name == "My Name"
assert new_genre == :no_binary
end
endO new_name e new_genre estão na posição onde está onde deveriam estar os valores certo? Ele é um espelho do que tem dentro da função, porem, no lugar dos valores, temos a definição de uma nova variável e é ai que o matching acontece. No nosso caso, estamos so dizendo que aceitamos qualquer valor que siga a estrutura de dentro do map, tendo name sendo chamado agora de new_name e a mesma coisa acontece com new_genre.
Sendo assim, podemos utilizar diretamente a variável, porque após o matching, ela existe.
Se rodar o teste a cima, ele funcionará corretamente.
$ mix test
...
Finished in 0.02 seconds (0.00s async, 0.02s sync)
3 tests, 0 failuresOutros exemplo:
iex> 10 = 10
iex> x = 1
iex> %{y: value} = %{y: 100}
iex> %{person: %{name: person_name}} = %{person: %{name": "iago"}}
iex> IO.inspet(x)
1
iex> IO.inspet(value)
100
iex> IO.inspet(person_name) #
iagoQuando um matching não é sucedido ele dispara uma exceção.
iex> {a, b, c} = {:hello, "world"}
** (MatchError) no match of right hand side value: {:hello, "world"}Pattern matching em funções
Na função funciona do mesmo jeito, porém, utilizado nos parâmetros. Vamos a um exemplo. Precisamos de uma função que exiba um tipo de documento dependendo do tipo pedido. Vamos chamar essa função de render e o módulo de Document. Nossa função precisará de um ID para identificar o documento e um atom pedindo o tipo apropriado, nesse exemplo :txt, :pdf. Vamos cuidar do txt primeiro:
defmodule DocumentTest do
use ExUnit.Case
describe "show/2" do
test "render txt" do
id = 12353
type = :txt
result = Document.render(id, type)
assert result == "Rendering txt"
end
end
endRodando esse teste obteremos um relatório comum de erro.
mix test test/document_test.exs
warning: Document.render/2 is undefined (module Document is not available or is yet to be defined)
test/document_test.exs:9: DocumentTest."test show/2 render txt"/1
1) test show/2 render txt (DocumentTest)
test/document_test.exs:5
** (UndefinedFunctionError) function Document.render/2 is undefined (module Document is not available)
code: result = Document.render(id, type)
stacktrace:
Document.render(12353, :txt)
test/document_test.exs:9: (test)
Finished in 0.02 seconds (0.00s async, 0.02s sync)
1 test, 1 failureNosso módulo ainda não existe. Vamos cria-lo:
defmodule Document do
def render(_id, _type) do
"Rendering txt"
end
endRodando o teste agora, tudo vai estar funcionando.
mix test test/document_test.exs
Compiling 1 file (.ex)
.
Finished in 0.01 seconds (0.00s async, 0.01s sync)
1 test, 0 failuresVamos lidar agora com o PDF.
defmodule DocumentTest do
use ExUnit.Case
describe "show/2" do
# ...
test "render pdf" do
id = 12353
type = :pdf
result = Document.render(id, type)
assert result == "Rendering pdf"
end
end
endSe rodarmos novamente, teremos um relatório de erro.
mix test test/document_test.exs
.
1) test show/2 render pdf (DocumentTest)
test/document_test.exs:14
Assertion with == failed
code: assert result == "Rendering pdf"
left: "Rendering txt"
right: "Rendering pdf"
stacktrace:
test/document_test.exs:20: (test)
Finished in 0.02 seconds (0.00s async, 0.02s sync)
2 tests, 1 failureAqui as coisas começam a ficar interessantes. Estamos chamando a mesma função, mas com parâmetros diferente. Uma solução fácil para isso, é por um if dentro da função e retornar o valor que queremos. Algo assim:
defmodule Document do
def render(_id, _type) do
if type == :txt do
"Rendering txt"
else
"Rendering pdf"
end
end
endIsso funcionária, mas se tornaria uma bagunça se precisarmos colocar mais tipos de arquivos. Com pattern matching, podemos controlar o fluxo das chamadas de funções, colocando nos parâmetros a formula do padrão para executar uma função.
defmodule Document do
def render(_id, :txt) do
"Rendering txt"
end
def render(_id, :pdf) do
"Rendering txt"
end
end
Na definição da função, no segundo parâmetro, ao invés de colocar uma variável, setamos diretamente o valor. Por regra do Pattern matching, o valor deve ser igual para ele ser realizado com sucesso e ai executar a função. A primeira função render so irá ser executada, quando o segundo parâmetro for :txt e a segunda função apenas quando for :pdf.
Se rodar o código, teremos um relatório positivo.
mix test test/document_test.exs
Compiling 1 file (.ex)
..
Finished in 0.01 seconds (0.00s async, 0.01s sync)
2 tests, 0 failuresCaso nenhuma dessas condições seja cumprida, uma exceção é lançada. Para evitar isso, podemos criar uma função para avisar que o tipo não é suportado:
defmodule DocumentTest do
use ExUnit.Case
describe "render/2" do
# ...
test "unsupported type" do
id = 12353
type = :unsupported
result = Document.render(id, type)
assert result == "This type #{type} is not supported"
end
end
end
executando o teste, temos o seguinte relatório:
mix test test/document_test.exs
1) test show/2 unsupported type (DocumentTest)
test/document_test.exs:23
** (FunctionClauseError) no function clause matching in Document.render/2
The following arguments were given to Document.render/2:
# 1
12353
# 2
:unsupported
Attempted function clauses (showing 2 out of 2):
def render(_id, :txt)
def render(_id, :pdf)
code: result = Document.render(id, type)
stacktrace:
(hello_world 0.1.0) lib/document.ex:2: Document.render/2
test/document_test.exs:27: (test)
..
Finished in 0.02 seconds (0.00s async, 0.02s sync)
3 tests, 1 failureO próprio relatório nos da as opções válidas com :txt e :pdf. Vamos corrigir isso.
defmodule Document do
def render(_id, :txt) do
"Rendering txt"
end
def render(_id, :pdf) do
"Rendering pdf"
end
def render(_id, unsupported) do
"This type #{unsupported} is not supported"
end
endNa ultima função, troquei novamente o segundo parâmetro para variável, assim ele não espera um valor especifico e consegue executar a função. Tendo isso em mãos, criamos uma frase para avisar que o tipo informado não é válido.
Podemos rodar esse código e temos o relatório de sucesso:
mix test test/document_test.exs
...
Finished in 0.02 seconds (0.00s async, 0.02s sync)
3 tests, 0 failuresConclusão
Pattern matching é uma das principais funcionalidades do elixir. Versátil e fácil de usar, é uma excelente ferramenta para se aprender. Coisas complexas podem ser resolvidas de forma simples e elegante.
Last updated
Was this helpful?