Scalaz iteratees: "Lifting" `EnumeratorT` to match `IterateeT` for a "bigger" monad
If I have an EnumeratorT
and a corresponding IterateeT
I can run them together:
val en: EnumeratorT[String, Task] = EnumeratorT.enumList(List("a", "b", "c"))
val it: IterateeT[String, Task, Int] = IterateeT.length
(it &= en).run : Task[Int]
If the enumerator monad is "bigger" than the iteratee monad, I can use up
or, more generally, Hoist
to "lift" the iteratee to match:
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]
But what do I do when the iteratee monad is "bigger" than the enumerator monad?
val en: EnumeratorT[String, Id] = ...
val it: IterateeT[String, Task, Int] = ...
it &= ???
There doesn't seem to be a Hoist
instance for EnumeratorT
, nor any obvious "lift" method.
Solution 1:
In the usual encoding an enumerator is essentially a StepT[E, F, ?] ~> F[StepT[E, F, ?]]
. If you try to write a generic method converting this type into a Step[E, G, ?] ~> G[Step[E, G, ?]]
given an F ~> G
, you'll quickly run into an issue: you need to "lower" a Step[E, G, A]
to a Step[E, F, A]
in order to be able to apply the original enumerator.
Scalaz also provides an alternative enumerator encoding that looks like this:
trait EnumeratorP[E, F[_]] {
def apply[G[_]: Monad](f: F ~> G): EnumeratorT[E, G]
}
This approach allows us to define an enumerator that's specific about the effects it needs, but that can be "lifted" to work with consumers that require richer contexts. We can modify your example to use EnumeratorP
(and the newer natural transformation approach rather than the old monad partial order):
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) }
We can now compose the two like this:
scala> def result = (iter &= enum(toTask)).run
result: scalaz.concurrent.Task[Int]
EnumeratorP
is monadic (if the F
is applicative), and the EnumeratorP
companion object provides some functions to help with defining enumerators that look a lot like the ones on EnumeratorT
—there's empty
, perform
, enumPStream
, etc. I guess there have to be EnumeratorT
instances that couldn't be implemented using the EnumeratorP
encoding, but off the top of my head I'm not sure what they would look like.