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.
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 aStep[E, G, ?] ~> G[Step[E, G, ?]]
given anF ~> G
, you'll quickly run into an issue: you need to "lower" aStep[E, G, A]
to aStep[E, F, A]
in order to be able to apply the original enumerator.Scalaz also provides an alternative enumerator encoding that looks like this:
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):We can now compose the two like this:
EnumeratorP
is monadic (if theF
is applicative), and theEnumeratorP
companion object provides some functions to help with defining enumerators that look a lot like the ones onEnumeratorT
—there'sempty
,perform
,enumPStream
, etc. I guess there have to beEnumeratorT
instances that couldn't be implemented using theEnumeratorP
encoding, but off the top of my head I'm not sure what they would look like.