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: ...