Skip to content

Latest commit

 

History

History
1377 lines (981 loc) · 40.3 KB

chapter1.livemd

File metadata and controls

1377 lines (981 loc) · 40.3 KB

Learn 4 Elixir - Capítulo 1

Introdução

Neste capítulo iremos apresentar a você, brevemente, a linguagem de programação Elixir. Assim como nos demais capítulos, você deve acompanhar este capítulo no Livebook.

Run in Livebook

Elixir

Elixir é uma linguagem criada pelo brasileiro José Valim. O projeto começou em 2011, enquanto Valim fazia parte da empresa Plataformatec, da qual ele era um dos sócios. Em 24 de maio de 2012 foi lançada a versão 0.5 de Elixir, a primeira versão, segundo Valim, com as características de Elixir atual. Por esta razão o aniversário de 10 anos de Elixir foi comemorado em 24 de maio de 2022.

De acordo com sua própria definição, o Elixir é "uma linguagem dinâmica e funcional, projetada para construir aplicações escaláveis e sustentáveis". A linguagem foi desenvolvida para ser executada sobre a máquina virtual de Erlang (chamada de BEAM), com o objetivo de ampliar a produtividade e extensibilidade do ecossistema da linguagem Erlang, criada na década de 80. O ecossistema de Erlang refere-se ao conjunto de tecnologias, ferramentas, bibliotecas e comunidades relacionadas a Erlang. Erlang é uma linguagem de programação funcional, concorrente e tolerante a falhas.

O ecossistema de Erlang é conhecido por sua força em sistemas distribuídos e de tempo real flexível, especialmente em aplicações de telecomunicações e comunicações em larga escala. WhatsApp é um exemplo de empresa que usa Erlang em produção. Erlang oferece recursos para lidar com concorrência, escalabilidade e resiliência, tornando-a adequada para sistemas altamente disponíveis e que exigem baixa latência.

Caso de Uso de Elixir

Um dos casos mais conhecidos de empresa que usa Elixir em produção como uma de suas principais linguagens é Discord. Em 2019, a empresa escreveu em seu blog o post "Usando Rust para escalar Elixir para 11 milhões de usuários concorrentes". Segundo a equipe de desenvolvedores, a escolha pelo Elixir veio desde o início, quando buscavam criar um sistema altamente concorrente em tempo real — o protótipo do Discord foi programado em Elixir.

Outros exemplos de empresas que usam Elixir em produção podem ser encontrados seção Cases da página da linguagem Elixir.

Agora vamos começar a aprender Elixir!

O que é um valor?

Um valor representa um dado do mundo. Por exemplo, 10 pode representar a idade de uma pessoa. Maria o nome desta pessoa e professora sua profissão. O salário mensal de Maria é R$ 5.050,55. Clique em Evaluate para avaliar cada um dos valores. O botão Evaluate aparece quando você passa o mouse em cima do canto superior esquerdo da caixa onde fica o valor. Depois da primeira avaliação, o botão se torna Reevaluate.

10
"Maria"
"professora"
5050.55

Quando executamos valores o Elixir nos retorna exatamente esse valor como resultado. Isso acontece porque os valores são constantes e imutáveis. Quando avaliados, retornam a si mesmos uma vez que nenhuma transformação é aplicada.

Mas antes de mais nada temos que observar que os dados acima têm tipos diferentes.

Quais são os tipos de valores?

Vamos começar nossa exploração pelos tipos de dados fundamentais na programação, que formam a base sobre a qual construímos a lógica e a estrutura de nossos programas. Os tipos básicos que abordaremos incluem: Números Inteiros, Números de Ponto Flutuante, Valores Lógicos (Booleanos), Átomos e Cadeias de Caracteres (Strings).

Números Inteiros

Abaixo você pode ver e avaliar vários números inteiros. Números inteiros são usados para representar valores númericos que não precisam de casas decimais.

10

Observe, no último exemplo, que você pode usar o "_" como separador.

654_677_888_788_899

Da mesma forma é possível representar números negativos utilizando sinal.

-5_323_456_546_346

Além disso é possível representar números em outras bases como binário (prefixando com 0b), octal (prefixando com 0o) e hexadecimal (prefixando com 0x).

0b101010
0o52
0x2A

Números de Ponto Flutuante

Números de ponto flutuante exigem um ponto decimal depois de pelo menos um dígito. Eles possuem precisão de 64 bits e suportam "e" para números exponenciais.

Avalie os exemplos abaixo para ver o que acontece!

1.618
.14
1.0e-10
1.234e3

Em qual dos exemplos acima aconteceu um erro? Por quê?

Átomos

Em Elixir, um átomo é uma espécie de palavra-chave constante que tem um nome que é igual ao seu valor. Imagine um átomo como uma etiqueta com uma palavra que representa algo específico. A coisa interessante sobre os átomos em Elixir é que eles sempre começam com dois pontos (:).

