ONLamp.com
oreilly.comSafari Books Online.Conferences.

advertisement


An Introduction to Erlang
Pages: 1, 2, 3, 4

room.erl
-module(room).
-compile(export_all).

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

add_user(Pid, MyName) ->
  room ! {Pid, add, MyName}.

loop(Chatters) ->
  receive
    {From, broadcast, SenderName, Message} ->
      [ Member ! { accept, SenderName, Message } || Member <- Chatters,
                                                    Member =/= From ],
      loop(Chatters);

    {From, add, MyName } ->
      lists:foreach(fun(Member) ->
         Member ! { accept, "Server", MyName ++ " has joined" } end, Chatters ),
      loop([From|Chatters])
  end.
user.erl
-module(user).
-compile(export_all).

create(MyName) ->
  Pid = spawn(fun() -> loop(MyName) end),
  room:add_user(Pid,MyName), Pid.

loop(MyName) ->
  receive
    { accept, SenderName, Message } ->
      io:format("~n~s receives from ~s: ~s ~n", [MyName, SenderName, Message]),
      loop(MyName);

    { say, Message } ->
      io:format("~n~s says ~s ~n",[MyName, Message]),
      room ! { self(), broadcast, MyName, Message },
      loop(MyName)

  end.

This code implements the bare minimum for functional chat application, which include a room that can accept new users and broadcast messages, and a user which can say things or accept messages from the room.



You'll notice our start() function in room.erl introduces a new command, register().

  register(room,spawn(fun() -> loop([]) end)).

Typically, it's best to use the process id that spawn returns for your message passing needs, because it is private and secure. However, in certain situations, you might want to register a process with a label so it is easily accessible. We have done this with room, and it allows us to send messages directly to this process using room ! some_message without knowing its explicit process identifier.

Our room module only matches two patterns, the first of which is a message which initiates a broadcast, where each user in the current list of users (except for the sender) is sent a message asking him to accept the broadcasted text. A list comprehension is used to walk a list of Pids that point to users who have joined the room.

The {From, add, MyName } pattern and associated add_user function simply broadcast a message to all present room members that a new user has entered the room, and then restarts the loop after adding the new user to the head of the list.

This is really all it takes to get basic functionality out of our room, the only important feature that has been left out to keep the example short is disconnecting users from the room.

When we look at the user.erl code, it is somewhat similar in nature, a simple processing loop that handles accepting and sending messages to other users via the room. You can see that when a user is created, she is autojoined to the room, which means the room must be started before any users can be created.

In use, the code you see here looks something like this:

1> room:start().
true
2> Jim = user:create("Jim").
<0.35.0>
3> Joe = user:create("Joe").
<0.37.0>
Jim receives from Server: Joe has joined 

4> Jack = user:create("Jack").
<0.39.0>
Joe receives from Server: Jack has joined 

Jim receives from Server: Jack has joined 

5> Jack ! { say, "Hello" }.
{say,"Hello"}

Jack says Hello 

Joe receives from Jack: Hello 

Jim receives from Jack: Hello 
6> Joe ! { say, "Hi Jack!" }.
{say,"Hi Jack!"}
Joe says Hi Jack! 

Jack receives from Joe: Hi Jack! 

Jim receives from Joe: Hi Jack! 
7> Sarah = user:create("Sarah").
<0.43.0>
Jack receives from Server: Sarah has joined 

Joe receives from Server: Sarah has joined 

Jim receives from Server: Sarah has joined

You can see how the room is acting as a server, broadcasting messages to its users. You can also see how as more users are added, the code simply does the right thing. Although we're just running these from the console with the same IO streams, these could conceivably be passing messages over a network and the code would work just as well.

I consider myself someone with a middling grasp of concurrency at best, and though I'd likely be scratching my head trying to write this in many other languages, I hacked this together in about 20 minutes with only a beginner's level of experience in Erlang. This certainly says a lot for the possibilities of the language to make parallel programming very easy.

Beyond the Basics

In this article, I've tried to cover as much of the essentials as possible without spending too much time on any one topic. There are some important beginner level topics I've skipped, such as Records and Guards. I've also skipped a number of the control structures such as the case() statement, and have not exposed many of the builtin functions that come in handy when programming in Erlang. I've also left out the details about a couple other surprises that are in store for people new to Erlang.

My best suggestion is to pick up Programming Erlang by Joe Armstrong and work your way through it. That's where I got most of the ideas and working knowledge to write this article, and it has much more in depth examples than what you've seen here. You can also check out the #erlang channel on Freenode for some helpful folks to ask for help. The users there convinced me to use this chat example instead of a more trivial timer example that I originally planned, and I hope that is for the better.

As always, I've made the source available for everything you've seen in this article, as well the example that didn't make the cut (see bomb.erl and bomb2.erl).

Seasoned Erlang hackers, please feel free to speak up if there are things in this article that have room for improvement. I'm mostly still at the exploring stage, and though I think this article will be helpful to others who are in the same ballpark, some notes from the experts can never hurt.

Gregory Brown is a New Haven, CT based Rubyist who spends most of his time on free software projects in Ruby. He is the original author of Ruby Reports.


Return to ONLamp.



Sponsored by: