19

的定义Enumerator是:

type Enumerator a m b = Step a m b -> Iteratee a m b

该文档指出,虽然Iteratees 消费数据,Enumerator但 s 生产它。我可以理解如何使用这种类型生成数据:

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,如果是则抛出错误。)ContinueEOF

即, an在运行时Iteratee会产生 a 。然后将其馈送到我的枚举器,它为它提供 a以便它可以继续。我的枚举器返回结果延续。SteprunIterateeStepStream

有一件事让我印象深刻:这段代码在Iterateemonad 中运行。这意味着它可以消耗数据,对吧?

-- | 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函数所示,我可以在 an 的定义中使用输入。

我的问题是:

  • 如果您尝试在 中“使用”数据,会发生什么Enumerator情况enumStreamWeird?数据从何而来?

  • 即使我们没有足够疯狂地使用枚举器中的数据,代表枚举器而不是代表迭代器读取我们正在生成的数据在底层 monad 中执行操作是否有效?

后一个问题可能与我的主要问题不太相关,但我试图了解 anEnumerator它是如何做的。

4

2 回答 2

3

是的,枚举器可以使用数据。枚举器基本上接受一个迭代器,并在给它一些项后将其转换为相同的迭代器。如果枚举器要求输入,则生成的迭代器将要求输入。

Enumerator 如何被馈送到 Iteratee

让我们看看如何将枚举数馈送到迭代对象:

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

注:feed是 的特例>>==

首先,feed运行迭代器,直到它准备好输入。然后,它将迭代者的第一个传递Step给枚举器。枚举器从那里接管。

最后一句话非常重要。枚举器可以对它的迭代对象做任何它想做的事情。如果愿意,它可以完全丢弃迭代对象。但是,枚举器通常会向迭代器提供它所拥有的输入,然后将控制权交还给迭代器。

示例 1:将枚举数提供给迭代对象

假设我们有一个 iteratee 请求三个字符串并打印它们:

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_Data.Enumerator.List中定义。

和一个枚举器,它向它的迭代器提供一个字符串:

getString :: Enumerator String IO a
getString (Continue k) = do
    line <- lift getLine
    k (Chunks [line])
getString step = Iteratee $ return step

getString给定一个需要多个项目的迭代器时,它将向迭代器提供第一个项目。然后,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:使用输入的枚举器

考虑一个确实消耗输入的枚举器:

consumeEnum :: Enumerator String IO a
consumeEnum step = do
    lift $ putStrLn "I take without giving"
    _ <- head_
    Iteratee $ return step

做什么iter3 `feed` consumeEnum?这可以通过查看consumeEnum自己的实现来回答。首先,它需要一个项目并将其丢弃。然后它将手电筒交给iter3,它还需要三件物品。

但是,回头看看feed组合器。它从运行开始iter3,然后将其传递StepconsumeEnum。这意味着"Gimmie a string!"将在控制到达之前打印consumeEnum

于 2011-10-22T06:31:40.617 回答
1

使用数据的枚举器没有任何问题。它是一个迭代转换器,它可以很好地将自己的输入输入到它的迭代器中。查看您将枚举数应用于迭代对象的方式。您还可以将另一个枚举器应用于应用于枚举器的迭代器。

于 2011-10-12T10:04:39.767 回答