F# Почтовый ящик против MailboxProcessor

Я заметил, что тип почтового ящика инкапсулирован и может использоваться только с использованием MailboxProcessor.

Это означает, что для того, чтобы иметь агента, которому я могу публиковать сообщения, я вынужден иметь один почтовый ящик одного типа (или использовать существующий MailboxProcessor экзотическим способом).

Должен ли я понимать, что наличие нескольких почтовых ящиков для одного рабочего процесса по своей сути приведет к плохому дизайну? Ccr явно дает вам этот уровень свободы.

Редактировать: Как отметил Даниэль, если кто-то хочет отправить несколько типов сообщений, DU элегантно решает проблему - и я не делал этого раньше.

Но вопрос в том, не делает ли это запах кода? Разве добавление большего количества типов сообщений, отправляемых агенту с течением времени, не приведет к тому, что у вас будет слишком много обязанностей? Я иногда думаю, что было бы важно всегда инкапсулировать типы сообщений, которые агент использует за интерфейсом, так что эта информация никогда не раскрывается.

4 ответа

Решение

Думаю, я нашел то, что искал. Я слушал выступление Рича Хики (мы уже там) по крайней мере 5 раз, и я верю, что его подход решает многие проблемы дизайна, которые у меня были. Очевидно, что это может быть реализовано с использованием почтовых ящиков F# или ссылок CAS.

Я очень рекомендую это и был бы рад услышать некоторые отзывы.

Я думаю, что F# агенты, использующие MailboxProcessor и CCR реализуют другую модель программирования, но я полагаю, что обе они одинаково мощны, хотя есть определенно проблемы, которые можно было бы лучше решить с одной или другой, так что было бы неплохо иметь другую библиотеку для 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;
  }
}

В этом примере асинхронные методы ведут себя как почтовые ящики (поэтому их четыре: empty, contains, Put а также Get) и тела ведут себя как обработчики, которые будут срабатывать, когда комбинация почтовых ящиков содержит значение (т.е. когда вы помещаете в пустой буфер или когда вы получаете из полного буфера). В 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 немного, но это просто немедленная мысль без оправдания...

Наконец, в реалистичном приложении, которое использует MailboxProcessor в F# вы, скорее всего, будете использовать большее их количество, и они будут каким-то образом связаны. Например, реализация конвейерной передачи является хорошим примером приложения, которое использует несколько MailboxProcessor экземпляры (которые, конечно, имеют какой-то простой асинхронный рабочий процесс, связанный с ними). Смотрите эту статью для примера.

Как правило, тип сообщения - это различающееся объединение, которое допускает различные виды сообщений в одном почтовом ящике. Разве это не работает в вашем случае?

Я не думаю, что вы когда-либо сможете успешно работать с почтовым ящиком, используя только один тип сообщения, если вы не используете что-то вроде ISubject введите из Реактивных Расширений. Сообщения приходят в разных формах, и все они важны. Два основных примера, о которых я могу подумать:

  1. Управляющие сообщения - обозначают операции, которые должен выполнять почтовый ящик, такие как очистка его очереди, поиск определенных сообщений, выключение, раскрутка дочерних процессов и т. Д.
  2. Сообщения с данными - отправка и получение (Put / Get) являются основными типами этих сообщений.

Вы правы, полагая, что вы, скорее всего, захотите ограничить сообщения данных определенным типом, но технически DU - это один тип со многими альтернативами. Если бы вы выбрали тот же подход, что и Лука с его первоначальным, динамичным подходом в L'Agent, я думаю, что и он, и я согласились бы с тем, что слишком много типов в одном почтовом ящике является проблемой.

Другие вопросы по тегам