Skip to content

Latest commit

 

History

History
291 lines (195 loc) · 10.3 KB

chapter4.livemd

File metadata and controls

291 lines (195 loc) · 10.3 KB

Learn4Elixir - Capítulo 4

Data e Tempo

Neste Capitulo você irá aprender um pouco sobre: Data e Hora, Processos em Elixir e Agentes.

Run in Livebook

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.

Obter a Data e a Hora Atuais

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.

Obter Partes de uma Data e Hora

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.

Manipular Datas e Horas

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}"

Comparar Datas e Horas

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

Processos em Elixir

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.

Criar um Processo

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.

Testando a "vida" de um processo

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.

Enviar e Receber Mensagens entre Processos

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.

Encerrar um Processo

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

Agentes são uma maneira de compartilhar e gerenciar estado em ambientes concorrentes de forma segura. Aqui está um exemplo de como usar agentes.

Inicialize um Agente (Servidor)

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.

Lendo e atualizando o estado

Em seguida, você pode ler ou atualizar seu estado.

Ler o estado do agente

Agent.get(agent, fn state -> state end)

Atualizar o estado do agente

Agent.update(agent, fn _state -> 10 end)

Agora vamos ver como está o estado do agente.

Agent.get(agent, fn state -> state end)

Encerre o Agente

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)

Criando um módulo que usa um agente

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)

Exercícios

Exercício 1

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.

  1. 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.

  2. Crie uma função no módulo Acesso chamada registrar_acesso que recebe o agente e registra a data e hora do acesso atual na lista de acessos.

  3. Crie uma função no módulo Acesso chamada obter_acessos que recebe o agente e retorna a lista de acessos registrados.

  4. 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.

"Coloque aqui sua solução"

Exercício 2

É 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
  # Escreva aqui a sua solução
end