Rutas
/
Elixir
Elixir
/
Ejercicios
/
Inspección de calculadora RPN
Inspección de calculadora RPN

Inspección de calculadora RPN

Ejercicio de aprendizaje

Introducción

Enlaces

Los procesos de Elixir están aislados y, de forma predeterminada, no comparten nada entre sí. Cuando un proceso hijo no enlazado falla, su proceso padre no se ve afectado.

Este comportamiento puede cambiarse enlazando procesos entre sí. Si dos procesos están enlazados, un fallo en uno de ellos se propagará al otro. Los enlaces son bidireccionales.

Los procesos se pueden generar ya enlazados al proceso llamador con spawn_link/1, que es una operación atómica, o se pueden enlazar más adelante con Process.link/1.

Enlazar procesos puede ser útil al hacer trabajo en paralelo, cuando cada parte del trabajo no debería continuar si otra parte no llega a terminar.

Capturar salidas

Enlazar también se puede usar para supervisar procesos. Si un proceso captura salidas, no fallará cuando falle un proceso al que está enlazado. En su lugar, recibirá un mensaje sobre el fallo. Esto le permite gestionar el fallo de forma controlada, por ejemplo reiniciando el proceso que falló.

Se puede configurar un proceso para que capture salidas llamando a Process.flag(:trap_exit, true). Ten en cuenta que Process.flag/2 devuelve el valor anterior de la marca, no el nuevo.

El mensaje que se enviará al proceso en caso de que un proceso enlazado falle coincidirá con el patrón {:EXIT, from, reason}, donde from es un PID. Si reason es algo distinto del átomo :normal, significa que el proceso falló o fue terminado a la fuerza.

Tareas

Las tareas son procesos destinados a ejecutar una operación concreta. Normalmente no se comunican con otros procesos, pero pueden devolver un resultado al proceso que inició la tarea.

Las tareas se usan habitualmente para paralelizar trabajo.

async/await

Para iniciar una tarea, usa Task.async/1. Toma una función anónima como argumento y la ejecuta en un proceso nuevo que está enlazado al proceso llamador. Devuelve una estructura %Task{}.

Para obtener el resultado de la ejecución, pasa la estructura %Task{} a Task.await/2. Esperará a que la tarea termine y devolverá su resultado. El segundo argumento es un tiempo de espera en milisegundos, que de forma predeterminada es 5000.

Ten en cuenta que, entre el inicio de la tarea y su espera, el proceso que la inició no queda bloqueado y puede realizar otras operaciones.

Toda tarea iniciada con Task.async/1 debe esperarse, porque enviará un mensaje al proceso llamador. Task.await/2 se puede llamar para cada tarea una sola vez.

Si quieres iniciar una tarea solo por sus efectos secundarios, usa Task.start/1 o Task.start_link/1. Task.start/1 iniciará una tarea que no está enlazada al proceso llamador, y Task.start_link/1 iniciará una tarea que está enlazada al proceso llamador. Ambas funciones devuelven una tupla {:ok, pid}.

Instrucciones

Tu trabajo en Instruments of Texas con una calculadora RPN experimental continúa. Tu equipo ha creado algunos prototipos que deben someterse a una inspección exhaustiva para elegir el mejor, el que pueda fabricarse en serie.

Quieres realizar dos tipos de comprobaciones.

En primer lugar, una comprobación de fiabilidad que detectará las entradas para las que la calculadora inspeccionada se cae o no responde con la suficiente rapidez. Para aislar los fallos, los cálculos de cada entrada deben ejecutarse en un proceso independiente. Enlazar procesos y capturar las salidas en el proceso llamador puede servir para detectar si el cálculo terminó o se cayó.

En segundo lugar, una comprobación de corrección que verificará si, para una entrada dada, el resultado devuelto por la calculadora es el esperado. Solo las calculadoras que ya hayan superado la comprobación de fiabilidad se someterán a la comprobación de corrección, así que las caídas no son un problema. No obstante, las operaciones deben ejecutarse de forma concurrente para acelerar el proceso, lo que la convierte en el caso de uso perfecto para tareas asíncronas.

1. Inicia una comprobación de fiabilidad para una sola entrada

Implementa la función RPNCalculatorInspection.start_reliability_check/2. Debe recibir 2 argumentos: una función (la calculadora) y una entrada para la calculadora. Debe devolver un mapa que contenga la entrada y el PID del proceso generado.

El proceso generado debe llamar a la función calculadora dada con la entrada dada. El proceso debe estar enlazado al proceso llamador.

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

2. Interpreta los resultados de una comprobación de fiabilidad

Implementa la función RPNCalculatorInspection.await_reliability_check_result/2. Debe recibir dos argumentos. El primer argumento es un mapa con la entrada de la comprobación de fiabilidad y el PID del proceso que ejecuta la comprobación de fiabilidad para esta entrada, tal y como lo devuelve RPNCalculatorInspection.start_reliability_check/2. El segundo argumento es un mapa que sirve como acumulador de los resultados de las comprobaciones de fiabilidad con distintas entradas.

La función debe esperar un mensaje de salida.

Si recibe un mensaje de salida ({:EXIT, from, reason}) con el motivo :normal del mismo proceso que ejecuta la comprobación de fiabilidad, debe devolver el mapa de resultados con el valor :ok añadido bajo la clave input.

Si recibe un mensaje de salida con un motivo distinto del mismo proceso que ejecuta la comprobación de fiabilidad, debe devolver el mapa de resultados con el valor :error añadido bajo la clave input.

Si no recibe ningún mensaje que cumpla esos criterios en 100 ms, debe devolver el mapa de resultados con el valor :timeout añadido bajo la clave 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. Ejecuta una comprobación de fiabilidad concurrente para muchas entradas

Implementa la función RPNCalculatorInspection.reliability_check/2. Debe recibir 2 argumentos: una función (la calculadora) y una lista de entradas para la calculadora.

Para cada entrada de la lista, debe iniciar la comprobación de fiabilidad en un nuevo proceso enlazado usando start_reliability_check/2. Después, para cada proceso iniciado de esta forma, debe esperar sus resultados usando await_reliability_check_result/2.

Antes de iniciar ningún proceso, la función debe marcar el proceso actual para capturar las salidas, de modo que pueda recibir mensajes de salida. Después, debe restablecer esta marca a su valor original.

La función debe devolver un mapa con los resultados de las comprobaciones de fiabilidad de todas las entradas.

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. Ejecuta una comprobación de corrección concurrente para muchas entradas

Implementa la función RPNCalculatorInspection.correctness_check/2. Debe recibir 2 argumentos: una función (la calculadora) y una lista de entradas para la calculadora.

Para cada entrada de la lista, debe iniciar una tarea asíncrona que llame a la calculadora con la entrada dada. Después, para cada tarea iniciada de esta forma, debe esperar sus 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 en GitHub El enlace se abre en una ventana o pestaña nueva
Elixir Exercism

¿Listo para empezar Inspección de calculadora RPN?

Regístrate en Exercism para aprender y dominar Elixir con 58 conceptos168 ejercicios y mentoría humana real, todo gratis.