Try something different with Erlang

The last week, I've been playing around with Erlang and I've really enjoyed it. It was first released in 1986, so it's definitely not a new language. However, I'm pretty sure that working with Erlang would give a lot of the programmers out there a new experience.

Erlang is a functional language with a Prolog inspired syntax. It's a language designed to create reliable systems with a main focus on concurrency, fault tolerance and distribution.

Concurrency is solved using the actor model. In Erlang actors are lightweight processes that communicate by sending messages between each other.

To get to know Erlang, I've created a simple chatroom. The idea is to have a server process that client processes can connect to. For simplicity, I've left out distributing the processes, but I may cover this in a later post.

This results in two types of actors, a server actor and a client actor.

Let's start by taking a look at the server actor.


start() -> spawn(fun() -> loop([]) end).

loop(Clients) ->  
  process_flag(trap_exit, true),
    {From, connect, Username} ->
      broadcast(join, Clients, {Username}),
      loop([{Username, From} | Clients]);
    {From, send, Msg} ->
      broadcast(new_msg, Clients, {find(From, Clients), Msg}),
    {'EXIT', From, _} ->
      broadcast(disconnect, Clients, {find(From, Clients)}),
      loop(remove(From, Clients));
    _ ->

broadcast(join, Clients, {Username}) ->  
  broadcast({info, Username ++ " joined the chat."}, Clients);
broadcast(new_msg, Clients, {Username, Msg}) ->  
  broadcast({new_msg, Username, Msg}, Clients);
broadcast(disconnect, Clients, {Username}) ->  
  broadcast({info, Username ++ " left the chat."}, Clients).

broadcast(Msg, Clients) ->  
  lists:foreach(fun({_, Pid}) -> Pid ! Msg end, Clients).

find(From, [{Username, Pid} | _]) when From == Pid ->  
find(From, [_ | T]) ->  
  find(From, T).

remove(From, Clients) ->  
  lists:filter(fun({_, Pid}) -> Pid =/= From end, Clients).

It all begins with the start function. Here we call spawn, which create a new process of an anonymous function that then calls loop. For now, let's ignore the process_flag function and go straight to receive. This function receives messages from other processes, which in our case would be the clients. It then use pattern matching to figure out what to do with the message.

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

The first two patterns matches a tuple with three elements. The first and the last element are variables, hence the capital letter at the beginning. The middle element however is an atom. An atom is just a constant with a name. So as you may suspect, the atom is the difference between the two patterns.

It's also worth noting that the From variable is expected to contain the pid of the sender.

Let's go through the different patterns to see what would happen if a match occurred.
connect - Cool, someone is trying to connect to our chat!

The first we do is to call the link function. This will help our process to monitor the connected process.

Next we call our own function called broadcast, that will make sure to notify all the other clients that a new client has connected. To send the messages to other processes, Erlang uses the ! operator with the process id on the left side and the message on the right.

As a final step, we recursively call loops with our new client added to the client list. Erlang is optimized for this kind of recursion, so it will handle it without problems.

new_msg - A new message for the chatroom!

Again, we call the broadcast function. But why can we use it for both connect and new_msg? We want to broadcast different things, right? Well, broadcast also uses pattern matching to figure out what to do. So as you may start to realise, pattern matching has an important role in Erlang.

EXIT??? - Aww, someone disconnected

This is where the fault tolerance comes in. To explain this, let's pick up the thread about the process_flag at the start of the loop function. This call will convert the exit signals to normal messages that we can receive just as any other message. This exit message will match the last pattern.

How cool isn't that? If a client process dies, the server process would get notified and pass this information on to the other clients.

Next, let's look at how the client is put together


connect(Username, Server) ->  
  spawn(fun() -> start(Username, Server) end).

start(Username, Server) ->  
  Server ! {self(), connect, Username},
  loop(Server, Username).

loop(Server, User) ->  
    {send, Msg} ->
      Server ! {self(), send, Msg},
      loop(Server, User);
    {new_msg, From, Msg} ->
      io:format("[~s's client] - ~s: ~s~n", [User, From, Msg]),
      loop(Server, User);
    {info, Msg} ->
      io:format("[~s's client] - ~s~n", [User, Msg]),
      loop(Server, User);
    disconnect ->
        io:format("[~s's client] - Disconnected~n", [User]),
    _ ->
      io:format("[~s's client] - Error - Unknown command~n", [User]),
      loop(Server, User)

As you can see, the structure looks a lot like our server. It spawns a start function that connects to the server before calling loop which receives messages.

I'm not going through this actor in detail, because you get the idea about how it works based on the explanation of the server process. Instead, let's try out the chatroom.

Let the chatting begin!

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

1> c(server).  

2> c(client).  

3> S = server:start().  

Next, let's get a client in there!

4> C1 = client:connect("Sam",S).

5> C1 ! {send, "Hi, anyone here?"}.  
[Sam's client] - Sam: Hi, anyone here?

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

6> C2 = client:connect("Mia",S).  
[Sam's client] - Mia joined the chat.

7> C3 = client:connect("Luke",S).  
[Mia's client] - Luke joined the chat.
[Sam's client] - Luke joined the chat.

8> C2 ! {send, "Hello!"}.  
[Luke's client] - Mia: Hello!
[Mia's client] - Mia: Hello!
[Sam's client] - Mia: Hello!

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

9> C3 ! disconnect.  
[Luke's client] - Disconnected
[Mia's client] - Luke left the chat.
[Sam's client] - Luke left the chat.

As expected, the server noticed that a process has died, and notifies the others.

Hopefully you've enjoyed this as much as I have. I'm just amazed with how easy it is to solve problems that we find so difficult in more mainstream languages. I'm definitely going to use more time in Erlang. It's loads of fun, and I love when a language makes you think in new ways.

You can find the source code of the example on GitHub.