In the last week I've been looking at Elixir, a fairly young language built on the Erlang VM.

I really enjoyed how it combines Erlang's way of reasoning with concurrency, fault tolerance and distribution with a simple syntax that made me feel comfortable and productive very quickly.

Concurrency is solved using the actor model. To recap, actors are lightweight processes that communicate asynchronously by sending messages between each other.

Creating a chatroom

To get familiar with Elixir, I’ve created a simple chatroom where people can participate in a common chat.

This is an example I've used in earlier posts when looking at how the actor model is solved in Erlang and Akka.

Since concurrency is a big part of this example and Elixir build on top of Erlang's Actor Model, you'll probably notice a similarity with the Erlang version.

Our example consists of two types of actors: server actors and client actors.

Clients will then be able to connect to the server in order to participate in a chatroom.

First, let's take a look at the server actor

defmodule Server do def start, do: spawn(Server, :init, []) def init do Process.flag(:trap_exit, true) loop([]) end def loop(clients) do receive do {sender, :connect, username} -> Process.link(sender) broadcast({:info, username <> " joined the chat"}, clients) loop([{username, sender} | clients]) {sender, :broadcast, msg} -> broadcast({:new_msg, find(sender, clients), msg}, clients) loop(clients) {:EXIT, pid, _} -> broadcast({:info, find(pid, clients) <> " left the chat."}, clients) loop(clients |> Enum.filter(fn {_, rec} -> rec != pid end)) end end defp broadcast(msg, clients) do Enum.each clients, fn {_, rec} -> send rec, msg end end defp find(sender, [{u, p} | _]) when p == sender, do: u defp find(sender, [_ | t]), do: find(sender, t) end

The first thing we do is to create the Server module. This module contains all code related to our server actor.

Then we got our start function. This function will use spawn to create a process of the init function. This process will then represent our server actor.

For now, let's ignore the Process.flag call and jump straight to the loop function.

In loop , receive will handle the messages that the actor receives from other processes. It decides what to do with the messages using pattern matching.

In our server actor we have three patterns that each represent different kinds of messages.

As you can see, the three patterns are quite similar. They are all tuples of three elements. The lower case words are variables that will be bound to values of the message.

The thing that separates the patterns from each other are the atoms, which are the words prefixed with : .

Atoms are constants where their names simply are their values. This means that two atoms with the same name will always be evaluated as equals.

If a message doesn't match any of our patterns, we should add some error handling. However, I've left this out to make sure the example doesn't grow too big.

Someone is connecting to our chatroom!

The first pattern matches messages from clients that want to connect.

The first thing we do is to link the client's pid (process id). By linking to the process, the server actor is able to monitor the newly connected process.

Next, we broadcast that a new client joined the chat to all clients already in the chatroom.

To send a message to one of our clients, we call the send function together with the pid of the process we want to reach and the message.

Now that we've handled the new client, we'll do a recursive call to ourselves with an updated client list, ready to handle new messages.

Wait, recursive call for each message? How could that scale?

Well, as long as a recursive call is the last thing the function does, we'll be backed up by a tail-call optimization. This optimization jumps back to the start of the function instead of adding new frames to the stack.

A new message in the chatroom!

The next pattern takes care of new messages for the chatroom.

Again, we use the our broadcast function to send the new message to all the clients in the chatroom. But first, we need to find the username of the sender.

find is a private function, hence the defp , that iterates the client list to find the username of a given pid.

As you can see, we got two definitions of the find function. To decide which one to use at a given time, Elixir uses pattern matching against the parameter list.

Someone disconnected...

This is where the fault tolerance comes in.

In the init function, we did a call to Process.flag(:trap_exit, true) . This call will convert the exit signals of linked processes to normal messages that we can match in the receive block.

So, if a client process dies, the server process would be notified and pass this information on to the other clients.

Now, let's take a quick look at the client actor

defmodule Client do def connect(username, server) do spawn(Client, :init, [username, server]) end def init(username, server) do send server, {self, :connect, username} loop(username, server) end def loop(username, server) do receive do {:info, msg} -> IO.puts(~s{[#{username}'s client] - #{msg}}) loop(username, server) {:new_msg, from, msg} -> IO.puts(~s{[#{username}'s client] - #{from}: #{msg}}) loop(username, server) {:send, msg} -> send server, {self, :broadcast, msg} loop(username, server) :disconnect -> exit(0) end end end

As you can see the, the structure is pretty much the same as in the server actor.

We spawn a process that first connects to our server, before entering a recursive function to receive messages.

Let's take our chatroom for a spin

Now that we have our actors in place, let's try them out!

First, let's start our repl, compile the code and start the server.

$ iex iex(1)> c("server.exs") iex(2)> c("client.exs") iex(3)> server = Server.start

Next, let's get a client in there!

iex(4)> c1 = Client.connect("Sam", server) iex(5)> send c1, {:send, "Hi, anyone here?"} [Sam's client] - Sam: Hi, anyone here?

Let's bring in someone for Sam to talk to.

iex(6)> c2 = Client.connect("Mia", server) [Sam's client] - Mia joined the chat iex(7)> c3 = Client.connect("Luke", server) [Mia's client] - Luke joined the chat [Sam's client] - Luke joined the chat iex(8)> send c2, {:send, "Hello!"} [Mia's client] - Mia: Hello! [Luke's client] - Mia: Hello! [Sam's client] - Mia: Hello!

As you can see, the messages about new connections and the messages themselves gets delivered to all the clients. But what about when a client process dies?

iex(28)> send c3, :disconnect [Mia's client] - Luke left the chat. [Sam's client] - Luke left the chat.

As expected, the server noticed that a process died, and notifies the other clients.

For further reading...

If you want to read up on Elixir, make sure to visit Elixir's homepage.

I also recommend getting Programming Elixir, an excellent book by Dave Thomas.

You can find the source code of the examples in this post on GitHub.