Designing Erlang#

time to read 4 min | 636 words

I am currently reading another Erlang book, and I am one again impressed by the elegance of the language. Just to be clear, I don't have any intention to actually implement what I am talking about here, this is merely a way to organize my thoughts. And to avoid nitpickers, yes, I know of Retlang.

Processes

Erlang's processes are granular and light weight. There is no real equivalent for OS threads or processes or even to .Net's AppDomains. A quick, back of the envelope, design for this in .Net would lead to the following interface:

public class ErlangProcess
{
	public ErlangProcess(ICommand cmd);
        public static ProcessHandle Current { get; } 
	public MailBox MailBox { get; }
	public IDictionary Dictionary { get; }

	public Action Execution { get; set; }
        public Func<Message> ExecutionFilter { get; set; }
}

A couple of interesting aspects of the design, we always start a process with a command. But we allow waiting using a delegate + filter. This is quite intentional, the reason is the spawn() API call, which looks like this:

public ProcessHandle Spawn<TCommand>();

We do not allow to pass an instance, only the type. The reason for that is that passing instances between processes opens up a chance for threading bugs. For that matter, we don't give you back a reference to the process either, just a handle to it.

Messages

Message passing is an important concept for Erlang, and something that I consider to be more and more essential to the way I think about software. Sending a message is trivial, all you need to do is call:

public void Send(ProcessHandle process, object msg);

Receiving  a message is a lot more complex, because you may have multiple receivers or conditional receivers. C#'s lack of pattern match syntax is really annoying in this regard (Boo has that, though :-) ). But I was able to come up with the following API:

public void Recieve<TMsg>(
	Action<TMsg> process);

public void Recieve<TMsg>(
	Expression<Func<TMsg, bool>> condition, 
	Action<TMsg> process);

A simple example of using this API would be:

public class PMap : ICommand
{
	List<object> results = new List<object>();

	public void Execute()
	{
		this.Receive(delegate(MapMessage msg)
		{
			foreach(var item in msg.Items)
			{
				Spawn<ActionExec>().Send(new ActionExecMessage(Self(), msg.Action, item));
				this.Recieve(delegate(ProcessedItemMessage itemMsg)
				{
					results.Add(itemMsg.Item);
					if(results.Count == msg.Items.Length)
						msg.Parent.Send(new ResultsMessage( results.ToArray() ));
				});
			}
		});
	}
}

This demonstrate a couple of important ideas. Chief among them is how we actually communicate between processes. Another interesting issue is the actual execution of this. Note that we have no threading involved, we are just registering to be notified at some date. When we have no more receivers registered, the process dies.

Execution environment

The processes should executed by something. In this case, I think we can define a very simple set of rules for the scheduler:

  • A process is in runnable state if it is has a message to process.
  • A process is in stopped state if there are no messages matching the current receivers list.
  • A process with no receivers is done, and will be killed.
  • A process may only run on a single thread at any given point.
  • It is allowed to move processes between threads (no thread affinity).
  • Processes have no priorities, but there is a preference for LIFO scheduling.
  • A process unit of work is the processing of a single message (not sure about that, though).
  • Since the only thing that can wake a process is a message, the responsibility for moving a process from stopped to runnable is at the hands of the process MailBox implementation.

Okay, that is enough for now.

Comments?