트랙
/
Elixir
Elixir
/
연습 문제
/
RPN 계산기 검사
RPN 계산기 검사

RPN 계산기 검사

학습 연습 문제

소개

링크

Elixir의 프로세스는 서로 격리되어 있어서 기본적으로 아무것도 공유하지 않아요. 링크되지 않은 자식 프로세스가 충돌해도 부모 프로세스는 영향을 받지 않아요.

이 동작은 프로세스끼리 링크 하는 것으로 바꿀 수 있어요. 두 프로세스가 링크되어 있으면, 한 프로세스의 실패가 다른 프로세스로 전파돼요. 링크는 양방향이에요.

spawn_link/1을 사용하면 호출한 프로세스에 이미 링크된 상태로 프로세스를 생성할 수 있는데, 이는 원자적 연산이에요. 또는 나중에 Process.link/1로 링크할 수도 있어요.

링크는 병렬 작업을 할 때 유용해요. 어떤 작업 조각이 끝나지 못하면 다른 조각도 계속 진행하지 않아야 하는 경우에 말이죠.

종료 트랩하기

링크는 프로세스를 감독 하는 데에도 사용할 수 있어요. 프로세스가 종료를 트랩 하면, 링크된 프로세스가 충돌해도 자신은 충돌하지 않아요. 대신 충돌에 대한 메시지를 받아요. 이를 통해 충돌을 우아하게 처리할 수 있는데, 예를 들어 충돌한 프로세스를 다시 시작하는 식이에요.

Process.flag(:trap_exit, true)를 호출하면 프로세스가 종료를 트랩하도록 설정할 수 있어요. 참고로 Process.flag/2는 플래그의 새 값이 아니라 이전 값을 반환해요.

링크된 프로세스가 충돌하면 그 프로세스에 전송되는 메시지는 {:EXIT, from, reason} 패턴과 일치해요. 여기서 from은 PID예요. reason이 :normal 원자가 아니라면, 그 프로세스가 충돌했거나 강제로 종료된 것이에요.

태스크

태스크는 하나의 특정 작업을 실행하기 위한 프로세스예요. 보통 다른 프로세스와 통신하지 않지만, 태스크를 시작한 프로세스에 결과를 반환할 수는 있어요.

태스크는 작업을 병렬화할 때 흔히 사용해요.

async/await

태스크를 시작하려면 Task.async/1을 사용해요. 이 함수는 익명 함수를 인자로 받아서, 호출한 프로세스에 링크된 새 프로세스에서 실행해요. 그러고 나서 %Task{} 구조체를 반환해요.

실행 결과를 얻으려면 %Task{} 구조체를 Task.await/2에 넘겨요. 그러면 태스크가 끝날 때까지 기다렸다가 결과를 반환해요. 두 번째 인자는 밀리초 단위의 타임아웃이고, 기본값은 5000이에요.

참고로, 태스크를 시작하고 기다리는 사이에 태스크를 시작한 프로세스는 블록되지 않고 다른 작업을 할 수도 있어요.

Task.async/1로 시작한 태스크는 반드시 기다려야 해요. 호출한 프로세스에 메시지를 보내기 때문이에요. Task.await/2는 각 태스크마다 한 번만 호출할 수 있어요.

부수 효과만을 위해 태스크를 시작하려면 Task.start/1이나 Task.start_link/1을 사용해요. Task.start/1은 호출한 프로세스에 링크되지 않은 태스크를 시작하고, Task.start_link/1은 호출한 프로세스에 링크된 태스크를 시작해요. 두 함수 모두 {:ok, pid} 튜플을 반환해요.

지침

_Instruments of Texas_에서 실험용 RPN 계산기를 만드는 작업은 계속되고 있어요. 팀에서는 대량 생산할 수 있는 최적의 하나를 고르기 위해, 철저한 검사를 거쳐야 하는 프로토타입 몇 개를 만들어 두었어요.

두 가지 종류의 검사를 진행하려고 해요.

먼저, 검사 대상 계산기가 충돌하거나 충분히 빠르게 응답하지 못하는 입력값을 찾아내는 신뢰성 검사예요. 실패를 격리하려면 각 입력값에 대한 계산을 별도의 프로세스에서 실행해야 해요. 호출한 프로세스에서 링크와 exit 트랩을 사용하면 계산이 완료됐는지 충돌했는지를 감지할 수 있어요.

