트랙
/
Elixir
Elixir
/
연습 문제
/
번호표 뽑기
번호표 뽑기

번호표 뽑기

학습 연습 문제

소개

프로세스

Elixir에서는 모든 코드가 프로세스 안에서 실행돼요.

함수는 기본적으로 자신을 호출한 프로세스에서 실행돼요. 특정 함수를 새로운 프로세스에서 명시적으로 실행해야 할 때는 spawn/1을 사용해요.

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

spawn/1은 주어진 인자가 0개인 함수를 실행하는 새로운 프로세스를 만들고, 프로세스 식별자(PID)를 반환해요. 새 프로세스는 함수가 실행되는 동안 살아 있다가 조용히 종료돼요.

Elixir의 프로세스를 운영체제 프로세스와 혼동하면 안 돼요. Elixir의 프로세스는 메모리와 CPU를 훨씬 적게 사용해요. Elixir 애플리케이션이 수백 개의 Elixir 프로세스를 실행해도 전혀 문제없어요.

메시지

프로세스는 서로 정보를 직접 공유하지 않아요. 프로세스는 데이터를 공유하기 위해 메시지를 주고받아요.

send/2를 사용하면 어떤 프로세스에든 메시지를 보낼 수 있어요. send/2의 첫 번째 인자는 받는 프로세스의 PID이고, 두 번째 인자는 메시지예요.

메시지는 어떤 타입이든 될 수 있어요. 흔히 아톰과 튜플로 이루어져 있어요. 응답을 받고 싶다면 메시지 어딘가에 보내는 프로세스의 PID를 넣어야 해요. 현재 프로세스의 PID는 self()로 얻을 수 있어요.

send/2는 메시지가 수신자에게 전달되었는지, 수신자가 아직 살아 있는지 확인하지 않아요. 메시지는 수신자의 _메일박스_에 들어가고, 수신자가 명시적으로 메시지 수신을 요청할 때에만 읽혀요.

메시지는 receive/1 매크로를 사용해 메일박스에서 읽을 수 있어요. receive/1은 메시지를 패턴 매칭할 수 있는 do 블록을 받아요.

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

receive/1은 주어진 패턴 중 하나에 일치하는 _메시지 하나_를 메일박스에서 꺼내서, 그 패턴에 해당하는 표현식을 실행해요. 메일박스에 메시지가 없거나, 메일박스의 어떤 메시지도 주어진 패턴에 일치하지 않으면, receive/1은 메시지 하나를 기다려요.

수신 루프

메시지를 두 개 이상 받고 싶다면 receive/1을 재귀적으로 호출해야 해요. receive/1을 호출하고, 메시지로 무언가를 한 다음, 다시 자신을 호출해 더 많은 메시지를 기다리는 재귀 함수를 만드는 것이 흔한 패턴이에요. 예를 들어 그 함수의 이름을 loop라고 지을 수 있어요. 한 receive/1 호출에서 다음 호출로 어떤 상태를 넘겨야 한다면, 그 loop 함수에 인자를 전달하면 돼요.

PID

프로세스 식별자는 그 자체로 하나의 데이터 타입이에요. 프로세스 식별자는 메일박스 주소 역할을 해요. 어떤 프로세스의 PID를 알고 있다면 그 프로세스에 메시지를 보낼 수 있어요. PID는 보통 spawn처럼 새로운 프로세스를 만드는 함수의 반환 값으로 간접적으로 만들어져요.

지침

번호표 발권기를 위한 임베디드 시스템을 작성하고 있어요. 아주 단순한 모델이에요. 이 기계는 연속된 번호를 내주고, 마지막으로 내준 번호가 무엇이었는지 알려줄 수 있어요.

1. 기계 시작하기

start/0 함수를 구현해요. 이 함수는 새 프로세스를 생성하고 그 프로세스의 PID를 반환해야 해요. 새 프로세스는 아직 아무것도 하지 않아도 돼요.

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

이 코드를 실행할 때마다 PID는 다를 수 있어요.

2. 기계 상태 보고하기

새로 생성된 프로세스가 초기 상태 0으로 메시지를 받을 준비를 하도록, 즉 receive 루프를 시작하도록 기계를 수정해요. 이 프로세스는 {:report_state, sender_pid} 메시지를 받을 수 있어야 해요. 그런 메시지에 대한 응답으로 자신의 현재 상태(마지막으로 내준 번호)를 sender_pid에게 보내고, 그런 다음 다시 메시지를 기다려요.

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. 번호 내주기

{:take_a_number, sender_pid} 메시지를 받을 수 있도록 기계를 수정해요. 상태를 1만큼 증가시키고, 새로운 상태를 sender_pid에게 보낸 다음, 다시 메시지를 기다려요.

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. 기계 멈추기

:stop 메시지를 받을 수 있도록 기계를 수정해요. 그러면 더 이상 메시지를 기다리지 않아요.

5. 예상치 못한 메시지 무시하기

예상치 못한 메시지를 받았을 때, 그 메시지를 무시하고 계속해서 다음 메시지를 기다리도록 기계를 수정해요.

GitHub에서 편집 링크가 새 창이나 탭에서 열려요
Elixir Exercism

번호표 뽑기 문제를 시작해 볼 준비가 됐나요?

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