Scalaz повторяет: "Подъем" `EnumeratorT` для соответствия "IterateeT" для "большей" монады

Если у меня есть EnumeratorT и соответствующий IterateeT, я могу запустить их вместе:

val en: EnumeratorT[String, Task] = EnumeratorT.enumList(List("a", "b", "c"))
val it: IterateeT[String, Task, Int] = IterateeT.length

(it &= en).run : Task[Int]

Если монада-перечислителя "больше", чем итерационная монада, я могу использовать up или, в более общем смысле, Hoist, чтобы "поднять" итерацию, чтобы она соответствовала:

val en: EnumeratorT[String, Task] = ...
val it: IterateeT[String, Id, Int] = ...

val liftedIt = IterateeT.IterateeTMonadTrans[String].hoist(
  implicitly[Task |>=| Id]).apply(it)
(liftedIt &= en).run: Task[Int]

Но что мне делать, когда итерационная монада "больше", чем монада перечислителя?

val en: EnumeratorT[String, Id] = ...
val it: IterateeT[String, Task, Int] = ...

it &= ???

Кажется, что экземпляр Hoist для EnumeratorT не существует, ни какой-либо очевидный метод "подъема".

Ответ 1

В обычном кодировании перечислитель по сути является StepT[E, F, ?] ~> F[StepT[E, F, ?]]. Если вы попытаетесь написать общий метод преобразования этого типа в Step[E, G, ?] ~> G[Step[E, G, ?]] с заданным F ~> G, вы быстро столкнетесь с проблемой: вам нужно "опустить" Step[E, G, A] до Step[E, F, A], чтобы возможность применить оригинальный перечислитель.

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

trait EnumeratorP[E, F[_]] {
  def apply[G[_]: Monad](f: F ~> G): EnumeratorT[E, G]
}

Этот подход позволяет нам определить перечислитель, который конкретно описывает необходимые эффекты, но его можно "поднять" для работы с потребителями, которым требуется более широкий контекст. Мы можем изменить ваш пример, чтобы использовать EnumeratorP (и более новый подход к естественному преобразованию, а не старый частичный порядок монад):

import scalaz._, Scalaz._, iteratee._, concurrent.Task

def enum: EnumeratorP[String, Id] = ???
def iter: IterateeT[String, Task, Int] = ???

val toTask = new (Id ~> Task) { def apply[A](a: A): Task[A] = Task(a) }

Теперь мы можем скомпоновать их так:

scala> def result = (iter &= enum(toTask)).run
result: scalaz.concurrent.Task[Int]

EnumeratorP является монадическим (если F является аппликативным), а сопутствующий объект EnumeratorP предоставляет некоторые функции, помогающие определить перечислители, которые во многом похожи на перечислители в EnumeratorT - там empty, perform, enumPStream и т.д. Я предполагаю, что должны быть экземпляры EnumeratorT, которые не могли бы быть реализованы с использованием кодировки EnumeratorP, но из головы не знаю, как они будут выглядеть ,

Ответ 2

Надеюсь, эта ссылка поможет: Документация Scala

Петли для и для доходности

Выражения for и for-yield позволяют нам писать монадическое понимание стиля, которое разбрасывается на вызовы методов map, flatMap, foreach и withFilter:

scala> val 'for-yield' = q"for (x <- xs; if x > 0; y = x * 2) yield x"
for-yield: universe.Tree =
xs.withFilter(((x) => x.$greater(0))).map(((x) => {
  val y = x.$times(2);
  scala.Tuple2(x, y)
})).map(((x$3) => x$3: @scala.unchecked match {
  case scala.Tuple2((x @ _), (y @ _)) => x
}))

Каждый перечислитель в понимании может быть выражен с помощью интерполятора fq "...":

scala> val enums = List(fq"x <- xs", fq"if x > 0", fq"y = x * 2")
enums: List[universe.Tree] = List('<-'((x @ _), xs), 'if'(x.$greater(0)), (y @ _) = x.$times(2))

scala> val 'for-yield' = q"for (..$enums) yield y"
for-yield: universe.Tree

Точно так же можно деконструировать for-yield обратно в список перечислителей и тела:

scala> val q"for (..$enums) yield $body" = 'for-yield'
enums: List[universe.Tree] = List('<-'((x @ _), xs), 'if'(x.$greater(0)), (y @ _) = x.$times(2))
body: universe.Tree = x

Важно отметить, что для и для выхода не совпадают друг с другом:

scala> val q"for (..$enums) $body" = 'for-yield'
scala.MatchError: ...