I think that F# agents using MailboxProcessor
and CCR implement a different programming model, but I believe that both are equally powerful, although there are definitely problems that could be more nicely solved with one or the other, so it would be nice to have another library for F# built around mailboxes. The programming model based on CCR is probably more clearly described in various languages based on join calculus such as COmega (which is an old MSR project).
For example, you can compare an implementation of one-place buffer using COmega and F# agents:
public class OnePlaceBuffer {
private async empty();
private async contains(string s);
public OnePlaceBuffer() { empty(); }
public void Put(string s) & empty() {
contains(s);
}
public string Get() & contains(string s) {
empty();
return s;
}
}
In this example, the async methods behave like mailboxes (so there are four of them: empty
, contains
, Put
and Get
) and the bodies behave like handlers that will be triggered when a combination of mailboxes contains a value (i.e. when you put into an empty buffer or when you get from a full buffer). In F#, you could use MailboxProcessor
and write:
type Message<'T> =
| Put of 'T * AsyncReplyChannel<unit>
| Get of AsyncReplyChannel<'T>
MailboxProcessor.Start(fun agent ->
let rec empty = agent.Scan(function
| Put(v, repl) -> repl.Reply(); Some(full(v))
| _ -> None)
and full v = agent.Scan(function
| Get repl -> repl.Reply(v); Some(empty)
| _ -> None)
empty )
The two implementations express the same ideas, but in a slightly different way. In F#, empty
and full
are two functions that represent different state of the agent and messages sent to the agent represent different aspect of the agent's state (the pending work to do). In the COmega implementation, all state of the program is captured by the mailboxes.
I guess that separating the state of the agent from the immediate messages that need to be processed might make it easier to think about F# MailboxProcessor
a bit, but that's just an immediate thought with no justification...
Finally, in a realistic application that uses MailboxProcessor
in F#, you'll quite likely use a larger number of them and they will be connected in some way. For example, implementing pipelining is a good example of an application that uses multiple MailboxProcessor
instances (that all have some simple runnning asynchronous workflow associated with them, of course). See this article for an example.