:atomo
:idade
:peso

Valores Lógicos (Booleanos)

Em Elixir, os valores lógicos são como interruptores que podem estar ligados (verdadeiros), desligados (falsos). Elixir usa dois átomos especiais para representar esses valores: :true para verdadeiro, :false para falso. Estes átomos especiais são tão importantes e bem conhecidos que você não precisa colocar dois pontos antes deles como você faria com outros átomos. Em vez disso, você pode simplesmente usar true para representar verdadeiro e false para representar falso.

is_atom(true)

Se você escrever true em Elixir, isso significa "verdadeiro" e representa um valor lógico que está ligado ou verdadeiro.

true

Se você escrever false em Elixir, isso significa "falso" e representa um valor lógico que está desligado ou falso.

false

Um átomo especial que está relacionado a 'true' e 'false' é 'nil'. Assim como os dois anteriores, você pode usar ':nil' ou 'nil'. Se você escrever nil em Elixir, isso significa a ausência de um valor e, em algumas situações, o 'nil' se comporta como o 'false'. Falaremos mais sobre isso quando abordarmos as operações com valores lógicos.

nil
nil

Cadeias de Caracteres (Strings)

Uma cadeia de caracteres, que é chamada de "string", é basicamente uma sequência de letras, números, símbolos ou até mesmo emojis. Para indicar que algo é uma string em Elixir, você a envolve com aspas duplas ("). Isso ajuda o Elixir a entender que tudo dentro das aspas é uma única string.

"Olá, Elixir!"

As strings em Elixir são inteligentes e podem conter caracteres especiais, como acentos em letras ou até mesmo emojis. O Elixir usa a codificação UTF-8, o que significa que é possível representar uma ampla variedade de caracteres de diferentes idiomas e símbolos.

"José Valim é o criador de Elixir!! 🎉🎉🎉 Ele é brasileiro!!! 🇧🇷🇧🇷🇧🇷"

Você pode até mesmo criar strings que se estendem por várias linhas. Para fazer isso, você usa três aspas duplas no início e no final da string, assim: """.

"""
Olá, pessoas!
Este é o Learn4Elixir da Universidade Brasileira Live.
Em Elixir você pode ter uma string com várias linhas!
"""

Verificando Tipos

Existem diversas funções que nos permitem verificar o tipo de um valor ou expressão. O mais comum é quando queremos saber o tipo de um valor usar uma função is_ (É? em inglês) seguida do tipo que queremos saber, como vimos antes is_atom checa se o valor é um átomo.

is_atom(:um_atomo)

Existe uma função similar para cada tipo, como vamos ver.

is_integer(1233)
is_integer(false)
is_float(1)
is_float(1.0)
is_number(1)
is_number(1.9)
is_atom(:peso)
is_atom(10)
is_atom(true)
is_atom(45.4)
is_boolean(true)
is_boolean(false)
is_boolean(0)
is_boolean(nil)

Porém, a função para verificar strings, diferente das outras se chama is_binary.

is_binary("UBL")
is_binary(true)
is_binary("🎉🎉🎉")

Expressões

Agora que já estamos familiarizados com os tipos de dados em Elixir, é hora de aprender sobre expressões. Expressões são maneiras de combinar e manipular esses tipos de dados para realizar tarefas em nosso código.

Pense em expressões como pequenos blocos de construção que nos permitem fazer coisas úteis. Por exemplo, podemos usar operadores, que são como ferramentas especiais, para realizar cálculos e comparações. No entanto, vale ressaltar que os operadores em Elixir são, na verdade, funções especiais que realizam operações específicas.

2 + 3
4 - 5
4 * 80
4 / 3

Observe que, no exemplo acima, apesar de os operandos serem números inteiros, o resultado foi um número de ponto flutuante.

Além dos operadores matemáticos temos também outros operadores. Por exemplo, temos os operadores lógicos que usamos para fazer operações com valores lógicos (booleanos), são eles and, or e not.

and: Este operador retorna true se ambos operandos forem verdadeiros. Caso contrário, retorna false. Por exemplo, true and false retornará false.

true and false

or: Este operador retorna true se qualquer um dos operandos for verdadeiro. Caso contrário, retorna false. Por exemplo, true or false retornará true.

false or true

not: Este é um operador de negação. Ele inverte o valor do operando. Se o operando for true, ele retornará false e vice-versa. Por exemplo, not true retornará false.

not false

Note que os operadores acima sempre esperam valores booleanos true e false como seus primeiros parâmetros. Passar um valor não booleano como primeiro parâmetro para esses operadores resulta em uma exceção BadBoolean, como pode ser testado abaixo:

1 and false

Você pode usar valores não lógicos como segundo argumento de 'and' e 'or'. Mas, caso queria utilizar operadores lógicos com valores não booleanos, como um átomo ou números, como primeiro argumento, o Elixir fornece os operadores &&, || e !. Esses operadores aceitam valores de qualquer tipo, e apenas os átomos false e nil serão executados como valores falseáveis ('falsy'). Os demais valores são considerados 'truthy'. Ou seja, para estes operadores (e alguns outros), eles se comportam como verdadeiro.

1 && true
1 || true
!1
!false
!nil
14 || 15
14 && 29
14 && nil
14 && false

Em suma, utilize os operadores and, or e not quando suas expressões esperam valores exclusivamente booleanos, e os operadores &&, || e ! quando valores de tipos diferentes podem ser esperados.

Concatenação de Strings

Em Elixir, usamos o operador <> para concatenar (ou seja, juntar) duas strings.

"UBL" <> " " <> "é legal!"
"https://" <> "ulivre.dev"

A interpolação de strings permite que você insira valores de variáveis ou expressões dentro de uma string. Em Elixir, usamos #{} para interpolação de strings.

"Onde você estuda? Eu estudo na #{"UBL"}!"
"Eu tenho #{25} anos de idade."

Observe que não apresentamos nenhuma operação com átomos, exceto quando estes são valores lógicos.

Variáveis

Uma variável é um nome que representa um valor. Podemos por exemplo atribuir o nome "curso" ao valor string "Matemática".

curso = "Matemática"

Observe que o retorno da expressão (toda expressão em Elixir retorna algo, se não ocorrer nenhum erro) é o valor que, após o casamento de padrões, está dos dois lados do operador '='.

Agora quando chamarmos o valor "curso" vamos obter seu valor.

curso

Agora podemos usar esse nome para passar esse valor em qualquer operação com strings.

is_binary(curso)
"Estou estudando #{curso}"

Em elixir, todas as variáveis são imutáveis. Isso significa que você não pode alterar o valor que uma variável representa, mas é possível reatribuir a variável a um novo valor.

curso = "Ciência da Computação"

Você não está mudando o valor "Matemática" para "Ciência da Computação", em vez disso criando um novo valor "Ciência da Computação" e fazendo a variável curso apontar para ele. O valor "Matemática" ainda existe na memória, mas não é mais acessível pela variável curso.

Casamento de Padrões (Pattern Matching)

Quando declaramos uma variável usamos o operador de casamento de padrões, cujo símbolo é o '=', você pode casar o padrão "UBL", por exemplo, com a variável 'nome' e assim atribuir "UBL" a 'nome'. Avalie o código abaixo:

nome = "UBL"

Apesar do símbolo do casamento de padrões ser '=', os lados esquerdo e direito de um casamento de padrões têm "poderes" diferentes. No exemplo acima, vimos como atribuir um valor à variável 'nome'. Só pudemos fazer isso porque 'nome' estava no lado esquerdo. Se estivesse no lado direito (ver o exemplo parecido seguinte), não funcionaria. Ou seja, o lado esquerdo "tem mais poder" do que o lado direito.

10 = idade

Porém, uma vez que uma variável já esteja atribuída, é possível usá-la do lado direito. Veja o exemplo abaixo.

"UBL" = nome

O exemplo acima é importante para deixar claro que o casamento de padrões não é uma simples atribuição de valor a variável.

Os demais exemplos de casamento de padrões só fazem sentido se você conhecer as estruturas de dados compostas (as coleções).

Listas

Coleções são estruturas de dados que contém zero ou mais valores. O tipo de coleção que provavelmente é o mais usado em Elixir são as listas. Uma lista pode ser vazia ou conter 1 ou mais elementos. Não vamos discutir detalhes de como listas são implementadas em Elixir aqui. Apenas mostraremos como são representadas e como fazer casamento de padrões com elas.

# Lista Vazia
[]

No exemplo acima você vê uma lista vazia e também como escrever um comentário de linha em Elixir: tudo que vier depois do '#' é um comentário.

# lista contendo um elemento
[1]
# lista contendo dois elementos
[3, 45]
# lista contendo 3 elementos
[45, 78, 21]

A partir dos exemplos acima, você já percebeu que as listas começam com '[', terminam com ']' os valores são separados por vírgulas. Mais alguns exemplos.

# lista com valores de diferentes tipos.
[1, "Casa", :idade]
# lista contendo outra lista
[1, [2, 45], 3]

Agora vamos ver exemplos de casamento de padrões com listas.

[cabeca | cauda] = [1, 3, 2]
cabeca
cauda
[primeiro, segundo | resto] = [45, 67, 784, 3453]
primeiro
segundo
resto

O exemplo abaixo não dá erro pois no final de toda lista há uma lista vazia.

[cabeca | cauda] = [78678]
cabeca
cauda

Porém, o exemplo abaixo resulta em erro.

[cabeca | cauda] = []

Isso acontece porque não existe nenhum valor para ser associado à variável cabeca, pois cabeca, nesse contexto, representa o primeiro elemento da lista.

No casamento de padrões, você pode usar o operador '_', que alguns chamam de "coringa", para ignorar algo.

[cabeca | _] = [646, 345, 345]
cabeca
_

Nada é atribuído a '_'.

Veremos depois como utilizar '_' com tuplas.

Uma observação importante, e que está relacionada com a forma com que listas são implementadas, é que as listas devem crescer a partir da cabeça. Ou seja, se eu for colocar um novo elemento, devo colocá-lo como primeiro elemento (mais à esquerda). Para isso, eu posso usar o operador '|' como nos exemplos abaixo.

[1 | [3, 4]]
[1 | []]
[2, 3, 4 | [6, 7, 8, 9]]

Apenas tenha cuidado para não gerar listas impróprias! Depois do '|' deve vir uma lista.

[1 | 2]

Embora seja uma possível, desaconselha-se a concatenação de listas usando o operador ++/2 quando a primeira lista for grande.

[1, 3, 56] ++ [34, 560, 8]

NOTA: Em Elixir, o nome de uma função ou operador tem dois componentes: o nome em si (neste caso ++) e sua aridade. Aridade é informação essencial quando se fala sobre código Elixir, pois esta indica o número de argumentos que uma função ou operador aceita (dois, neste nosso exemplo). Ou seja, uma função soma, que recebe dois argumentos seria representada como soma/2, e esta é uma função completamente distinta de soma/3, por exemplo. Nome e aridade são combinados com uma barra (/).

# subtração de listas, operador '--/2'
[1, 2, 3] -- [1, 3]
[1, 2, 2, 3, 2, 3] -- [1, 2, 3, 2]

Tuplas

Uma tupla é uma coleção ordenada de elementos. Parece um pouco com uma lista, mas seu casamento de padrões trabalha com um número fixo de elementos, diferente das listas, que permitem o casamento de padrões sem que se saiba o número exato de seus elementos. As tuplas são definidas usando chaves '{', '}' e seus valores são separados por vírgulas.

# tupla vazia
{}
{1}
{:nome, "João"}
{:nome, "Maria", :idade, 10}

Após ver os exemplos acima, vamos ver como fazer casamento de padrões com tuplas. Suponha que você tem uma variável 'pessoa', como abaixo:

pessoa = {:nome, "Maria", :idade, 10}

E suponha que eu, sabendo a estrutura da tupla pessoa, queira apenas saber o nome da pessoa. Basta fazer:

{:nome, nome, _, _} = pessoa
nome

O mesmo poderia ser feito para idade.

{_, _, :idade, idade} = pessoa
idade

Ou para os dois ao mesmo tempo.

{:nome, nome, :idade, idade} = pessoa
{nome, idade}

Veja que acima nós construímos uma tupla a partir dos resultados do casamento de padrões.

Mapas (Maps)

Mapas são coleções de pares chave-valor, onde cada chave é única e associada a um valor específico. Em Elixir, os mapas são delimitados por chaves %{} e podem conter zero ou mais pares chave-valor. Aqui está uma representação básica de um mapa em Elixir:

%{:chave1 => "valor 1", :chave2 => "valor 2", :chave3 => "valor 3"}

As chaves e os valores em um mapa podem ser de qualquer tipo, incluindo átomos, números, strings e até mesmo outras estruturas de dados.

%{1 => "algo", 2 => :algo, 3 => [1, 2, 3]}

Em Elixir, você pode criar um mapa simplesmente usando a notação de chaves %{} e especificando os pares chave-valor. Aqui estão alguns exemplos:

# Um mapa vazio
%{}
# Um mapa com pares chave-valor
%{nome: "Alice", idade: 30, cidade: "São Paulo"}
# Um mapa com tipos diferentes de valores
%{nome: "Bob", pontos: 42, aprovado: true}

Lembre-se de que as chaves em um mapa são únicas. Se você tentar adicionar uma chave que já existe, o valor anterior será substituído pelo novo valor.

# Isso substituirá "Alice" por "Bob" na chave "nome"
%{nome: "Alice", nome: "Bob"}

Para acessar valores em um mapa, você usa a sintaxe de colchetes mapa[chave]. Aqui estão alguns exemplos:

# Criando um mapa
dados = %{nome: "Alice", idade: 30, cidade: "São Paulo"}

# Acessando valores
nome = dados[:nome]
idade = dados[:idade]
cidade = dados[:cidade]
nome
idade
cidade

Quando tentamos acessar uma chave que não existe em nosso mapa usando [] será retornado nil caso o valor dessa chave não exista.

valores = %{"uno" => 1, "dos" => 2, "tres" => 3}
valores["uno"]
valores["quatro"]

Porém, quando criamos um mapa em que todas as chaves são átomos podemos acessar nossos valores tanto usando [] passando o valor da chave, quanto usando o operador ponto . para acessar a chave que queremos, esse operador tem a vantagem de lançar um erro caso a chave não exista no mapa o que nos ajuda a encontrar erros e problemas mais cedo.

chaves_valores = %{:um => 1, :dois => 2, :tres => 3}
chaves_valores[:um]
chaves_valores[:quatro]
chaves_valores.um
chaves_valores.quatro

Caso queira evitar o erro se a chave não existir no mapa, você pode usar o operador de acesso seguro Map.get/2 (Map é um espaço de nomes que contêm bastante funções para trabalhar com Mapas, entre elas a função get que recebe dois parâmetros - o mapa e a chave), que retorna um valor padrão em vez de lançar uma exceção.

# Acessando com Map.get/2
Map.get(dados, :nome)

Porém, caso não exista a chave isso não vai nos lançar uma exceção.

Map.get(dados, :algo)

Também possui a versão Map.get/3 que recebe, como terceiro argumento, o valor que deve ser retornado caso a chave não exista.

Map.get(dados, :algo, "valor quando a chave não existe")

Mapas em Elixir são imutáveis, o que significa que não podem ser modificados após a criação. No entanto, você pode criar novos mapas com valores atualizados. O operador | é usado para criar um novo mapa que contém todas as associações do mapa original, juntamente com quaisquer associações adicionais ou atualizadas.

# Atualizando o mapa
mapa = Map.put(dados, :idade, 31)
novos_mapa = Map.put(mapa, :profissao, "Engenheira")

Tenha em mente que, como os mapas são imutáveis, o mapa original permanece inalterado após cada atualização. É comum atribuir o novo mapa a uma nova variável, como fizemos acima.

mapa

Para remover uma chave e seu valor associado de um mapa, você pode usar a função Map.delete/2.

mapa_atualizados = Map.delete(dados, :idade)
mapa_atualizados

Novamente, observe que a operação de exclusão não modifica o mapa original, mas cria um novo mapa sem a chave removida.

dados

Para verificar se uma chave específica existe em um mapa, você pode usar a função Map.has_key?/2.

Map.has_key?(dados, :idade)
Map.has_key?(dados, :non_existent_key)

Em Elixir, mapas são frequentemente usados como registros leves para representar dados estruturados. Em vez de definir tipos de registro estáticos, você pode criar mapas com chaves específicas para representar entidades de dados.

# Representando um usuário com mapa
usuario = %{nome: "Alice", idade: 30, cidade: "São Paulo"}

# Representando um produto com mapa
produto = %{nome: "Celular", preco: 799.99, estoque: 50}

Isso oferece flexibilidade, pois você pode adicionar ou remover campos conforme necessário, sem precisar modificar a estrutura do registro.

O casamento de padrões com mapas em Elixir permite que você desestruture um mapa e associe os valores das chaves à variáveis. Isso torna mais fácil acessar os valores específicos que você deseja dentro de um mapa. Vamos começar com um exemplo simples:

usuario = %{nome: "Bob", idade: 25, cidade: "Porto Alegre"}

# Casando o mapa
%{nome: nome, idade: idade} = usuario

Neste exemplo, estamos casando o mapa usuario com um padrão que consiste em duas chaves: :nome e :idade. Os valores associados a essas chaves são extraídos e atribuídos às variáveis nome e idade. O resultado é a impressão do nome e da idade do usuário.

nome
idade

Às vezes, você pode querer casar um mapa, mas não está interessado em todos os valores. Nesse caso, você pode usar o caractere sublinhado _ para ignorar valores que não são relevantes para sua operação.

# criando um mapa/registro de um curso
curso = %{nome: "Ciência da Computação", duracao: 5, inicio: 2023}

# Casando o mapa, mas ignorando nome
%{nome: _, duracao: duracao, inicio: inicio} = curso

# soma os valores extraídos do mapa/registro
inicio + duracao

Você também pode casar partes específicas de um mapa sem a necessidade de casar todas as chaves. Isso é útil quando você deseja acessar um valor em um mapa complexo sem se preocupar com as outras chaves.

# Definindo um mapa complexo
pedido = %{cliente: %{nome: "Alice", idade: 30}, produtos: [%{nome: "Celular", preco: 799.99}]}

# Casando partes do mapa
%{cliente: %{nome: nome, idade: idade}} = pedido

# Interpola os valores extraídos
"Nome do Cliente: #{nome}, Idade do Cliente: #{idade}"

Neste exemplo, estamos casando apenas a parte do mapa que corresponde à informação do cliente. Isso nos permite acessar o nome e a idade do cliente, independentemente de outras informações no mapa.

Em Elixir, você pode casar mapas aninhados, o que significa que você pode acessar valores dentro de mapas dentro de mapas. Isso é especialmente útil quando você lida com estruturas de dados complexas.

# Definindo um mapa com aninhamento
empresa = %{nome: "Minha Empresa", endereco: %{rua: "Rua Principal", cidade: "São Paulo"}}

# Casando mapas aninhados
%{endereco: %{cidade: cidade}} = empresa

Neste exemplo, estamos casando o mapa empresa e acessando a cidade dentro do mapa aninhado endereco. Isso nos permite obter a cidade da empresa de forma direta.

Podemos construir um novo mapa com as chaves extraidas.

%{cidade: cidade}

Podemos usar o operador | que vimos anteriormente em Listas para atualizar os valores em mapas.

evento = %{nome: "Learn4Elixir", mes_inicio: 10, mes_fim: 11, nota: 10}
novo_evento = %{evento | nota: 1000}
# atualizando vários valores
novo_evento = %{evento | nome: "GambiConf", mes_inicio: 11}

Estruturas (Structs)

Em Elixir, as estruturas, ou structs, são uma maneira de definir e manipular dados com uma estrutura fixa. Enquanto os mapas são flexíveis e podem conter qualquer chave, as structs têm um conjunto predefinido de campos e um nome associado a elas. As structs são úteis para representar entidades de dados com campos específicos e fornecem benefícios como validação de presença das chaves definidas em tempo de compilação, semântica na definição de um dado e documentação clara.

defmodule Usuario do
  defstruct [:nome, :idade, :cidade]
end

Neste exemplo, criamos uma struct chamada Usuario com três campos: nome, idade e cidade.

%Usuario{}

Para criar um valor do tipo estrutura com dados basta passá-los como chave e valor, passsando corretamente o nome e chaves válidas em sua definição. Caso uma seja omitida, ela irá receber o valor padrão.

%Usuario{nome: "Camilo", idade: 28}

Uma estrutura não irá aceitar qualquer chave que não tenha sido declarada em sua declaração.

%Usuario{qualquer: "coisa"}
%Usuario{nome: "Camilo", idade: 28, qualquer: "coisa"}

É possível definir estruturas que possuem atributos com valores padrão. Caso algum desses valores não sejam fornecidos ao criar uma instância da struct, o valor padrão é usado ao invés de nil.

defmodule Pessoa do
  defstruct nome: "", idade: 0, cidade: ""
end

Agora quando criarmos um valor da estrutura com valores padrão vamos ter uma estrutura que já é criada com os valores iniciais.

%Pessoa{}

Você pode acessar os valores de uma estrutura usando o operador "."

%Pessoa{nome: "Ana Bastos", cidade: "São Paulo"}.nome

Da mesma forma quando salvamos uma estrutura em uma variável.

pessoa = %Pessoa{nome: "Ana Bastos", cidade: "São Paulo"}
pessoa.cidade

Ambas chaves, com e sem valor padrão, podem ser misturadas na declaração de uma estrutura desde que as chaves sem valor padrão venham primeiro, caso contrário isso irá causar um erro.

defmodule Animal do
  defstruct [:nome, especie: "", localizacao: "", :idade]
end

Mude a ordem para [:nome, :idade, especie: "", localizacao: ""] e execute novamente a célula.

%Animal{}

Também é possivel usar @enforce_keys para garantir que todas as chaves que são especificadas nessa propriedade sejam fornecidas ao criar um valor da estrutura. Se o campo estiver faltando isso vai causar um erro.

defmodule Jogador do
  @enforce_keys [:nome, :idade, :cidade]
  defstruct nome: "", idade: 0, cidade: ""
end
%Jogador{nome: "John Doe", idade: 23, cidade: "Salvador"}

Resolva esse erro passando os campos que são necessários na célula acima.

Podemos retornar uma nova estrutura copiando os valores da antiga e alterando o que é necessário utilizando o operador |

# Cria a variável contendo os valores de uma instância da nossa estrutura
mentor = %Pessoa{nome: "Dickson Melo", idade: 20, cidade: "Natal"}
mentor_atualizado = %{mentor | idade: mentor.idade + 1}
# Criando outra variável com um valor de nossa estrutura
outro_mentor = %Pessoa{nome: "Douglas Tofoli", cidade: "São Paulo"}

# Atualizando vários campos
outro_mentor_atualizado = %{outro_mentor | nome: "Douglas", idade: 23}

Exercício

Exercicio 1

Escreva um exemplo de dado em Elixir para cada tipo apresentado abaixo, colocando-o como parâmetro no lugar de "" na respectiva função. O resultado de todas as funções deve ser true.

is_number(3)
is_integer(3)
is_float(3.14)
is_binary("string")
is_atom(:atom)
is_boolean(true)
is_map(%{foo: "bar"})
is_tuple({:foo, "bar"})

Exercicio 2

Imagine que vários desenvolvedores de software que trabalham com front-end publicaram abertamente seus salários no Twitter. Você pensou em fazer um programa que calculasse a média aritmética de salários de cada região a partir do input de 3 salários dessa região.


Handle no Twitter Salário
@ocamilodev R$ 71255.53
@ocam_l R$ 30236.23
@camilotk_ R$ 5256.12

Defina variáveis para cada salário, depois, atribua o cálculo de média aritmética à variável média. Por fim, rode a última célula com if para garantir que a resposta está certa.

ocamilodev = 71255.53
ocam_l = 30236.23
camilotk_ = 5256.12
# ...
media = (ocamilodev + ocam_l + camilotk_) / 3
# Checa se a resposta está correta
if floor(media) == 35582 do
  "Parabéns, você acertou!"
else
  "Errooooou!"
end

Exercicio 3

Resolva a equação quadrática x² - 2x + 1 = 0 calculando seu delta e sua(s) raíz(es).

Dica: A operação de raíz quadrada é feita com :math.sqrt(), :math é um módulo do Erlang que contêm diversas funções matemáticas que podem ser úteis na sua jornada.

:math.sqrt(4)
delta = :math.sqrt(:math.pow(2, 2) - 4 * 1 * 1)
x1 = (-1 * -2 - delta) / 2 * 1
x2 = (-1 * -2 + delta) / 2 * 1

{x1, x2}

Exercicio 4

Trabalhando com Elixir você desenvolveu um sistema de E-commerce. Recentemente você decidiu extrair os comentários e o sistema respondeu te enviando uma lista de mapas com os comentários. Cada mapa possui as propriedades:

  • productId: Que é o valor de identificação do produto.
  • id Que é o identificador do comentário.
  • name: Que é o título do comentário.
  • email: Que é o email de quem fez o comentário.
  • body: Que contêm o comentário.

A resposta completa foi:

resposta_completa = [
  %{
    "productId" => 1,
    "id" => 1,
    "name" => "id labore ex et quam laborum",
    "email" => "[email protected]",
    "body" =>
      "laudantium enim quasi est quidem magnam voluptate ipsam eos\ntempora quo necessitatibus\ndolor quam autem quasi\nreiciendis et nam sapiente accusantium"
  },
  %{
    "productId" => 1,
    "id" => 2,
    "name" => "quo vero reiciendis velit similique earum",
    "email" => "[email protected]",
    "body" =>
      "est natus enim nihil est dolore omnis voluptatem numquam\net omnis occaecati quod ullam at\nvoluptatem error expedita pariatur\nnihil sint nostrum voluptatem reiciendis et"
  },
  %{
    "productId" => 2,
    "id" => 6,
    "name" => "et fugit eligendi deleniti quidem qui sint nihil autem",
    "email" => "[email protected]",
    "body" =>
      "doloribus at sed quis culpa deserunt consectetur qui praesentium\naccusamus fugiat dicta\nvoluptatem rerum ut voluptate autem\nvoluptatem repellendus aspernatur dolorem in"
  },
  %{
    "productId" => 2,
    "id" => 7,
    "name" => "repellat consequatur praesentium vel minus molestias voluptatum",
    "email" => "[email protected]",
    "body" =>
      "maiores sed dolores similique labore et inventore et\nquasi temporibus esse sunt id et\neos voluptatem aliquam\naliquid ratione corporis molestiae mollitia quia et magnam dolor"
  },
  %{
    "productId" => 3,
    "id" => 11,
    "name" => "fugit labore quia mollitia quas deserunt nostrum sunt",
    "email" => "[email protected]",
    "body" =>
      "ut dolorum nostrum id quia aut est\nfuga est inventore vel eligendi explicabo quis consectetur\naut occaecati repellat id natus quo est\nut blanditiis quia ut vel ut maiores ea"
  },
  %{
    "productId" => 3,
    "id" => 12,
    "name" => "modi ut eos dolores illum nam dolor",
    "email" => "[email protected]",
    "body" =>
      "expedita maiores dignissimos facilis\nipsum est rem est fugit velit sequi\neum odio dolores dolor totam\noccaecati ratione eius rem velit"
  }
]

Lembre-se de executar a célula com o valor de resposta_completa para poder usá-la.

Com esses dados use casamento de padrões (pattern matching) para:

  1. Extrair o primeiro comentário.
[primeiro_comentario | _] = resposta_completa
primeiro_comentario
  1. Salvar o nome do segundo comentário em uma variável titulo.
[_, segundo_comentario | _] = resposta_completa
titulo = segundo_comentario["name"]
  1. Extrair o comentário de id 11 do produto de productId 3 e criar um novo mapa que contêm todos os valores desse item porém mudando o email para [email protected].
[comentario] =
  Enum.filter(resposta_completa, fn %{"id" => id, "productId" => productId} ->
    id == 11 && productId == 3
  end)

Map.replace(comentario, "email", "[email protected]")
  1. Extrair o body do terceiro comentário.
[_, _, terceiro_comentario | _] = resposta_completa
terceiro_comentario["body"]
  1. Criar uma nova lista removendo o segundo item.
[primeiro_comentario, _ | resto] = resposta_completa
[primeiro_comentario | resto]

Exercicio 5

O Departamento de Astrofísica da NASA entrou em contato com você. Eles precisam organizar as informações dos planetas do Sistema Solar para que os sistemas em Elixir que estão rodando simulações utilizem para processar essas simulações.

Você recebeu uma ficha com as seguintes informações:

Nome Superfície (km²) Volume (km³) Massa (kg) Temperatura Média (ºC) Distância do Sol (km)
Mercúrio 7,48×10^7 6,083×10^10 3,3011×10^23 166,85 57910000
Vênus 4,60×10^8 92,843×10^10 4,8685×10^24 461 108200000
Terra 5,10×10^8 1,08321×10^12 5,9736×10^24 14 149600000
Marte 1,44×10^8 1,6318×10^11 6,4174×10^23 -63 227940000
Júpiter 6,21796×10^10 1,43128×10^15 1,8986×10^27 -108 778330000
Saturno 4,27×10^10 8,2713×10^14 5,6846×10^26 -139 1429400000
Urano 8,1156×10^9 6,833×10^13 8,6810×10^25 -220 2870990000
Netuno 7,6183×10^9 6,254×10^13 1,0243×10^26 -223 4504000000

E com base nesses dados foi pedido que:

  1. Crie um modelo de estrutura (struct) que represente o modelo de um planeta baseado nesses dados fornecidos. Ele deve ter valores default e todos os campos são requeridos.
defmodule Planet do
  @enforce_keys [:surface, :volume, :mass, :temperature, :sun_distance]
  defstruct surface: 0, volume: 0, mass: 0, temperature: 0, sun_distance: 0
end
  1. Crie uma variavel mercurio com as informações deste planeta em uma instância da estrutura que você criou.
mercurio = %Planet{
  surface: 7.48e7,
  volume: 6.083e10,
  mass: 3.3011e23,
  temperature: 166.85,
  sun_distance: 57_910_000
}
  1. Crie uma variavel venus com as informações deste planeta em uma instância da estrutura que você criou.
venus = %Planet{
  surface: 4.60e8,
  volume: 92.843e10,
  mass: 4.8685e24,
  temperature: 461,
  sun_distance: 108_200_000
}
  1. Crie uma variavel terra com as informações deste planeta em uma instância da estrutura que você criou.
terra = %Planet{
  surface: 5.10e8,
  volume: 1.08321e12,
  mass: 5.9736e24,
  temperature: 14,
  sun_distance: 149_600_000
}
  1. Crie uma variavel marte com as informações deste planeta em uma instância da estrutura que você criou.
marte = %Planet{
  surface: 1.44e8,
  volume: 1.6318e11,
  mass: 6.4174e23,
  temperature: -63,
  sun_distance: 227_940_000
}
  1. Crie uma variavel jupiter com as informações deste planeta em uma instância da estrutura que você criou.
jupiter = %Planet{
  surface: 6.21796e10,
  volume: 1.43128e15,
  mass: 1.8986e27,
  temperature: -108,
  sun_distance: 778_330_000
}
  1. Crie uma variavel saturno com as informações deste planeta em uma instância da estrutura que você criou.
saturno = %Planet{
  surface: 4.27e10,
  volume: 8.2713e14,
  mass: 5.6846e26,
  temperature: -139,
  sun_distance: 1_429_400_000
}
  1. Crie uma variavel urano com as informações deste planeta em uma instância da estrutura que você criou.
urano = %Planet{
  surface: 8.1156e9,
  volume: 6.833e13,
  mass: 8.6810e25,
  temperature: -220,
  sun_distance: 2_870_990_000
}
  1. Crie uma variavel netuno com as informações deste planeta em uma instância da estrutura que você criou.
netuno = %Planet{
  surface: 7.6183e9,
  volume: 6.254e13,
  mass: 1.0243e26,
  temperature: -223,
  sun_distance: 4_504_000_000
}
  1. Crie um mapa para representar o sistema solar, ela deve ter as chaves:
  • :estrela: String, valor "Sol"
  • :nome: String, valor "Sistema Solar"
  • :planetas: Lista de %Planeta (struct/estrutura), uma lista contendo todos os planetas criados.

Salve esse mapa criado em uma variável sistema_solar.

sistema_solar = %{
  estrela: "Sol",
  nome: "Sistema Solar",
  planetas: [mercurio, venus, terra, marte, jupiter, saturno, urano, netuno]
}

Se você quiser saber mais, recomendamos abaixo alguns links que podem ser úteis: