Scalaz iteratees: „Lifting” „EnumeratorT”, aby dopasować „IterateeT” do „większej” monady

445

Jeśli mam EnumeratorTodpowiedni odpowiednik IterateeT, mogę je uruchomić razem:

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

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

Jeśli monada modułu wyliczającego jest „większa” niż monada iteracyjna, mogę użyć uplub, bardziej ogólnie, Hoist„podnieść” iterat, aby dopasować:

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]

Ale co mam zrobić, gdy monada iteracyjna jest „większa” niż monada wyliczająca?

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

it &= ???

Wydaje się, że nie ma Hoistinstancji EnumeratorTani żadnej oczywistej metody „podnoszenia”.

lmm
źródło
59
+1 za fajne pytanie, ale z góry nie jestem pewien, czy jest to możliwe w ogólnym przypadku, ponieważ Enumeratortak naprawdę jest to tylko opakowanie wokół StepT => IterateeT, co sugeruje, że musisz „ustąpić” od A StepT[E, BigMonad, A].
Travis Brown
12
Tak, odkryłem, że kiedy próbowałem to wdrożyć bezpośrednio. Ale logicznie rzecz biorąc, Enumeratorjest tylko skutecznym źródłem, prawda? Wydaje mi się, że powinienem być w stanie użyć rzeczy, która może dostarczyć Ado zaopatrzenia Task[A].
lmm
8
Nie wiem wystarczająco dużo o Scali, aby udzielić odpowiedzi, ale czy nie potrafiłeś zdefiniować własnego typu i zapewnić mechanizmu podnoszenia ?
Rob
8
Nie, to wcale nie jest to samo, to inny rodzaj „podnoszenia”.
lmm
2
@TravisBrown jest teraz nagroda za tę nagrodę, jeśli chcesz ją napisać.
Aaron Hall

Odpowiedzi:

4

W zwykłym kodowaniu moduł wyliczający to w zasadzie a StepT[E, F, ?] ~> F[StepT[E, F, ?]]. Jeśli spróbujesz napisać ogólną metodę konwertującą ten typ na Step[E, G, ?] ~> G[Step[E, G, ?]]dany an F ~> G, szybko napotkasz problem: musisz „obniżyć” a Step[E, G, A]do a Step[E, F, A], aby móc zastosować oryginalny moduł wyliczający.

Scalaz zapewnia również alternatywne kodowanie modułu wyliczającego, który wygląda następująco:

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

Takie podejście pozwala nam zdefiniować moduł wyliczający, który jest specyficzny dla potrzebnych efektów, ale który można „podnieść” do pracy z konsumentami, którzy wymagają bogatszych kontekstów. Możemy zmodyfikować twój przykład, aby używał EnumeratorP(i nowszego podejścia do naturalnej transformacji zamiast starej częściowej kolejności monady):

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

Możemy teraz skomponować dwa w ten sposób:

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

EnumeratorPjest monadycznego (jeśli Fjest aplikacyjnych), a EnumeratorPobiekt towarzysz oferuje kilka funkcji, aby pomóc w określeniu rachmistrzów, które wyglądają trochę jak te na EnumeratorT-Jest empty, perform, enumPStream, itd. Myślę, że nie trzeba być EnumeratorTprzypadki, które nie mogą być realizowane z wykorzystaniem EnumeratorPkodowania, ale przy mojej głowie nie jestem pewien, co oni wyglądać.

Travis Brown
źródło