web-dev-qa-db-fra.com

Scalaz iteratees: "Lifting" `EnumeratorT` to match` IterateeT` for a "larger" monad

Si j'ai un EnumeratorT et un IterateeT correspondant, je peux les exécuter ensemble:

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

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

Si la monade de l'énumérateur est "plus grande" que la monade des itérérés, je peux utiliser up ou, plus généralement, Hoist pour "soulever" l'itérée à faire correspondre:

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]

Mais que dois-je faire lorsque la monade itérative est "plus grosse" que la monade énumératrice?

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

it &= ???

Il ne semble pas y avoir d'instance Hoist pour EnumeratorT, ni de méthode "lift" évidente.

444
lmm

Dans le codage habituel, un énumérateur est essentiellement un StepT[E, F, ?] ~> F[StepT[E, F, ?]]. Si vous essayez d'écrire une méthode générique convertissant ce type en Step[E, G, ?] ~> G[Step[E, G, ?]] donné un F ~> G, vous rencontrerez rapidement un problème: vous devez "baisser" un Step[E, G, A] à un Step[E, F, A] afin de pouvoir appliquer l'énumérateur d'origine.

Scalaz fournit également n encodage d'énumérateur alternatif qui ressemble à ceci:

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

Cette approche nous permet de définir un énumérateur qui est spécifique sur les effets dont il a besoin, mais qui peut être "levé" pour travailler avec des consommateurs qui nécessitent des contextes plus riches. Nous pouvons modifier votre exemple pour utiliser EnumeratorP (et la nouvelle approche de transformation naturelle plutôt que l'ancien ordre partiel monade):

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) }

Nous pouvons maintenant composer les deux comme ceci:

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

EnumeratorP est monadique (si le F est applicatif), et l'objet compagnon EnumeratorP fournit quelques fonctions pour aider à définir des énumérateurs qui ressemblent beaucoup à ceux sur EnumeratorT— il y a empty, perform, enumPStream, etc. Je suppose qu'il doit y avoir EnumeratorT instances qui ne pourraient pas être implémentées en utilisant le EnumeratorP encodage, mais du haut de ma tête, je ne sais pas à quoi ils ressembleraient.

4
Travis Brown