둘째, 주어진 입력값에 대해 계산기가 반환한 결과가 예상대로인지 확인하는 정확성 검사예요. 신뢰성 검사를 이미 통과한 계산기만 정확성 검사를 거치므로, 충돌은 걱정할 필요가 없어요. 다만 과정을 빠르게 하려면 연산을 동시에 실행해야 하는데, 이는 비동기 작업에 안성맞춤인 활용 사례예요.

1. 단일 입력값에 대한 신뢰성 검사 시작하기

RPNCalculatorInspection.start_reliability_check/2 함수를 구현해요. 인자 두 개, 즉 함수(계산기)와 계산기에 전달할 입력값을 받아야 해요. 입력값과 생성된 프로세스의 PID를 담은 맵을 반환해야 해요.

생성된 프로세스는 주어진 계산기 함수를 주어진 입력값으로 호출해야 해요. 이 프로세스는 호출한 프로세스에 링크되어야 해요.

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

2. 신뢰성 검사 결과 해석하기

RPNCalculatorInspection.await_reliability_check_result/2 함수를 구현해요. 인자 두 개를 받아야 해요. 첫 번째 인자는 RPNCalculatorInspection.start_reliability_check/2가 반환한 것처럼, 신뢰성 검사의 입력값과 이 입력값에 대한 신뢰성 검사를 실행하는 프로세스의 PID를 담은 맵이에요. 두 번째 인자는 서로 다른 입력값에 대한 신뢰성 검사 결과를 모아 두는 맵이에요.

이 함수는 exit 메시지를 기다려야 해요.

신뢰성 검사를 실행하는 같은 프로세스로부터 이유가 :normal인 exit 메시지({:EXIT, from, reason})를 받으면, 결과 맵의 input 키 아래에 :ok 값을 추가하여 반환해야 해요.

신뢰성 검사를 실행하는 같은 프로세스로부터 다른 이유의 exit 메시지를 받으면, 결과 맵의 input 키 아래에 :error 값을 추가하여 반환해야 해요.

100ms 안에 그러한 조건에 맞는 메시지를 받지 못하면, 결과 맵의 input 키 아래에 :timeout 값을 추가하여 반환해야 해요.

# 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. 여러 입력값에 대한 동시 신뢰성 검사 실행하기

RPNCalculatorInspection.reliability_check/2 함수를 구현해요. 인자 두 개, 즉 함수(계산기)와 계산기에 전달할 입력값 목록을 받아야 해요.

목록의 모든 입력값에 대해 start_reliability_check/2를 사용하여 새로 링크된 프로세스에서 신뢰성 검사를 시작해야 해요. 그런 다음 이렇게 시작한 모든 프로세스에 대해 await_reliability_check_result/2를 사용하여 결과를 기다려야 해요.

프로세스를 시작하기 전에, 함수는 exit 메시지를 받을 수 있도록 현재 프로세스가 exit을 트랩하도록 플래그를 설정해야 해요. 그런 다음 이 플래그를 원래 값으로 되돌려야 해요.

함수는 모든 입력값에 대한 신뢰성 검사 결과를 담은 맵을 반환해야 해요.

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. 여러 입력값에 대한 동시 정확성 검사 실행하기

RPNCalculatorInspection.correctness_check/2 함수를 구현해요. 인자 두 개, 즉 함수(계산기)와 계산기에 전달할 입력값 목록을 받아야 해요.

목록의 모든 입력값에 대해 주어진 입력값으로 계산기를 호출할 비동기 작업을 시작해야 해요. 그런 다음 이렇게 시작한 모든 작업에 대해 100ms 동안 결과를 기다려야 해요.

fast_cheating_calculator = fn input -> 14 end
inputs = ["13 1 +", "50 2 *", "1000 2 /"]
RPNCalculatorInspection.correctness_check(fast_cheating_calculator, inputs)
# => [14, 14, 14]
GitHub에서 편집 링크가 새 창이나 탭에서 열려요
Elixir Exercism

RPN 계산기 검사 문제를 시작해 볼 준비가 됐나요?

Exercism에 가입하고 Elixir 트랙을 개념 58개연습 문제 168개, 그리고 실제 사람의 멘토링과 함께 배우고 익혀 보세요. 모두 무료예요.