Where you will be when a response from process kicks in?

When I was starting with Elixir one of the hardest concepts to grasp was processes and messages. The topic of this post is to explain how message sending and receiving works. Let's explore how Elixir does it.

At first there is a mailbox for each process. This is where all messages arrive. The key factor is that each process eats up these messages one by one - First in, first out.
Let's spawn a process, send it a greeting and receive "(insert your greeting here) World" back. First things first, we will need to spawn a process.

pid = spawn(fn ->
  receive do
    {parent, greeting} ->
      send(parent, "#{greeting} World!")

Whenever you spawn a process in Elixir you will get back pid (Process ID). We need to bind it to variable pid in order to be able to interact with it later on. spawn/1** is interesting little function. In our case spawn/1 is accepting lambda expression, which this process will execute.

In this function we asked it to wait until someone sends it a message, that is what receive block is for.

The process is wating for a specific message that is a tuple with two elements {parent, greeting}. If a tuple is containing two elements it will get to message processing, where we can acces values that are binded to parent and greeting variables.

On the next line first thing we see is a send/2 function. It is used to send a message to any given process in system. As a first argument it accepts pid and as a second - message that we want to send that process.

At this point it can get confusing, but bare with me.

Let's recap what we have done here

  1. We spawned a process
  2. We told it to wait until someone sends a message to it
  3. When there is a new message arriving, make sure it is a tuple with two elements and bind them to variables parent and greeting
  4. When correct message arrives we process it by sending a message to parent, which is supposedly a process id

With that out of the way, let's see how we will send a message to this process:

send(pid, {self, "Hello"})

As we seen before, we use send/2 in this example. We have stored pid in a variable and now we can interact with it. As explained above, this process is waiting for a message containing tuple with two elements. First element of the tuple is magic keyword self. Under the hood it is just a shorthand to Erlangs function :erlang.self which returns pid of the process you are currently in. And the second element of the tuple is string "Hello".

When we execute this code a message is being sent to our spawned process. First thing it does, is it tries to match it to the expected form {parent, greeting}. In our case it successfully matches and binds elements inside it. parent variable is being set to pid of our "main process"* and greeting is being sent to "Hello" string. We proceed to the next line where we interpolate greeting in to the string and the result is "Hello World!". Again we are using send/2 to send back a message to our main process with our newly created string.

Now what we need to do is to receive a message in our main process. We can easily do so by writing this code:

receive do
  full_hello -> IO.puts(full_hello)

Obviously enough we try to bind our string to variable full_hello and successfully do so. When we proceed to execution of a match, we call IO.puts(full_hello) that prints received string to STDOUT.

Let's recap what happened here:

  1. We sent waiting process a message containing pid of ourselves and a greeting string "Hello"
  2. We opened receive block and went on waiting for a message that can be pattern-matched with to a full_hello variable.

There are few significant moments that happened here. The process we spawned and sent message to worked independently and possibly on other core of your laptop (That is if you are not on the desktop). By then our little process was doing it's job, the main process continued it's work in parallel, even tough only thing it did was to enter a receive block and wait for message to arrive. If our string interpolation process would have sent a response faster than we entered a receive block on the main process, it would have stored this message in our mailbox. Once it did enter receive block, we would start processing the messages one at the time by the order they arrived.

Question arises - What will happen if the receive block will not accept the response, because the format is not correct? Answer is - It will just leave it in the mailbox. In fact, Elixir has a lot of ways to handle malformed messages. Ill try to cover that in the next blog post.

*In fact there is no main process in elixir application. Each process is considered equal in Elixir

**function_name/number where number is used to indicate arity (number of arguments you can pass) of the function.

If you want to continue to learn about processes and message passing, please reffer to Elixir docs page.
Also I highly recommend a book by Dave Thomas - Programming Elixir