Статьи

Endo — новый свободный API

Я написал это в выходные дни …

хороший заголовок для возможного поста в блоге … endo — это новый свободный API …

Мои последние два поста в блоге были об эндоморфизмах и о том, как они сочетаются с другими функциональными структурами, чтобы помочь вам написать выразительные и составляемый код. В DSL с бесплатными эндо-моноидами эндо играют с монадой Writer и реализуют DSL для последовательности действий посредством моноидальной композиции. А в упражнении «Рефакторинг — игра с моноидами и эндоморфизмами» я обсуждаю упражнение по рефакторингу, которое использует моноид эндо для облегчения композиции. Эндоморфизмы помогают вам поднять ваши вычисления в тип данных, который дает вам экземпляр моноида. ИmappendРабота моноида является функцией композиции. Следовательно, как только вы определили Endoдля своего типа, вы получите хороший декларативный синтаксис для операций, которые вы хотите составить, в результате чего вы получите свободный API. Просто быстрое резюме … эндоморфизмы — это функции, которые отображают тип на себя и предлагают композицию поверх моноидов. Учитывая эндоморфизм, мы можем определить неявный экземпляр моноида ..

implicit def endoInstance[A]: Monoid[Endo[A]] = new Monoid[Endo[A]] {
  def append(f1: Endo[A], f2: => Endo[A]) = f1 compose f2
  def zero = Endo.idEndo
}

Я не буду вдаваться в подробности этого, которые я подробно обсуждал в своих предыдущих постах. В этой статье я подведу итог еще одному варианту использования для создания плавных API, использующих моноидный экземпляр Endo. Рассмотрим пример из области торговли ценными бумагами, где сделка с ценными бумагами проходит последовательность преобразований в своем жизненном цикле в процессе торговли. Вот типичная Tradeмодель (очень тривиально для демонстрации).

sealed trait Instrument
case class Security(isin: String, name: String) extends Instrument
 
case class Trade(refNo: String, tradeDate: Date, valueDate: Option[Date] = None,
  ins: Instrument, principal: BigDecimal, net: Option[BigDecimal] = None,
  status: TradeStatus = CREATED)

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

  1. Подтвердить сделку
  2. Присвойте дату валютирования сделке, которая в идеале будет датой расчета
  3. Обогатите сделку налогами / сборами и чистой стоимостью сделки
  4. Журнализировать торговлю книгами

Каждая из функций принимает
Tradeи возвращает копию
Tradeс некоторыми измененными атрибутами. Наивный способ сделать это будет следующим.

def validate(t: Trade): Trade = //..
 
def addValueDate(t: Trade): Trade = //..
 
def enrich(t: Trade): Trade = //..
 
def journalize(t: Trade): Trade = //..

и вызывать эти методы последовательно при моделировании жизненного цикла. Вместо этого мы пытаемся сделать его более сложным и поднять функцию в
Trade => Tradeпределах
Endo..

type TradeLifecycle = Endo[Trade]

и вот реализация ..

// validate the trade: business logic elided
def validate: TradeLifecycle =
  ((t: Trade) => t.copy(status = VALIDATED)).endo
 
// add value date to the trade (for settlement)
def addValueDate: TradeLifecycle =
  ((t: Trade) => t.copy(valueDate = Some(t.tradeDate), status = VALUE_DATE_ADDED)).endo
 
// enrich the trade: add taxes and compute net value: business logic elided
def enrich: TradeLifecycle =
  ((t: Trade) => t.copy(net = Some(t.principal + 100), status = ENRICHED)).endo
 
// journalize the trade into book: business logic elided
def journalize: TradeLifecycle =
  ((t: Trade) => t.copy(status = FINALIZED)).endo

Теперь у endo есть экземпляр, Monoidзаданный scalaz, а mappendкомпозиция Endois — это функция. Отсюда наша модель жизненного цикла, использующая священный моноид endo.

def doTrade(t: Trade) =
  (journalize |+| enrich |+| addValueDate |+| validate).apply(t)

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

Почему не простая старая композиция?

Действительный вопрос. Причина — абстракция. Абстрагирование композиции внутри типов помогает вам комбинировать результат с другими типами, как мы видели в моих предыдущих постах в блоге. В одном из них мы построили большие абстракции, используя монаду Writer,
Endoа в другом мы использовали
mzeroмоноид как запасной вариант во время композиции, избегая, таким образом, каких-либо особых операторов ветвления.

Один размер не подходит для всех ..

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