Tracks
/
Elixir
Elixir
/
Exercises
/
Take-A-Number
Take-A-Number

Take-A-Number

Learning Exercise

Introduction

Processes

In Elixir, all code runs inside processes.

By default, a function will execute in the same process from which it was called. When you need to explicitly run a certain function in a new process, use spawn/1:

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

spawn/1 creates a new process that executes the given 0-arity function and returns a process identifier (PID). The new process will stay alive as long as the function executes, and then silently exit.

Elixir's processes should not be confused with operating system processes. Elixir's processes use much less memory and CPU. It's perfectly fine to have Elixir applications that run hundreds of Elixir processes.

Messages

Processes do not directly share information with one another. Processes send and receive messages to share data.

You can send a message to any process with send/2. The first argument to send/2 is the PID of the recipient, the second argument is the message.

A message can be of any type. Often it consists of atoms and tuples. If you want to get a response, you should include the PID of the sender somewhere in the message. You can get the PID of the current process with self().

send/2 does not check if the message was received by the recipient, nor if the recipient is still alive. The message ends up in the recipient's mailbox and it will only be read if and when the recipient explicitly asks to receive messages.

A message can be read from a mailbox using the receive/1 macro. It accepts a do block that can pattern match on the messages.

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

receive/1 will take one message from the mailbox that matches any of the given patterns and execute the expression given for that pattern. If there are no messages in the mailbox, or none of messages in the mailbox match any of the patterns, receive/1 is going to wait for one.

Receive loop

If you want to receive more than one message, you need to call receive/1 recursively. It is a common pattern to implement a recursive function, for example named loop, that calls receive/1, does something with the message, and then calls itself to wait for more messages. If you need to carry some state from one receive/1 call to another, you can do it by passing an argument to that loop function.

PIDs

Process identifiers are their own data type. They function as mailbox addresses - if you have a process's PID, you can send a message to that process. PIDs are usually created indirectly, as a return value of functions that create new processes, like spawn.

Instructions

You are writing an embedded system for a Take-A-Number machine. It is a very simple model. It can give out consecutive numbers and report what was the last number given out.

1. Start the machine

Implement the start/0 function. It should spawn a new process and return the process's PID. The new process doesn't need to do anything yet.

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

Note that each time you run this code, the PID may be different.

2. Report the machine state

Modify the machine so that the newly spawned process is ready to receive messages (start a receive loop) with an initial state of 0. It should be able to receive {:report_state, sender_pid} messages. As a response to those messages, it should send its current state (the last given out ticket number) to sender_pid and then wait for more messages.

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. Give out numbers

Modify the machine so that it can receive {:take_a_number, sender_pid} messages. It should increase its state by 1, send the new state to sender_pid, and then wait for more messages.

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 the machine

Modify the machine so that it can receive a :stop message. It should stop waiting for more messages.

5. Ignore unexpected messages

Modify the machine so that when it receives an unexpected message, it ignores it and continues waiting for more messages.

Edit via GitHub The link opens in a new window or tab
Elixir Exercism

Ready to start Take-A-Number?

Sign up to Exercism to learn and master Elixir with 57 concepts, 159 exercises, and real human mentoring, all for free.