tipos - take haskell
¿Qué pasa si un enumerador intenta consumir información? (2)
No hay nada de malo en que un enumerador consuma datos. Es un transformador iterativo, que bien puede introducir su propia entrada en su iteración. Mira la forma en que aplicas un enumerador a una iterada. También puede aplicar otro enumerador a una iteración aplicada a un enumerador.
La definición de Enumerator
es:
type Enumerator a m b = Step a m b -> Iteratee a m b
La documentación indica que mientras que los datos de consumo de Iteratee, Enumerator
s lo producen. Puedo entender cómo se pueden producir datos con este tipo:
enumStream :: (Monad m) => Stream a -> Enumerator a m b
enumStream stream step =
case step of
Continue k -> k stream
_ -> returnI step -- Note: ''stream'' is discarded
( enumEOF
es más complicado que esto ... aparentemente verifica que el Iteratee
no Continue
después de recibir EOF
, lo que genera un error si lo hace).
A saber, un Iteratee
produce un Step
cuando se ejecuta con runIteratee
. Este Step
se envía a mi enumerador, que lo suministra con un Stream
para que pueda continuar. Mi enumerador devuelve la continuación resultante.
Una cosa se destaca en mí: este código se ejecuta en la mónada Iteratee
. Eso significa que puede consumir datos, ¿verdad?
-- | Like ''enumStream'', but consume and discard a chunk from the input stream
-- simply because we can.
enumStreamWeird :: (Monad m) => Stream a -> Enumerator a m b
enumStreamWeird stream step = do
_ <- continue return -- Look, mommy, I''m consuming input!
case step of
Continue k -> k stream
_ -> returnI step
La documentación indica que cuando un enumerador actúa como fuente y sumidero, se debe utilizar Enumeratee
su lugar:
type Enumeratee ao ai m b = Step ai m b -> Iteratee ao m (Step ai m b)
Sin embargo, al parecer no tenía que hacerlo; Podría consumir información en la definición de un Enumerator
, como lo demuestra mi función enumStreamWeird
.
Mis preguntas son:
¿Qué sucede si intenta "consumir" datos dentro de un
enumStreamWeird
, como haceenumStreamWeird
? ¿De dónde vienen los datos?Incluso si no estamos lo suficientemente locos como para consumir datos en un enumerador, ¿es válido realizar acciones en la mónada subyacente en nombre del enumerador, en lugar de en nombre de la iteración que lee los datos que estamos produciendo?
La última pregunta podría estar menos relacionada con mi pregunta principal, pero estoy tratando de entender cómo un Enumerator
hace lo que hace.
Sí, un enumerador puede consumir datos. Básicamente, un enumerador toma una iteración y la transforma en la misma iteración después de haber alimentado algunos elementos. Si el enumerador solicita una entrada, la iteración resultante solicitará una entrada.
Cómo se alimenta un enumerador a un iterado
Veamos cómo se alimenta un enumerador a una iterada:
-- | Feed an Enumerator to an Iteratee.
feed :: Monad m
=> Iteratee a m b
-> Enumerator a m b
-> Iteratee a m b
feed iteratee enumerator =
Iteratee $ do
step <- runIteratee iteratee
runIteratee $ enumerator step
Nota: feed
es un caso especial de >>==
.
Primero, feed
ejecuta el iterador hasta que esté listo para la entrada. Luego, pasa el primer Step
del iterante al enumerador. El enumerador toma el relevo desde allí.
Esa última frase es muy importante. El enumerador puede hacer lo que quiera con su iterado. Puede descartar el iterado por completo si así lo desea. Sin embargo, un enumerador generalmente proporciona al iterado la entrada que tiene, y luego le devuelve el control al iterado.
Ejemplo 1: alimentar a los enumeradores a una iterada
Supongamos que tenemos una iteración que solicita tres cadenas y las imprime:
iter3 :: Iteratee String IO ()
iter3 = do
lift $ putStrLn "Gimmie a string!"
a <- head_
lift $ putStrLn a
lift $ putStrLn "Gimmie another string!"
b <- head_
lift $ putStrLn b
lift $ putStrLn "Gimmie one more string!"
c <- head_
lift $ putStrLn c
lift $ putStrLn "Thank you!"
head_
se define en Data.Enumerator.List .
y un enumerador que alimenta su iterado una sola cadena:
getString :: Enumerator String IO a
getString (Continue k) = do
line <- lift getLine
k (Chunks [line])
getString step = Iteratee $ return step
Cuando getString
recibe una iteración que necesita más de un elemento, alimenta la iteración con el primer elemento. Entonces, getString
necesitará los elementos restantes.
iter3
necesita tres elementos antes de que pueda regresar()
.iter3 `feed` getString
necesita dos elementos.iter3 `feed` getString `feed` getString
necesita un elemento.iter3 `feed` getString `feed` getString `feed` getString
no necesita más elementos.iter3 `feed` getString `feed` getString `feed` getString `feed` getString
es equivalente a lo anterior. Esto es manejado por el segundo caso degetString
.
Ejemplo 2: un enumerador que consume entrada
Considere un enumerador que consume entrada:
consumeEnum :: Enumerator String IO a
consumeEnum step = do
lift $ putStrLn "I take without giving"
_ <- head_
Iteratee $ return step
¿Qué hace iter3 `feed` consumeEnum
? Eso puede ser respondido mirando la propia implementación de consumeEnum
. Primero necesita un artículo y lo descarta. Luego entrega la antorcha a iter3
, que necesita tres elementos más.
Sin embargo, vuelva a mirar el combinador de feed
. Comienza ejecutando iter3
, luego pasa su Step
a consumeEnum
. Esto significa "Gimmie a string!"
Se imprimirá antes de que el control llegue a consumeEnum
.