Percursos
/
Elixir
Elixir
/
Exercícios
/
Tirar uma senha
Tirar uma senha

Tirar uma senha

Exercício de aprendizagem

Introdução

Processos

Em Elixir, todo o código corre dentro de processos.

Por predefinição, uma função é executada no mesmo processo a partir do qual foi chamada. Quando precisas de executar explicitamente uma determinada função num novo processo, usa spawn/1:

spawn(fn -> 2 + 2 end)
# => #PID<0.125.0>

spawn/1 cria um novo processo que executa a função de aridade 0 fornecida e devolve um identificador de processo (PID). O novo processo mantém-se vivo enquanto a função estiver a executar e, depois, termina silenciosamente.

Os processos de Elixir não devem ser confundidos com processos do sistema operativo. Os processos de Elixir usam muito menos memória e CPU. Não há problema nenhum em ter aplicações Elixir que correm centenas de processos Elixir.

Mensagens

Os processos não partilham informação diretamente entre si. Os processos enviam e recebem mensagens para partilhar dados.

Podes enviar uma mensagem a qualquer processo com send/2. O primeiro argumento de send/2 é o PID do destinatário; o segundo argumento é a mensagem.

Uma mensagem pode ser de qualquer tipo. Muitas vezes, é composta por átomos e tuplas. Se quiseres obter uma resposta, deves incluir o PID do remetente algures na mensagem. Podes obter o PID do processo atual com self().

send/2 não verifica se a mensagem foi recebida pelo destinatário nem se o destinatário ainda está vivo. A mensagem acaba na caixa de correio do destinatário e só é lida se e quando o destinatário pedir explicitamente para receber mensagens.

Uma mensagem pode ser lida de uma caixa de correio com a macro receive/1. Aceita um bloco do que pode fazer correspondência de padrões com as mensagens.

receive do
  {:ping, sender_pid} -> send(sender_pid, :pong)
  :do_nothing -> nil
end

receive/1 retira uma mensagem da caixa de correio que corresponda a qualquer dos padrões indicados e executa a expressão indicada para esse padrão. Se não houver mensagens na caixa de correio, ou se nenhuma das mensagens na caixa de correio corresponder a algum dos padrões, receive/1 fica à espera de uma.

Ciclo de receção

Se quiseres receber mais do que uma mensagem, tens de chamar receive/1 recursivamente. É um padrão comum implementar uma função recursiva, por exemplo com o nome loop, que chama receive/1, faz algo com a mensagem e depois se chama a si própria para esperar por mais mensagens. Se precisares de transportar algum estado de uma chamada a receive/1 para outra, podes fazê-lo passando um argumento a essa função loop.

PIDs

Os identificadores de processo são um tipo de dados próprio. Funcionam como endereços de caixa de correio: se tiveres o PID de um processo, podes enviar uma mensagem a esse processo. Os PIDs são normalmente criados indiretamente, como valor devolvido de funções que criam novos processos, como spawn.

Instruções

Estás a escrever um sistema embebido para uma máquina de senhas. É um modelo muito simples. Consegue emitir números consecutivos e indicar qual foi o último número emitido.

1. Inicia a máquina

Implementa a função start/0. Deve criar um novo processo e devolver o PID desse processo. O novo processo ainda não precisa de fazer nada.

TakeANumber.start()
# => #PID<0.138.0>

Repara que, cada vez que executas este código, o PID pode ser diferente.

2. Indica o estado da máquina

Altera a máquina para que o processo recém-criado esteja pronto para receber mensagens (inicia um ciclo de receção) com um estado inicial de 0. Deve conseguir receber mensagens {:report_state, sender_pid}. Como resposta a essas mensagens, deve enviar o seu estado atual (o número da última senha emitida) a sender_pid e depois aguardar mais mensagens.

machine_pid = TakeANumber.start()

# a client sending a message to the machine
send(machine_pid, {:report_state, self()})

# a client receiving a message from the machine
receive do
  msg -> msg
end

# => 0

3. Emite números

Altera a máquina para que consiga receber mensagens {:take_a_number, sender_pid}. Deve aumentar o seu estado em 1, enviar o novo estado a sender_pid e depois aguardar mais mensagens.

machine_pid = TakeANumber.start()

# a client sending a message to the machine
send(machine_pid, {:take_a_number, self()})

# a client receiving a message from the machine
receive do
  msg -> msg
end

# => 1

4. Para a máquina

Altera a máquina para que consiga receber uma mensagem :stop. Deve deixar de aguardar mais mensagens.

5. Ignora mensagens inesperadas

Altera a máquina para que, quando recebe uma mensagem inesperada, a ignore e continue a aguardar mais mensagens.

Editar via GitHub A ligação abre numa nova janela ou separador
Elixir Exercism

Estás pronto para começar Tirar uma senha?

Inscreve-te no Exercism para aprenderes e dominares Elixir com 58 conceitos168 exercícios, e mentoria humana real, tudo grátis.