Percursos
/
Elixir
Elixir
/
Exercícios
/
Inspeção da calculadora RPN
Inspeção da calculadora RPN

Inspeção da calculadora RPN

Exercício de aprendizagem

Introdução

Ligações

Os processos de Elixir são isolados e, por predefinição, não partilham nada. Quando um processo filho que não está ligado falha, o processo pai não é afetado.

Este comportamento pode ser alterado ligando processos uns aos outros. Se dois processos estiverem ligados, uma falha num processo será propagada para o outro processo. As ligações são bidirecionais.

Os processos podem ser criados já ligados ao processo que os invoca com spawn_link/1, que é uma operação atómica, ou podem ser ligados mais tarde com Process.link/1.

Ligar processos pode ser útil em trabalho paralelizado, quando cada pedaço de trabalho não deve continuar se outro pedaço não conseguir terminar.

Capturar saídas

Ligar também pode ser usado para supervisionar processos. Se um processo capturar saídas, não falha quando um processo ao qual está ligado falha. Em vez disso, recebe uma mensagem sobre a falha. Isto permite-lhe lidar com a falha de forma controlada, por exemplo reiniciando o processo que falhou.

Um processo pode ser configurado para capturar saídas chamando Process.flag(:trap_exit, true). Repara que Process.flag/2 devolve o valor antigo do sinalizador, não o novo.

A mensagem que será enviada ao processo caso um processo ligado falhe segue o padrão {:EXIT, from, reason}, em que from é um PID. Se reason for algo diferente do átomo :normal, isso significa que o processo falhou ou foi terminado à força.

Tarefas

As tarefas são processos destinados a executar uma operação específica. Normalmente não comunicam com outros processos, mas podem devolver um resultado ao processo que iniciou a tarefa.

As tarefas são frequentemente usadas para paralelizar trabalho.

async/await

Para iniciar uma tarefa, usa Task.async/1. Recebe uma função anónima como argumento e executa-a num novo processo ligado ao processo que a invoca. Devolve uma estrutura %Task{}.

Para obter o resultado da execução, passa a estrutura %Task{} a Task.await/2. Vai esperar que a tarefa termine e devolver o seu resultado. O segundo argumento é um tempo limite em milissegundos, com o valor predefinido de 5000.

Repara que, entre iniciar e aguardar a tarefa, o processo que a iniciou não fica bloqueado e pode realizar outras operações.

Qualquer tarefa iniciada com Task.async/1 deve ser aguardada, porque envia uma mensagem ao processo que a invoca. Só se pode chamar Task.await/2 uma vez por tarefa.

Se quiseres iniciar uma tarefa apenas para efeitos secundários, usa Task.start/1 ou Task.start_link/1. Task.start/1 inicia uma tarefa que não está ligada ao processo que a invoca, e Task.start_link/1 inicia uma tarefa ligada ao processo que a invoca. Ambas as funções devolvem um tuplo {:ok, pid}.

Instruções

O teu trabalho na Instruments of Texas numa calculadora RPN experimental continua. A tua equipa construiu alguns protótipos que precisam de passar por uma inspeção minuciosa, para escolher o melhor, o que possa ser produzido em série.

Queres fazer dois tipos de verificações.

Em primeiro lugar, uma verificação de fiabilidade, que deteta os valores de entrada para os quais a calculadora em inspeção falha ou não responde com a rapidez suficiente. Para isolar as falhas, os cálculos de cada valor de entrada têm de ser executados num processo separado. A ligação de processos e a captura de saídas no processo que invoca podem ser usadas para detetar se o cálculo terminou ou falhou.

Em segundo lugar, uma verificação de correção, que verifica se, para um determinado valor de entrada, o resultado devolvido pela calculadora é o esperado. Só as calculadoras que já passaram na verificação de fiabilidade é que são submetidas à verificação de correção, por isso as falhas não são um problema. No entanto, as operações devem ser executadas em simultâneo para acelerar o processo, o que faz disto o caso de uso perfeito para tarefas assíncronas.

1. Iniciar uma verificação de fiabilidade para um único valor de entrada

