F# 邮箱与 MailboxProcessor

发布于 2024-12-21 05:40:37 字数 363 浏览 3 评论 0原文

我注意到 Mailbox 类型是封装的,只能通过 MailboxProcessor 来使用。

这意味着要拥有一个可以向其发布消息的代理,我必须拥有单一类型的单个邮箱(或以一种奇特的方式使用现有的 MailboxProcessor)。

我是否应该理解,为单个工作流程设置多个邮箱必然会导致糟糕的设计? Ccr 显然为您提供了这种程度的自由。

编辑: 正如 Daniel 指出的,如果想要发送多种消息类型,DU 可以优雅地解决问题 - 而且我过去也不是没有这样做过。

但问题是,这样做不是有代码味道吗?随着时间的推移,添加更多类型的消息发送给代理是否会导致您承担太多责任?我有时认为始终将代理消耗的消息类型封装在接口后面非常重要,这样该信息就不会暴露。

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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

荭秂 2024-12-28 05:40:37

我认为使用 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),主体的行为类似于处理程序,当邮箱组合包含值时(即当您put< /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# 中,emptyfull 是两个函数,代表代理的不同状态,发送到代理的消息代表代理状态的不同方面(待处理的工作) 。在 COmega 实现中,程序的所有状态都由邮箱捕获。

我猜想,将代理的状态与需要处理的即时消息分开可能会让您更容易地思考一下 F# MailboxProcessor,但这只是一个没有理由的直接想法...

最后,在 F# 中使用 MailboxProcessor 的实际应用程序中,您很可能会使用大量它们,并且它们将以某种方式连接。例如,实现管道是使用多个 MailboxProcessor 实例的应用程序的一个很好的示例(当然,所有实例都具有一些与其关联的简单运行异步工作流)。有关示例,请参阅本文

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.

梦断已成空 2024-12-28 05:40:37

通常,消息类型是可区​​分联合,它允许在单个邮箱中容纳各种类型的消息。这对你的情况不起作用吗?

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?

oО清风挽发oО 2024-12-28 05:40:37

我认为您永远无法仅使用一种类型的消息成功处理邮箱,除非您使用响应式扩展中的 ISubject 类型。信息有不同的形式,但都很重要。我能想到的两个主要示例是:

  1. 控制消息 - 表示邮箱应执行的操作,例如清除队列、查找特定消息、关闭、启动子进程等。
  2. 数据消息 - 发送和接收(Put / Get) ) 是这些的一般类型。

您的想法是正确的,您很可能希望将数据消息限制为某种类型,但从技术上讲,DU 是一种有多种选择的类型。如果您采用与 Luca 相同的方法,即在《L'Agent》中采用他最初的动态方法,我想他和我都会同意,一个邮箱中包含太多类型是一个挑战。

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.

行至春深 2024-12-28 05:40:37

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

我真的推荐它并且很高兴听到一些反馈。

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文