Neste Capitulo você irá aprender um pouco sobre: Data e Hora, Processos em Elixir e Agentes.
No Elixir, você pode usar o módulo DateTime para trabalhar com datas e horas. O Elixir possui uma biblioteca robusta para manipulação de datas e horas, que permite realizar diversas operações, como formatação, cálculos e comparações. Aqui estão alguns exemplos de como usar datas e horas.
Você pode obter a data e hora atuais usando a função DateTime.now/2 (o segundo argumento não é obrigatório). Por exemplo:
{:ok, data_hora} = DateTime.now("Etc/UTC")
" #{data_hora}: Data e hora agora no horário do Tempo Universal Coordenado (UTC)."
Observe que o resultado da expressão acima vai mudar a cada vez que você avaliar a célula.
Você pode acessar partes específicas de uma data e hora, como o ano, mês, dia, hora, minuto e segundo, usando os campos 'year', 'month', 'day', etc.
{:ok, data_hora} = DateTime.now("Etc/UTC")
"Ano: #{data_hora.year}, Mês: #{data_hora.month}, Dia: #{data_hora.day}"
Veja mais opções em Date.
Você pode realizar operações de manipulação de datas e horas, como adicionar ou subtrair dias, horas, minutos, etc., usando as funções disponíveis no módulo DateTime. Por exemplo, para adicionar 3 dias a partir da data atual:
{:ok, current_datetime} = DateTime.now("Etc/UTC")
future_datetime = DateTime.add(current_datetime, 3, :day)
"Data daqui a 3 dias: #{future_datetime}"
Você pode comparar datas e horas com '>', '<', '==' e outros operadores de comparação.
{:ok, current_datetime} = DateTime.now("Etc/UTC")
future_datetime = DateTime.add(current_datetime, 3, :day)
if future_datetime > current_datetime do
"A data futura é posterior à data atual."
else
"A data futura não é posterior à data atual."
end
Os processos são a principal unidade de concorrência em Elixir e permitem que você execute tarefas em paralelo, compartilhe dados e crie sistemas concorrentes. Aqui estão alguns exemplos de como usar processos.
Para criar um processo em Elixir, você pode usar a função spawn/1 ou Task.start/1.
Inicialmente veja abaixo um exemplo do uso de spawn/1.
meu_pid = spawn(fn -> 0 end)
O que aconteceu? Elixir iniciou um processo que executou uma função que não recebe nenhuma argumento e retorna 0. O que 'spawn' retornou? O identificador do processo (PID).
O que posso fazer com este PID? Várias coisas. Por exemplo, posso perguntar se o processo está vivo.
Process.alive?(meu_pid)
Quando eu chego a avaliar a célula acima, o processo já foi executado e já morreu. Por esta razão, 'Process.alive?(meu_pid)' retorna 'false'.
Agora veja um exemplo usando Task.start/1 para criar um processo que imprime "Hello, World!" após um atraso de 2 segundos.
task =
Task.start(fn ->
:timer.sleep(2000)
IO.puts("Hello, World!")
end)
Observe que no caso acima incluímos um 'IO.puts' para poder ver o que o processo fez. Observe também que o Task.start retornou uma tupla contendo :ok e o PID.
Agora vamos criar um processo que dura um pouco mais de tempo e vamos pedir para que você avalie a segunda célula sem esperar o fim da avaliação da primeira. Avalie a segunda célula várias vezes. Assim, você verá que inicialmente o retorno será 'true' mas depois de 4 segundos o retorno será 'false'.
{:ok, minha_tarefa} =
Task.start(fn ->
IO.puts("Começando a contar 4 segundos!")
:timer.sleep(4000)
IO.puts("Hello, World!")
end)
Process.alive?(minha_tarefa)
Observe também que "Hello, World!" aparecerá na célula que estiver ativa no momento.
Os processos em Elixir podem se comunicar entre si por meio do envio e recebimento de mensagens. Você pode usar a função send/2 para enviar mensagens e receive/1 para receber mensagens. Aqui está um exemplo de envio e recebimento de mensagens entre dois processos:
receptor =
spawn(fn ->
receive do
{:mensagem, msg} ->
IO.puts("Mensagem Recebida: #{msg}")
end
end)
emissor =
spawn(fn ->
send(receptor, {:mensagem, "Oi, de outro processo!"})
end)
No exemplo acima, criamos um processo 'receptor' que fica esperando receber uma tupla contendo :mensagem e uma mensagem. Depois criamos um processo 'emissor' que envia uma mensagem para 'receptor'. Logo depois de enviar a mensagem, 'emissor' morre. Logo depois de receber a mensagem, 'receptor' escreve a (usando IO.puts) no dispositivo de saída (no caso, uma célula do Livebook). Depois disso, 'receptor' morre.
Você pode encerrar um processo usando a função Process.exit/2 ou deixando que ele termine naturalmente. Por exemplo, para encerrar um processo.
Process.exit(receptor, :shutdown)
Agentes são uma maneira de compartilhar e gerenciar estado em ambientes concorrentes de forma segura. Aqui está um exemplo de como usar agentes.
Comece inicializando um agente que conterá o estado que você deseja compartilhar. Você pode fazer isso em uma célula do Livebook. Este agenda ficará disponível nas próximas células.
# Inicialize um agente com um estado inicial
{:ok, agent} = Agent.start(fn -> 0 end)
Neste exemplo, o agente é inicializado com um estado inicial de 0.
Em seguida, você pode ler ou atualizar seu estado.
Agent.get(agent, fn state -> state end)
Agent.update(agent, fn _state -> 10 end)
Agora vamos ver como está o estado do agente.
Agent.get(agent, fn state -> state end)
Inicialmente, verifique se o agente está vivo.
Process.alive?(agent)
Após concluir a interação com o agente, você pode encerrá-lo para liberar os recursos associados. Você pode fazer isso da seguinte forma:
# Encerre o agente
:ok = Agent.stop(agent)
Agora, verifique se o agente morreu mesmo.
Process.alive?(agent)
defmodule MeuAgente do
def inicia do
{:ok, agent} = Agent.start(fn -> 0 end)
IO.puts("Agente criado com pid #{inspect(agent)} e estado 0")
agent
end
def le_estado(agent) do
IO.puts("Lendo o estado do agente")
estado = Agent.get(agent, fn state -> state end)
IO.puts("O estado do agente é #{estado}")
end
def incrementa_estado(agent) do
IO.puts("Incrementando o estado do agente")
Agent.update(agent, fn state -> state + 1 end)
IO.puts("O estado do agente foi incrementado.")
end
def encerra(agent) do
Agent.stop(agent)
IO.puts("Agente com pid #{inspect(agent)} foi encerrado.")
end
end
Agora vamos usar o módulo acima. Observe que colocamos vários 'IO.puts' no código para poder ver o que acontece.
meuagente = MeuAgente.inicia()
MeuAgente.le_estado(meuagente)
MeuAgente.incrementa_estado(meuagente)
MeuAgente.le_estado(meuagente)
MeuAgente.incrementa_estado(meuagente)
MeuAgente.le_estado(meuagente)
MeuAgente.encerra(meuagente)
Vamos criar um sistema simples para rastrear o número de acessos a uma página da web usando Agentes em Elixir e registrar a data e hora de cada acesso.
-
Crie um módulo chamado
Acesso
que inicializa um agente com um estado inicial de uma lista vazia para armazenar os acessos. O estado deve ser uma lista de tuplas contendo a data e hora do acesso. -
Crie uma função no módulo
Acesso
chamadaregistrar_acesso
que recebe o agente e registra a data e hora do acesso atual na lista de acessos. -
Crie uma função no módulo
Acesso
chamadaobter_acessos
que recebe o agente e retorna a lista de acessos registrados. -
Agora, crie uma interação no Livebook para demonstrar o uso do módulo
Acesso
. Inicie o agente, registre alguns acessos e, em seguida, obtenha a lista de acessos registrados.
Escreva sua solução na célula abaixo. Se necessário, crie mais células.
defmodule Acesso do
def inicia do
{:ok, agent} = Agent.start(fn -> [] end)
IO.puts("Agente criado com pid #{inspect(agent)} e estado []")
agent
end
def registrar_acesso(agent, data_hora_acesso) do
IO.puts("Reginstrando o acesso")
Agent.update(agent, fn state -> [{:ok, data_hora_acesso} | state] end)
IO.puts("O acesso foi registrado.")
end
def obter_acessos(agent) do
IO.puts("Lendo o estado do agente")
estado = Agent.get(agent, fn state -> state end)
IO.puts("O estado do agente é #{inspect(estado)}")
end
def encerra(agent) do
Agent.stop(agent)
IO.puts("Agente com pid #{inspect(agent)} foi encerrado.")
end
end
É uma boa prática gravar o aniversário de alguém, e não a sua idade no banco de dados, pois com a data de nascimento temos dados mais precisos e flexíveis, facilitando o cálculo da idade em tempo real, sem a necessidade de atualizar a idade toda vez que um ano passa.
Com essa informação em mente, imagine que você é um desenvolvedor de sistemas e precisa criar uma função em Elixir que receba uma data, e se for aniversário deverá returnar um texto com "Hoje você completa anos, parabéns pelo aniversário!", caso não seja o aniversáio, a função deve retornar somente o seguinte texto: " anos". Essa função aceita um formato {ano, mês, dia} e calcula a idade da pessão em relação a data atual.
Por exemplo, se a data de nascimento fornecida for {2001, 5, 7}, e a função for chamada em 7 de maio de 2023, o resultado deve ser "22 anos", caso seja aniversário o resultado deve ser, Hoje você completa 22 anos, parabéns pelo aniversário!".
Requisitos:
A função deve aceitar a data de nascimento como argumento e retornar um texto com a idade dentro.
Você deve levar em conta os anos bissextos ao calcular a diferença nos anos.
Considere o caso em que a data de nascimento fornecida está no futuro em relação à data atual, e lide com isso de maneira apropriada.
Este exercício permitirá que você pratique a manipulação de datas em Elixir, incluindo o cálculo de anos bissextos, a diferença entre datas e a formatação dos resultados.
def calcular_idade(data_aniversario) do
{ano, mes, dia} = data_aniversario
{:ok, cur_datetime} = DateTime.now("Etc/UTC")
cur_dia = cur_datetime.day
cur_mes = cur_datetime.month
cur_ano = cur_datetime.year
anos = cur_ano - ano
plural_s = if anos == 1, do: "", else: "s"
{:ok, formatted_birthdate} = Date.from_iso8601("#{ano}-#{mes}-#{dia}")
date_difference = Date.diff(cur_datetime, formatted_birthdate)
cond do
date_difference < 0 -> "Data inválida"
dia == cur_dia && mes == cur_mes -> "Hoje você completa #{anos} ano#{plural_s}, parabéns pelo aniversário!"
true -> "#{anos} ano#{plural_s}"
end
end