Композиционное дозирование с использованием функциональных конструкций - PullRequest
2 голосов
/ 12 июля 2020

Я пытаюсь создать композиционный механизм дозирования.

Поток процесса должен быть.

  1. Накапливать состояние в виде s: Map[BatchFragmentId, FragmentRequest]
  2. Вычислите результат в форме Map[BatchFragmentId, FragmentResponse]
  3. Выполните серию заранее определенных вычислений, которые имеют форму f: Map[BatchFragmentId, FragmentResponse] => R для некоторого результата R.

Мое решение

Пусть Batch[R] будет монада, которая содержит некоторое накопленное состояние s и некоторую функцию f.

map[B](g: R => B) имеет смысл, так как это просто состав автономных функция f (Batch.make(f=f.andThen(g), s=s).

flatMap[B](f: R => Batch[B]) на руке не имеет особого смысла, так как R требует завершения вычислений, поэтому Batch[B] не может существовать в этой точке, поскольку s должен содержать состояние Batch[B] при выполнении вычисления ответа.

Изменение flatMap:

def combine[B, C](that: Batch[B])(t: (A, B) => C): Batch[C] = {
  val combined: Map[BatchFragmentId, FragmentRequest] = that.state ++ state
  val g: Map[BatchFragmentId, FragmentResponse] => C = 
    (r: Map[BatchFragmentId, FragmentResponse]) => t(f(r), that.f(r))
  Batch.make(combined, g)
}

Это плохо работает с Scala для понимания и его читабельность ухудшается быстрее, чем для понимания.

val b: Batch[String] = ???
val b2: Batch[String] = ???
val b3: Batch[Int] = ???
val combined: Batch[(String, String)] = 
  (b.combine(b2){ case (s1, s2) => s1 + s2 })
  .combine(b3){ case (s1, i1) => s1 + i1.toString }

Что может стать очень запутанным при использовании пары пакетов. Желательный способ составить эти пакеты:

val o: Batch[(String, String)] = for {
  _ <- put("key", "42")
  x1 <- get("key")
  _ <- put("key2", "24")
  x2 <- get("key2")
} yield (x1, x2)

Но любой элегантный синтаксис будет решением.

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

Это даже правильный подход для такой проблемы; правильно ли я моделирую проблему? Эта проблема уже хорошо изучена и обобщена?

В моем распоряжении также есть кошки.

1 Ответ

0 голосов
/ 12 июля 2020

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

Для всех, кого интересует результат, я получил следующее.

type BatchFragmentId = String
trait FragmentRequest
trait FragmentResponse
type S = Map[BatchFragmentId, FragmentRequest]

object Batch {
  def make[A](s: S, f: Map[BatchFragmentId, FragmentResponse] => A): Batch[A] = Batch[A](
   transformer = f,
   state = s
  )
}

implicit object BatchApplicative extends Applicative[Batch] {
  override def pure[A](x: A): Batch[A] = Batch[A](
    transformer = _ => x,
    state =  Map.empty
  )

  override def ap[A, B](ff: Batch[A => B])(fa: Batch[A]): Batch[B] = {
    val f: Map[String, FragmentResponse] => B = (r: Map[BatchFragmentId, FragmentResponse]) => ff.transformer(r)(fa.transformer(r))
    Batch[B](
      transformer = f,
      state =  ff.state ++ fa.state
    )
  }
}

case class Batch[A] (
                      transformer: Map[BatchFragmentId, FragmentResponse] => A,
                      state: S
                    )

implicit class BatchOps[A](fa: Batch[A])(implicit A: Applicative[Batch]) {
  def <>[B](fb: Batch[B]): Batch[(A, B)] = A.product(fa, fb)
}

// Example
case class SomeFragmentRequest(x: Int) extends FragmentRequest
case class SomeFragmentResponse(x: Int) extends FragmentResponse

val id: BatchFragmentId = "some-id"
val exampleBatch = Batch.make[SomeFragmentResponse](
  s = Map(id -> SomeFragmentRequest(42)),
  f = responses => responses.get(id).map{ case x: SomeFragmentResponse => x }.get
)

// Example of composition
val o: Batch[(SomeFragmentResponse, SomeFragmentResponse)] = exampleBatch <> exampleBatch
// N arity
val o2: Batch[(SomeFragmentResponse, SomeFragmentResponse, SomeFragmentResponse, SomeFragmentResponse)] =
  Applicative[Batch].tuple4(exampleBatch, exampleBatch, exampleBatch, exampleBatch)

def run(s: S): Map[BatchFragmentId, FragmentResponse] = ???
def resolve[R](r: Map[BatchFragmentId, FragmentResponse], b: Batch[R]): R = b.transformer(r)

val res: (SomeFragmentResponse, SomeFragmentResponse) = resolve(run(o.state), o)
...