450

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.

lmm
  • 17,386
  • 3
  • 26
  • 37
  • 59
    +1 for a neat question, but off the top I'm not sure of my head that this is possible in the general case, since an `Enumerator` is really just a wrapper around a `StepT => IterateeT`, which suggests that you'll need to "step down" from a `StepT[E, BigMonad, A]`. – Travis Brown Nov 13 '14 at 21:06
  • 12
    Yeah, I found that when I tried to implement it directly. But logically an `Enumerator` is just an effectful source, right? It feels like I should be able to use a thing that can supply `A` to supply `Task[A]`. – lmm Nov 13 '14 at 23:38
  • 8
    I don't know enough about Scala to offer an answer but couldn't you [define your own type and provide a lifting mechanism for it](https://docs.scala-lang.org/overviews/quasiquotes/lifting.html#bring-your-own)? – Rob Sep 07 '18 at 08:54
  • 8
    No, that isn't the same thing at all, it's a different kind of "lifting". – lmm Sep 07 '18 at 19:37
  • 1
    As for me, EnumeratorT produce value, so the question here is this: If you want to enumerate more than there is data, what do you expect to get AFTER the end of the enumerator? I would say you need something like an Applicative[F] for an EnumeratorT[A, F], so you can call pure[A] and get an "empty" value out of the enumerator. By the way, I use cats types here because I know them more, sorry. – Merlin Jul 01 '19 at 21:05
  • 2
    @TravisBrown there's a bounty on this one right now, if you want to write it up. – Russia Must Remove Putin Sep 27 '19 at 22:01
  • 1
    @AaronHall Just took a stab at it. – Travis Brown Sep 28 '19 at 15:48

1 Answers1

7

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.

Travis Brown
  • 138,631
  • 12
  • 375
  • 680