Enumerator
是:
type Enumerator a m b = Step a m b -> Iteratee a m b
文档指出,虽然 Iteratee
使用数据,Enumerator
生成数据。我可以理解如何生成具有这种类型的数据:
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
比这更复杂......它显然会检查以确保 Iteratee
不给出 EOF
后继续,如果出现则抛出错误。)
即,Iteratee
生成 步骤
当它使用 runIteratee
运行。然后这个Step
被输入到我的枚举器,它为它提供一个Stream
以便它可以继续。我的枚举器返回结果延续。
有一点对我来说很突出:这段代码在 Iteratee
monad 中运行。这意味着它可以消耗数据,对吗?
-- | 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
该文档指出,当枚举器同时充当源和接收器时, Enumeratee
应该改用:
type Enumeratee ao ai m b = Step ai m b -> Iteratee ao m (Step ai m b)
但是,显然我不必这样做;我可以使用 Enumerator
定义中的输入,如我的 enumStreamWeird
函数所示。
我的问题是:
后一个问题可能与我的主要问题不太相关,但我试图了解 Enumerator
是如何完成它的工作的。
The definition of Enumerator
is:
type Enumerator a m b = Step a m b -> Iteratee a m b
The documentation states that while Iteratee
s comsume data, Enumerator
s produce it. I can understand how one might produce data with such a type:
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
is more complicated than this... it apparently checks to make sure the Iteratee
does not Continue
after being given EOF
, throwing an error if it does.)
Namely, an Iteratee
produces a Step
when it is run with runIteratee
. This Step
is then fed to my enumerator, which supplies it with a Stream
so it can continue. My enumerator returns the resulting continuation.
One thing stands out at me: this code is running in the Iteratee
monad. That means it can consume data, right?
-- | 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
The documentation states that when an enumerator acts as both a source and sink, Enumeratee
should be used instead:
type Enumeratee ao ai m b = Step ai m b -> Iteratee ao m (Step ai m b)
However, apparently I didn't have to; I could consume input in the definition of an Enumerator
, as demonstrated by my enumStreamWeird
function.
My questions are:
-
What happens if you try to "consume" data within an Enumerator
, like enumStreamWeird
does? Where does the data come from?
-
Even if we aren't insane enough to consume data in an enumerator, is it valid to perform actions in the underlying monad on the behalf of the enumerator, rather than on behalf of the iteratee reading the data we're producing?
The latter question might be less related to my main question, but I'm trying to understand how an Enumerator
does what it does.
发布评论
评论(2)
是的,枚举器可以消耗数据。枚举器基本上采用一个迭代器,并在输入一些项目后将其转换为相同的迭代器。如果枚举器要求输入,则生成的迭代器将要求输入。
如何将枚举器提供给迭代器
让我们看看如何将枚举器提供给迭代器:
注意:
feed
是>>==
.首先,
feed
运行 iteratee,直到它准备好输入。然后,它传递迭代器的第一个步骤
到枚举器。枚举器从那里接管。最后一句话非常重要。枚举器可以对其迭代器做任何它想做的事情。如果愿意的话,它可以完全丢弃 iteratee。然而,枚举器通常向迭代器提供它所拥有的输入,然后将控制权交还给迭代器。
示例 1:向迭代器提供枚举器
假设我们有一个迭代器,它请求三个字符串并打印它们:
head_
定义于 Data.Enumerator.List .和一个为其 iteratee 提供单个字符串的枚举器:
当为 getString 提供一个需要多个项目的 iteratee 时,它将向 iteratee 提供第一个项目。然后,
getString
本身将需要剩余的项目。iter3
需要三个项目才能返回()
。iter3 `feed` getString
需要两项。iter3 `feed` getString `feed` getString
需要一项。iter3 `feed` getString `feed` getString `feed` getString
不再需要任何项目。iter3 `feed` getString `feed` getString `feed` getString `feed` getString
与上面等效。这是由getString
的第二种情况处理的。示例 2:消耗输入的枚举器
考虑一个消耗输入的枚举器:
iter3 `feed` ConsumerEnum 做什么?这可以通过查看consumeEnum自己的实现来回答。首先它需要一个项目并丢弃它。然后它将火炬交给
iter3
,它还需要三个项目。不过,请回头看看
feed
组合器。它首先运行iter3
,然后传递其步骤
到consumeEnum
。这意味着"Gimmie a string!"
将在控制到达consumeEnum
之前打印。Yes, an enumerator can consume data. An enumerator basically takes an iteratee and transforms it into the same iteratee after it has been fed some items. If the enumerator asks for input, then the resulting iteratee will ask for input.
How an Enumerator is fed to an Iteratee
Let's look at how an enumerator is fed to an iteratee:
Note:
feed
is a special case of>>==
.First,
feed
runs the iteratee until it is ready for input. Then, it passes the iteratee's firstStep
to the enumerator. The enumerator takes over from there.That last sentence is very important. The enumerator can do whatever it wants with its iteratee. It can discard the iteratee entirely if it wants to. However, an enumerator usually supplies the iteratee with the input it has, then hands control back to the iteratee.
Example 1: Feeding enumerators to an iteratee
Suppose we have an iteratee that asks for three strings and prints them:
head_
is defined in Data.Enumerator.List.and an enumerator that feeds its iteratee a single string:
When
getString
is given an iteratee that needs more than one item, it will feed the iteratee with the first item. Then,getString
itself will need the remaining items.iter3
needs three items before it can return()
.iter3 `feed` getString
needs two items.iter3 `feed` getString `feed` getString
needs one item.iter3 `feed` getString `feed` getString `feed` getString
does not need any more items.iter3 `feed` getString `feed` getString `feed` getString `feed` getString
is equivalent to the above. This is handled bygetString
's second case.Example 2: An enumerator that consumes input
Consider an enumerator that does consume input:
What does
iter3 `feed` consumeEnum
do? That can sort of be answered by looking atconsumeEnum
's own implementation. First it needs an item and discards it. Then it hands the torch toiter3
, which needs three more items.However, look back to the
feed
combinator. It starts by runningiter3
, then passes itsStep
toconsumeEnum
. This means"Gimmie a string!"
will be printed before control reachesconsumeEnum
.枚举器消耗数据没有任何问题。它是一个 iteratee 转换器,很可能将自己的输入输入到 iteratee 中。查看将枚举器应用于迭代器的方式。您还可以将另一个枚举器应用于应用于枚举器的迭代器。
There is nothing wrong with an enumerator consuming data. It is an iteratee transformer, which may well feed its own input into its iteratee. Look at the way you apply an enumerator to an iteratee. You can also apply another enumerator to an iteratee applied to an enumerator.