Tuplas
A tupla é uma estrutura de dados simples que aceita todos os tipos de dados. Diferente do map ela não aceita chave valor e sim, diretamente o valor. Iniciamos sua declaração com abrir chaves {
e finalizamos com fechar chaves }
. Seus valores são separados por virgula.
Exemplo :
defmodule TupleTest do
use ExUnit.Case
test "simple tests about tuple" do
int_value = 1
atom_value = :ok
map_value = %{name: "iago"}
tuple = {int_value, atom_value, map_value, 10}
assert tuple == {1, :ok, %{name: "iago"}, 10}
end
end
mix test test/tuple_test.exs
.
Finished in 0.01 seconds (0.00s async, 0.01s sync)
1 test, 0 failures
Você verá muito pela comunidade elixir o padrão de utilização de tuplas para simbolizar sucesso de uma função, separando elas como {:ok, data}
e {:error, reason}
. Diversas bibliotecas utilizam esse mesmo padrão e é uma forma de aprender a lidar com os dados.
Ao lidar com tuplas, temos algumas funções para nos ajudar
Tuple.insert_at/2
- Inserir valor ao final da tupleelem/2
- Acessar a tupla por um indiceput_elem/3
- Substituir o valor de um elemento da tupla por um indicetuple_size/1
- obter número de elementos na tupla
Outro exemplo simples:
defmodule TupleTest do
use ExUnit.Case
#...
test "inserting elements" do
data = {:initial}
data = Tuple.insert_at(data, 1, :foo)
data = Tuple.insert_at(data, 2, :bar)
assert data == {:initial, :foo, :bar}
assert tuple_size(data) == 3
assert elem(data, 1) == :foo
end
end
mix test test/tuple_test.exs
..
Finished in 0.01 seconds (0.00s async, 0.01s sync)
2 tests, 0 failures
Tuplas também podem ter valores complexos como structures
ou maps
.
Utilização de tupla de :ok
e :error
em uma função
:ok
e :error
em uma funçãoConvenções
Temos uma seção convenções da linguagem que é bem importante o entendimento. Tupla :ok/:error é uma delas. Acesse aqui
Vamos supor que faremos uma operação de cadastro em um fornecedor. Após a operação precisamos informar se a operação foi bem sucedida ou não. Para facilitar chamarei esse modulo de Vendor
e a função de register
. No registro precisamos apenas de um ID e uam descrição. Então passaremos dois parâmetros para a função. Queremos como retorno, a utilização de tuplas com :ok
e :error
chamado de error_handling (ou, tratamento de erro).
defmodule VendorTest do
use ExUnit.Case
describe "register/2" do
test "when params are valid" do
id = 54853
description = "Novo ponto de venda"
{:ok, result} = Vendor.register(id, description)
assert result == %{description: "Novo ponto de venda", id: 54853}
end
end
end
Conseguimos extrair o result utilizando pattern matching
. O :ok
garante que so poderá ter um matching caso venha um :ok
no primeiro elemento da tupla.
Se rodarmos isso teremos os primeiros relatórios de erro
mix test test/vendor_test.exs
warning: Vendor.register/2 is undefined (module Vendor is not available or is yet to be defined)
test/vendor_test.exs:8: VendorTest."test register/2"/1
1) test register/2 (VendorTest)
test/vendor_test.exs:4
** (UndefinedFunctionError) function Vendor.register/2 is undefined (module Vendor is not available)
code: {:ok, result} = Vendor.register(id, description)
stacktrace:
Vendor.register(54853, "Novo ponto de venda")
test/vendor_test.exs:8: (test)
Finished in 0.02 seconds (0.00s async, 0.02s sync)
1 test, 1 failure
Precisamos criar nosso módulo com nossa função
defmodule Vendor do
def register(id, description) do
result = %{id: id, description: description}
{:ok, result} # temos a tupla com o :ok simbolizando que tudo deu certo
end
end
mix test test/vendor_test.exs
Compiling 1 file (.ex)
.
Finished in 0.00 seconds (0.00s async, 0.00s sync)
1 test, 0 failures
O cenário feliz foi definido e tudo esta funcionando. Agora, precisamos também garantir o comportamento para a tupla com :error
. Para isso iremos garantir que so possa ser enviado por parâmetro IDs do tipo inteiro.
defmodule VendorTest do
use ExUnit.Case
describe "register/2" do
# ...
test "when params are not valid" do
id = "invalid-id"
description = "Novo ponto de venda"
{:error, reason} = Vendor.register(id, description)
assert reason == "Params are not valid"
end
end
end
Se rodarmos isso veremos que ele gera um novo erro para nós
mix test test/vendor_test.exs
Compiling 1 file (.ex)
.
1) test register/2 when params are not valid (VendorTest)
test/vendor_test.exs:14
** (FunctionClauseError) no function clause matching in Vendor.register/2
The following arguments were given to Vendor.register/2:
# 1
"invalid-id"
# 2
"Novo ponto de venda"
Attempted function clauses (showing 1 out of 1):
def register(id, description) when is_integer(id)
code: {:error, reason} = Vendor.register(id, description)
stacktrace:
(hello_world 0.1.0) lib/vendor.ex:2: Vendor.register/2
test/vendor_test.exs:18: (test)
Finished in 0.01 seconds (0.00s async, 0.01s sync)
2 tests, 1 failure
Isso significa que nao foi encontrado uma função que atenda as espectativas dessa chamada. Devido a precisar ser inteiro e estarmos enviando String o código quebra.
Para resolver esse problema, precisamos criar uma nova função de mesmo nome que atenda a nova especificação. No nosso caso, criaremos uma apenas para informar que algo esta errado e retornar o :error
.
defmodule Vendor do
# ...
def register(_id, _description) do
{:error, "Params are not valid"} # temos a tupla com :error, algo deu errado
end
end
Basta rodar e ver que agora temos o tratamento correto.
mix test test/vendor_test.exs
Compiling 1 file (.ex)
..
Finished in 0.01 seconds (0.00s async, 0.01s sync)
2 tests, 0 failures
Tratamento de erro Esta não é a melhor forma de tratar erros. Aqui foi simplificado para facilitar a explicação e exemplificar a utilização de tuplas. Mais a frente falaremos sobre tratemento de erros. Veja mais sobre isso aqui
Conclusão
Tuplas são uma estrutura de dados simples onde guarda valores de todos os tipos. Vimos formas de utiliza-lo e algumas funções de apoio. Também demos uma olhada em um cenário comum que a tupla é utilizada.
Last updated
Was this helpful?