Thursday, December 06, 2007

Erlang Eureka!

State Stinks! Part II discusses that managing the operational control of an application is problematic when the application is multi-threaded. Multi-threaded applications consist of concurrently operating execution paths that share common state and data.

Jeff Moser suggested that I look into the Erlang programming language. One of his blog postings led me to an excellent interview with Joe Armstrong, the principal inventor of Erlang. Joe is an engaging individual and has many interesting things to say. Joe prefers "concurrent orientation" over "object orientation" and he makes a compelling point. Some of my past posts reflect my opinion that strongly favors object orientation. I also ruminated on threads and the odd pairing of concurrency and object orientation. Basically this is the kind of stuff that peaks my interest so I proceeded to download Erlang.

First of all, everything went well, the Erlang web site is well organized and full of resources. Erlang has been around since 1991 and is a very well documented, mature programming language.

I started working my way through the tutorials, looking forward to that "eureka" moment when I would see Erlang's secret sauce. To begin with, Erlang's functional approach with (immutable) variables that can only be bound once, is well chronicled. Other features of the language such as dynamic-typing combined with assignment by pattern-matching makes for a feature-rich, expressive language that you just have to try for yourself.

My eureka moment?

init(Module) ->
register(Module, self(),
State = Module:init(),
loop(Module, State).

loop(Module, State) ->
receive
{call, From, Request} ->
{Result, State2} = Module:handle_call(Request, State),
From ! {Module, Result},
loop(Module, State2);
{cast, Request} ->
State2 = Module:handle_cast(Request, State),
loop(Module, State2)
end.
Note the function loop(Module, State). Erlang variables are capitalized. loop has two parameters Module and State. I won't discuss Module, think of it as a file containing a group of helper functions.

State is an immutable value containing our application's operational state.
First, the receive statement blocks the thread that loop is executing and awaits a message (from another thread) that contains data that matches one of the two tuples (variables enclosed in curly braces) either {call, From, Request}, or {cast, Request}.

If the message matches {call, From, Request}, the next three lines in the code will execute. First, the Request is delegated to one of the handle_call helper function in Module. Note that the immutable State is passed along. The updated state is returned to variable State2.

Next, the calling thread identified by From is messaged (!) with data {Module, Result}.

Finally, loop is called for its next iteration with the updated state present in the State2 variable.

It is important to note that the above code fragment is not pseudo-code. The code demonstrates sending and receiving messages between threads and how messages are used to share state.

At the top of this post I mentioned that an application's threads share common state. Erlang threads do not directly share common state but must share state as immutable variables passed using messages. Erlang calls threads "processes" because they do not share state.

Eureka! - Erlang's secret sauce isn't very secret, just saucy. State is maintained via very simple and explicit operations that are designed to scale up into large, robust systems. The code fragment did not show any detail about State/State2 and how State was updated (e.g. inside the Module:handle_call function).

Note that Erlang doesn't remove the need to manage state, in fact, state within a Erlang application may be very complex if it uses multiple "processes" to handle computations.

Erlang allows us to use its full toolkit to simply, safely and explicitly work with the data used to maintain the operational state of a software application.

2 comments:

Jeff Moser said...

It's interesting that after all these years of software development, we're finding the biggest bang for our concurrent/service buck is to write solutions that mimic passing around a paper document like the world did back in the early 1900s.

Great to hear you're liking Erlang. Quite a few of its ideas are making their way into F#. In addition, I'm convinced that declarative ways to express concurrency (like ParallelFX) are what's going to help the most as the rest of the programming world figures out that the free lunch is over.

Mike Petry said...

It's funny that in order to solve the problems of the day, we need to go back and remove the solutions to yesterdays problems.

This is why software development is so interesting. Take carpentry for example, there is an adage, measure twice, cut once. In software, we enter the field at measure twice, cut once, which evolves into just cut - boards are cheap.