F# Mailbox vs MailboxProcessor
Asked Answered
V

4

0

I notice the Mailbox type is encapsulated and can only be used through the use of the MailboxProcessor.

It implies that to have an agent to which I can post messages, I'm forced to have a single Mailbox of a single type (or use the existing MailboxProcessor in an exotic way).

Should I understand that having multiple Mailbox for a single workflow would inherently result in a bad design? The Ccr clearly gives you that level of freedom.

Edit: As Daniel pointed out, if one wants to send multiple message types, DUs elegantly solves the problem - and it's not like I haven't done that in the past.

But the question is, isn't doing that a code smell? Wouldn't adding more types of messages sent to an agent over time lead you into have too many responsibilities? I sometimes think it would be important to always encapsulate the message types the agent consumes behind an interface such that this information is never exposed.

Ventail answered 12/12, 2011 at 22:14 Comment(0)
V
0

I think I may have found what I was looking for. I have listened to Rich Hickey's talk (Are we there yet) at least 5 times and I believe his approach solves many of the design concerns I had. Obviously this can be implemented with either F# Mailboxes or CAS references.

I really recommend it and would be happy to hear some feedback.

Ventail answered 20/6, 2012 at 17:55 Comment(0)
S
5

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.

Serrano answered 12/12, 2011 at 23:37 Comment(4)
Very instructive thanks. Clearly had to clarify my question :DVentail
@Tomas, I notice that you always use AsyncReplyChannel, even on a seemingly one-way message like Put. Is there a reason for this? Does awaiting the reply prevent out-of-order messages? I would have thought that the MailboxProcessor should work fine receiving messages only via Post.Uraninite
@RyanRiley I wouldn't use AsyncReplyChannel on a simple message that just invokes some action of the agent. However, I used it here mainly because I thought that the user of the agent may want to wait until the agent handles the previous Put message. For example, when using the one-place buffer to implement a (simple) producer/consumer pattern (with just 1 element store in the middle). Using Post is definitely fine if you just want to send some information to the agent without waiting. (But it is true that the COmega based version here doesn't wait on Put, so the two are different).Serrano
@Tomas I moved my question to a new question. Thanks for responding. Maybe you could follow up over there?Uraninite
D
1

Generally the message type is a discriminated union, which allows for various kinds of messages within a single mailbox. Does that not work in your case?

Dorolisa answered 12/12, 2011 at 22:30 Comment(6)
Definitely not trying to solve a problem here... this is purely a design consideration.Ventail
I guess I'm not following. I thought you were suggesting a mailbox being limited to a single message type is problematic, but a DU circumvents that "limitation."Dorolisa
Agreed, but that doesn't say much about the design qualities of such an approach. But I wasn't clear, I will update my question.Ventail
@DavidGrenier: I'm not familiar with CCR, so maybe I'm missing your point. But I'd hardly consider DUs a code smell in this context. In fact, I can't think of a data structure that better fits the domain. If you're worried about one MailboxProcessor having too many responsibilities, you could always delegate to specialized processors behind the scenes.Dorolisa
Perhaps my mistake is to see Agent based programming as a development paradigm rather than a low level implementation detail (like an array is) and that design can be considered at a higher level whether you use agents or not. I may have been mislead by this paper: icc.mpei.ru/documents/00000827.pdfVentail
Agent-based programming is probably somewhere in the middle. Agents are certainly higher-level than locks, but they can be tucked away, as well. Erlang, for instance, exposes its actors, at least in the code I've seen. Disclaimer: I've never written any Erlang. .NET certainly lends itself to the Java-style OOP with interfaces, etc., which would allow you to hide agents under those constructs. See MiniRx, which is now in FSharpx.Uraninite
U
1

I don't think you would ever be able to successfully work with a mailbox using only one type of message, unless you use something like the ISubject type from the Reactive Extensions. Messages come in different forms, and all are important. The two primary examples I can think of are:

  1. Control messages - denote operations the mailbox should undertake such as clearing its queue, looking for specific messages, shutting down, spinning up child processes, etc.
  2. Data messages - sending and receiving (Put / Get) are the general types of these.

You are correct in thinking that you would most likely want to restrict the data messages to a certain type, but technically a DU is one type with many alternatives. If you were to go for the same approach as Luca with his initial, dynamic approach in L'Agent, I think both he and I would agree that too many types in one mailbox is a bit of a challenge.

Uraninite answered 13/12, 2011 at 16:21 Comment(5)
Aren't the two points you bring low-level implementation details? 1-Clearly, 2- This would invariably result in a very CRUD-like agent? I certainly would want all of that well encapsulated behind an interface otherwise I'm just getting implementation details leaking everywhere in my application - which partly confirms the assumptions in my question.Ventail
I found that working with one type often leads to an interface and polymorphic implementations. I have worked successfully with this approach years ago in C#, however I found that IoC containers also ended being involved and a whole slew of setup logic and construction. I feel like DI and mailboxes quite nicely solve this problem.Ketchum
David Grenier Be aware that theres probably a memory leak in L'Agent. See my post here: moiraesoftware.com/?p=505Ketchum
Thanks, this is more along the lines of what I was looking for. I voted to close my question nonetheless.Ventail
Great point about a CRUD-like agent. I didn't mean for the list to be exhaustive, just exemplary, which I may not have achieved. :) I really just hoped to demonstrate that a single message type was generally insufficient for a generally useful agent. That said, Rx includes several instances of ISubject that are really more like proxies, so I could be wrong, though I don't know how well an app would work with only agents of a single, non-DU type. I definitely agree with Dave's point above re: DI and interfaces.Uraninite
V
0

I think I may have found what I was looking for. I have listened to Rich Hickey's talk (Are we there yet) at least 5 times and I believe his approach solves many of the design concerns I had. Obviously this can be implemented with either F# Mailboxes or CAS references.

I really recommend it and would be happy to hear some feedback.

Ventail answered 20/6, 2012 at 17:55 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.