我注意到邮箱类型是封装的,只能通过使用MailboxProcessor来使用。
这意味着要拥有一个可以发布消息的代理,我不得不拥有一个单一类型的邮箱(或以异乎寻常的方式使用现有的邮箱处理器)。
我是否应该了解在单个工作流程中使用多个邮箱会导致设计错误? Ccr清楚地给你这种自由度。
编辑: 正如丹尼尔指出的那样,如果想要发送多种消息类型,DU优雅地解决了这个问题 - 而且这并不像我以前没有这样做过。
但问题是,这不是代码味道吗?随着时间的推移,不会添加更多类型的消息发送给代理会导致您承担太多责任吗?我有时认为始终封装代理在接口后面消息的消息类型是很重要的,这样就不会暴露这些信息。
答案 0 :(得分:4)
我认为使用MailboxProcessor
和CCR的F#代理实现了不同的编程模型,但我相信两者都同样强大,尽管肯定有一些问题可以通过其中一个更好地解决,所以它很高兴为邮箱建立另一个F#库。基于CCR的编程模型可能会基于 join calculus (例如COmega(这是一个旧的MSR项目))以各种语言更清晰地描述。
例如,您可以使用COmega和F#代理比较一位缓冲区的实现:
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;
}
}
在此示例中,异步方法的行为类似于邮箱(因此其中有四个:empty
,contains
,Put
和Get
),并且主体的行为类似当邮箱组合包含值时(即当将放入空缓冲区或从获取时)将触发的处理程序em>完整的缓冲区)。在F#中,您可以使用MailboxProcessor
并写:
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 )
这两个实现表达了相同的想法,但方式略有不同。在F#中,empty
和full
是表示代理的不同状态的两个函数,发送给代理的消息代表代理状态的不同方面(要执行的待处理工作)。在COmega实现中,邮箱捕获程序的所有状态。
我想将代理的状态与需要处理的即时消息分开可能会让人更容易思考F#MailboxProcessor
,但这只是一个没有正当理由的直接思考...... / p>
最后,在F#中使用MailboxProcessor
的实际应用程序中,您很可能会使用更多数量的它们,并且它们将以某种方式连接。例如,实现流水线操作是使用多个MailboxProcessor
实例的应用程序的一个很好的示例(当然,所有实例都有一些与之关联的简单运行异步工作流)。有关示例,请参阅this article。
答案 1 :(得分:1)
通常,消息类型是一个有区别的联合,它允许单个邮箱中的各种消息。这不适合你的情况吗?
答案 2 :(得分:1)
除非您使用Reactive Extensions中的ISubject
类型,否则我认为您不能仅使用一种类型的邮件成功使用邮箱。消息有不同的形式,一切都很重要。我能想到的两个主要例子是:
您认为您很可能希望将数据消息限制为某种类型是正确的,但从技术上讲,DU是一种具有许多替代方案的类型。如果你在L'Agent中使用他最初的,动态的方法采用与Luca相同的方法,我认为他和我都同意在一个邮箱中有太多类型是一个挑战。
答案 3 :(得分:0)
我想我可能找到了我想要的东西。我已经听过Rich Hickey的演讲(我们还在那里)至少5次,我相信他的方法解决了我的许多设计问题。显然,这可以使用F#Mailboxes或CAS引用来实现。
我真的推荐它,很乐意听到一些反馈。