В предыдущих сообщениях (Монада Writer для F #, Как войти в Apache Spark, Функциональный подход) мы обсуждали идею использования монады Writer. как способ агрегирования событий. Однако мы использовали это простой текстовый регистратор, который имеет лишь несколько небольших отличий от любого из обычных регистраторов, которые мы могли бы использовать в нашем приложении.

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

В этом посте мы собираемся представить монаду Writer как агрегатор событий, который можно использовать более общим образом для различных случаев использования, показывая, что он имеет более широкое удобство использования. Мы также собираемся использовать C #, чтобы показать, что эта концепция не только тесно связана с так называемыми чистыми функциональными языками, и что ее можно использовать в любой среде, в которой мы выберите.

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

Поиск событий

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

Почему Монада писателя?

Монада Writer имеет те же характеристики, что и описанные выше, она может записывать общие события, которые изменяют текущее состояние системы. В то же время события записываются в неизменной структуре, а текущее состояние изменяется только в результате применения новых событий.

Журнал калькулятора

Давайте начнем с упрощенного примера - реализации калькулятора.

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

Обратите внимание, что наш калькулятор определен на основе структуры, которую мы называем Writer, которую мы собираемся определить ниже. Основная идея состоит в том, что каждая операция знает только о том, как создать Writer с помощью операции, которую она выполняет.

Монаду Writer можно определить следующим образом.

Здесь мы определяем следующие операции.

  • Bind через конструктор позволяет нам создать новый Writer.
  • Map позволяет нам изменить текущее состояние.
  • FlatMap изменяет текущее состояние при записи того, как изменение состояния произошло.
  • Unsafe получает текущее состояние и журнал событий.

Обратите внимание, что единственный способ изменить состояние - это использовать .Map и .FlatMap.

Используя эту структуру, мы можем использовать наш калькулятор следующим образом.

Используя .UnSafe(), мы получаем текущее состояние и журнал событий.

В этом конкретном случае мы используем монаду Writer только как строковый журнал, и, возможно, из-за этого это пока не выглядит так интересно. Однако это основа для наших следующих примеров.

Суммарный агрегатор

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

Давайте сначала определим наш источник.

Как мы видим, мы собираемся использовать неограниченный / бесконечный поток случайных целых чисел.

Теперь давайте посмотрим, как мы используем монаду Writer для получения и обработки этих событий.

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

Пример использования банковского счета

Наш последний пример покажет, как мы можем использовать монаду Writer для обработки событий, отправленных на банковский счет. Банковский счет поддерживает две основные операции: добавлять на него деньги и снимать с них деньги.

Теперь предположим, что у нас есть генератор событий, который генерирует транзакции, которые мы собираемся обрабатывать нашей монадой Writer в качестве потокового процессора.

Транзакции представлены типами Extraction и Deposit, это два типа событий, которые мы собираемся обрабатывать.

Теперь, когда у нас есть источник транзакции, мы начинаем с инициализации нашего начального состояния.

Затем мы берем для обработки ряд событий, в данном случае нас интересуют 100 из них, но на самом деле это может быть любое количество.

Обратите внимание, что для каждой транзакции мы выполняем соответствующую операцию над банковским счетом через .FlatMap на accountState.

В конце концов, мы можем получить текущее (окончательное) состояние учетной записи и события, которые были обработаны.

Интересно то, что когда мы начинаем с того же начального состояния, а затем применяем те же транзакции в журнале монад Writer к начальному состоянию, мы должны закончить с тем же конечным состоянием. Изменение состояния значения является прямым следствием действий, записанных в журнале.

Использование чисто функционального подхода

Для тех, кто хочет использовать чисто функциональный подход и избежать мутации переменной accountState, мы можем добавить метод .FoldLeft в IEnumerable<T>. Посмотрим как.

Во-первых, мы добавляем метод расширения, чтобы делать .FoldLeft на C #.

Тогда нам нужно только изменить то, как мы обрабатываем события.

Обратите внимание, что таким образом мы устранили мутации монады Writer и вместо этого создаем новые, используя .FoldLeft и .FlatMap.

Выводы

Монада Writer представляет собой функциональный подход к отслеживанию изменений с использованием неизменяемого журнала, который можно использовать на любом языке программирования, включая C #.

Кроме того, использование .FlatMap позволяет нам плавно связывать операции, что представляет собой декларативное управление потоком, которое способствует неизменности.

Монада Writer иногда неправильно понимается и связана только с журналами приложений, пространством, в котором преобладают библиотеки побочных эффектов. Однако монада Writer - это больше, чем средство ведения журнала, это также источник событий, который можно использовать для записи изменений состояния в чистом и элегантном виде.

Мы также показали, как C # может поддерживать такой подход, доказав, что монады не ограничиваются такими чистыми функциональными языками.