2011-12-12 110 views
0

我注意到邮箱类型被封装,只能通过使用MailboxProcessor来使用。这意味着要有一个我可以发布消息的代理,我不得不拥有一个单一类型的邮箱(或以异乎寻常的方式使用现有的MailboxProcessor)。F#邮箱vs邮箱处理器

我是否应该明白,为单个工作流程设置多个邮箱会导致设计错误? Ccr明确地给你这种自由度。正如Daniel指出的那样,如果想要发送多种消息类型,DU可以优雅地解决这个问题 - 而且这并不像我过去没有这样做过。

但问题是,是不是在做一个代码味道?随着时间的推移,不会添加发送给代理的更多类型的消息会导致您承担过多的责任?我有时认为总是将代理消耗的消息类型封装在接口后面非常重要,以避免此类信息暴露。

回答

0

我想我可能已经找到了我要找的东西。我已经听过Rich Hickey的讲话(我们还在那里)至少5次,我相信他的方法解决了我的许多设计问题。显然,这可以用F#邮箱或CAS引用来实现。

我真的很推荐它,很乐意听到一些反馈。

1

通常,消息类型是区分的联合,它允许单个邮箱中的各种消息。你的情况不适用吗?

+0

绝对不是试图解决问题在这里......这纯粹是一个设计考虑。 –

+0

我想我没跟着。我以为你建议一个邮件被限制在一个单一的消息类型中是有问题的,但是一个DU规避了这个“限制”。 – Daniel

+0

同意,但这并没有太多说明这种方法的设计质量。但我不清楚,我会更新我的问题。 –

4

我认为使用MailboxProcessor和CCR的F#代理实现了不同的编程模型,但我相信两者同样强大,虽然肯定有问题可以用其中一个或另一个更好地解决,所以它会很好为邮箱建立另一个F#库。基于CCR的编程模型可能在基于加入微积分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; 
    } 
} 

在这个例子中,异步方法的行为就像邮箱(所以有他们四个人:emptycontainsPutGet)和尸体的行为那样会触发处理程序时,邮箱的组合包含一个值(即当你缓冲区或当您从0123得到 full buffer)。在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#中,emptyfull是表示代理的不同状态的两个函数,发送给代理的消息表示代理状态的不同方面(待完成的工作)。在COmega实现中,程序的所有状态都由邮箱捕获。

我猜想,分离需要处理的即时消息代理的状态可能更容易考虑F#MailboxProcessor了一点,但是这只是一个立即的思想,没有理由......

最后,在F#中使用MailboxProcessor的现实应用程序中,您很可能会使用更多的这些应用程序,并且它们将以某种方式连接。例如,实现流水线是使用多个MailboxProcessor实例的应用程序的一个很好的例子(当然,它们都有一些简单的运行异步工作流程)。一个例子见this article

+0

非常有益的感谢。显然必须澄清我的问题:D –

+0

@Tomas,我注意到你总是使用'AsyncReplyChannel',即使是看似单向的消息,比如'Put'。是否有一个原因?等待回复是否可以防止乱序信息?我原以为'MailboxProcessor'应该只能通过'Post'接收消息。 – 2011-12-13 16:15:37

+0

@RyanRiley我不会在简单的消息中使用'AsyncReplyChannel'来调用代理的某些动作。不过,我在这里使用它主要是因为我认为代理的用户可能要等到代理处理先前的“放入”消息。例如,使用单位缓冲区来实现(简单)生产者/消费者模式(中间只有一个元素存储)时。如果您只是想在不等待的情况下向代理发送一些信息,那么使用'Post'确实很好。 (但是这里基于COmega的版本确实没有等待'Put',所以两者不同)。 –

1

我不认为您只能使用一种类型的邮件才能成功使用邮箱,除非您使用类似于Reactive Extensions中的ISubject类型的邮件。消息以不同的形式出现,而且都很重要。我能想到的两个主要的例子是:

  1. 控制消息 - 表示操作的邮箱应该承担诸如清除其队列,寻找特定信息,关停转起来子进程等
  2. 数据消息 - 发送和接收(Put/Get)是这些的一般类型。

你认为你很可能想限制数据信息为某种类型,但在技术上DU是一种有很多选择的类型。如果你想和卢卡一样采用他在L'Agent中最初的动态方法,我认为他和我都会同意在一个邮箱中使用太多的类型是一个挑战。

+0

是不是你带来低级实施细节的两点? 1-显然,2-这总是会导致非常类似CRUD的代理? 我当然希望所有这些很好地封装在一个接口后面,否则我只是在我的应用程序的任何地方泄漏实现细节 - 这部分证实了我的问题中的假设。 –

+2

我发现使用一种类型通常会导致接口和多态实现。几年前我在C#中已经成功地使用了这种方法,但是我发现IoC容器也结束了参与和整个设置逻辑和构建。我觉得DI和邮箱很好地解决了这个问题。 – 7sharp9

+0

David Grenier请注意,这可能是L'Agent中的内存泄漏。看到我的帖子在这里:http://moiraesoftware.com/?p=505 – 7sharp9