Implementa a função RPNCalculatorInspection.start_reliability_check/2. Deve receber 2 argumentos: uma função (a calculadora) e um valor de entrada para a calculadora. Deve devolver um mapa que contém o valor de entrada e o PID do processo criado.

O processo criado deve chamar a função calculadora recebida com o valor de entrada recebido. O processo deve ficar ligado ao processo que o invoca.

RPNCalculatorInspection.start_reliability_check(fn _ -> 0 end, "2 3 +")
# => %{input: "2 3 +", pid: #PID<0.169.0>}

2. Interpretar os resultados de uma verificação de fiabilidade

Implementa a função RPNCalculatorInspection.await_reliability_check_result/2. Deve receber dois argumentos. O primeiro argumento é um mapa com o valor de entrada da verificação de fiabilidade e o PID do processo que está a executar a verificação de fiabilidade para esse valor de entrada, tal como devolvido por RPNCalculatorInspection.start_reliability_check/2. O segundo argumento é um mapa que serve de acumulador para os resultados de verificações de fiabilidade com valores de entrada diferentes.

A função deve aguardar uma mensagem de saída.

Se receber uma mensagem de saída ({:EXIT, from, reason}) com o motivo :normal do mesmo processo que executa a verificação de fiabilidade, deve devolver o mapa de resultados com o valor :ok adicionado sob a chave input.

Se receber uma mensagem de saída com um motivo diferente do mesmo processo que executa a verificação de fiabilidade, deve devolver o mapa de resultados com o valor :error adicionado sob a chave input.

Se não receber nenhuma mensagem que cumpra estes critérios em 100 ms, deve devolver o mapa de resultados com o valor :timeout adicionado sob a chave input.

# when an exit message is waiting for the process in its inbox
send(self(), {:EXIT, pid, :normal})

RPNCalculatorInspection.await_reliability_check_result(
  %{input: "5 7 -", pid: pid},
  %{}
)

# => %{"5 7 -" => :ok}

# when there are no messages in the process inbox
RPNCalculatorInspection.await_reliability_check_result(
  %{input: "3 2 *", pid: pid},
  %{"5 7 -" => :ok}
)

# => %{"5 7 -" => :ok, "3 2 *" => :timeout}

3. Executar uma verificação de fiabilidade em simultâneo para vários valores de entrada

Implementa a função RPNCalculatorInspection.reliability_check/2. Deve receber 2 argumentos: uma função (a calculadora) e uma lista de valores de entrada para a calculadora.

Para cada valor de entrada da lista, deve iniciar a verificação de fiabilidade num novo processo ligado, utilizando start_reliability_check/2. Depois, para cada processo iniciado desta forma, deve aguardar os seus resultados, utilizando await_reliability_check_result/2.

Antes de iniciar quaisquer processos, a função tem de marcar o processo atual para capturar saídas, de forma a conseguir receber mensagens de saída. Depois disso, deve repor esta marca no valor original.

A função deve devolver um mapa com os resultados das verificações de fiabilidade de todos os valores de entrada.

fake_broken_calculator = fn input ->
  if String.ends_with?(input, "*"), do: raise("oops")
end

inputs = ["2 3 +", "10 3 *", "20 2 /"]

RPNCalculatorInspection.reliability_check(fake_broken_calculator, inputs)
# => %{
#       "2 3 +" => :ok,
#       "10 3 *" => :error,
#       "20 2 /" => :ok
#     }

4. Executar uma verificação de correção em simultâneo para vários valores de entrada

Implementa a função RPNCalculatorInspection.correctness_check/2. Deve receber 2 argumentos: uma função (a calculadora) e uma lista de valores de entrada para a calculadora.

Para cada valor de entrada da lista, deve iniciar uma tarefa assíncrona que vai chamar a calculadora com o valor de entrada recebido. Depois, para cada tarefa iniciada desta forma, deve aguardar os seus resultados durante 100 ms.

fast_cheating_calculator = fn input -> 14 end
inputs = ["13 1 +", "50 2 *", "1000 2 /"]
RPNCalculatorInspection.correctness_check(fast_cheating_calculator, inputs)
# => [14, 14, 14]
Editar via GitHub A ligação abre numa nova janela ou separador
Elixir Exercism

Estás pronto para começar Inspeção da calculadora RPN?

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