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.
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.
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!
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.
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).
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 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ê?
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
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
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!
"""
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("🎉🎉🎉")
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.
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.
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.
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).
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]
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 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}
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, :idade, especie: "", localizacao: ""]
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: "Ana Bastos", idade: 20, cidade: "São Paulo"}
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}
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(5)
is_integer(67_900)
is_float(1.78)
is_binary("Foo")
is_atom(true)
is_boolean(false)
is_map(%{nome: "Foo", idade: 100})
is_tuple({:nome, "Bar", :idade, 9999})
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
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.pow(2, 2) - 4 * 1 * 1
x = :math.sqrt(delta) / 2 * 1
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:
- Extrair o primeiro comentário.
[primeiro | _] = resposta_completa
primeiro["body"]
- Salvar o nome do segundo comentário em uma variável
titulo
.
[_, segundo | _] = resposta_completa
titulo = segundo["name"]
- Extrair o comentário de id
11
do produto de productId3
e criar um novo mapa que contêm todos os valores desse item porém mudando oemail
para[email protected]
.
[_, _, _, _, res | _] = resposta_completa
Map.put(res, "email", "[email protected]")
- Extrair o body do terceiro comentário.
[_, _, ter | _] = resposta_completa
ter["body"]
- Criar uma nova lista removendo o segundo item.
[prim, _ | resto] = resposta_completa
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:
- 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 Planeta do
@enforce_keys [:nome, :superficie, :volume, :massa, :temp_media, :dist_sol]
defstruct nome: "", superficie: 0, volume: 0, massa: 0, temp_media: 0, dist_sol: 0
end
- Crie uma variavel
mercurio
com as informações deste planeta em uma instância da estrutura que você criou.
# Mercúrio 7,48×10^7 6,083×10^10 3,3011×10^23 166,85 57910000
mercurio = %Planeta{
nome: "Mercúrio",
superficie: 7.48e7,
volume: 6.083e10,
massa: 3.3011e23,
temp_media: 166.85,
dist_sol: 57_910_000
}
- Crie uma variavel
venus
com as informações deste planeta em uma instância da estrutura que você criou.
# Vênus 4,60×10^8 92,843×10^10 4,8685×10^24 461 108200000
venus = %Planeta{
nome: "Vênus",
superficie: 4.60e8,
volume: 92.843e10,
massa: 4.8685e24,
temp_media: 461,
dist_sol: 108_200_000
}
- Crie uma variavel
terra
com as informações deste planeta em uma instância da estrutura que você criou.
# Terra 5,10×10^8 1,08321×10^12 5,9736×10^24 14 149600000
terra = %Planeta{
nome: "Terra",
superficie: 5.10e8,
volume: 1.08321e12,
massa: 5.9736e24,
temp_media: 14,
dist_sol: 149_600_000
}
- Crie uma variavel
marte
com as informações deste planeta em uma instância da estrutura que você criou.
# Marte 1,44×10^8 1,6318×10^11 6,4174×10^23 -63 227940000
marte = %Planeta{
nome: "Marte",
superficie: 1.44e8,
volume: 1.6318e11,
massa: 6.4174e23,
temp_media: -63,
dist_sol: 227_940_000
}
- Crie uma variavel
jupiter
com as informações deste planeta em uma instância da estrutura que você criou.
# Júpiter 6,21796×10^10 1,43128×10^15 1,8986×10^27 -108 778330000
jupiter = %Planeta{
nome: "Júpiter",
superficie: 6.21796e10,
volume: 1.43128e15,
massa: 1.8986e27,
temp_media: -108,
dist_sol: 778_330_000
}
- Crie uma variavel
saturno
com as informações deste planeta em uma instância da estrutura que você criou.
# Saturno 4,27×10^10 8,2713×10^14 5,6846×10^26 -139 1429400000
saturno = %Planeta{
nome: "Saturno",
superficie: 4.27e10,
volume: 8.2713e14,
massa: 5.6846e26,
temp_media: -139,
dist_sol: 1_429_400_000
}
- Crie uma variavel
urano
com as informações deste planeta em uma instância da estrutura que você criou.
# Urano 8,1156×10^9 6,833×10^13 8,6810×10^25 -220 2870990000
urano = %Planeta{
nome: "Urano",
superficie: 8.1156e9,
volume: 6.833e13,
massa: 8.6810e25,
temp_media: -220,
dist_sol: 2_870_990_000
}
- Crie uma variavel
netuno
com as informações deste planeta em uma instância da estrutura que você criou.
# Netuno 7,6183×10^9 6,254×10^13 1,0243×10^26 -223 4504000000
netuno = %Planeta{
nome: "Netuno",
superficie: 7.6183e9,
volume: 6.254e13,
massa: 1.0243e26,
temp_media: -223,
dist_sol: 4_504_000_000
}
- 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:
- Elixir School - Site que contém várias "lições" em português.
- Livros e outros recursos de aprendizagem
- Elixir Brasil - Comunidade no Telegram
- Elixir em Foco - podcast em português