【问题标题】:What happens if an Enumerator tries to consume input?如果 Enumerator 尝试使用输入会发生什么?
【发布时间】:2011-10-07 05:35:45
【问题描述】:

Enumerator的定义是:

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

文档指出,Iteratees 消费数据,Enumerators 生产数据。我可以理解如何生成具有这种类型的数据:

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 在被赋予Continue 之后不会Continue,如果它确实会抛出错误。)子>

即,Iteratee 在与runIteratee 一起运行时会产生Step。这个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 函数所示。

我的问题是:

  • 如果您尝试像 enumStreamWeird 那样“使用”Enumerator 中的数据,会发生什么?数据从何而来?

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

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

【问题讨论】:

    标签: haskell iterate


    【解决方案1】:

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

    如何将 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&gt;&gt;== 的特例。

    首先,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,然后将其Step 传递给consumeEnum。这意味着"Gimmie a string!" 将在控制到达consumeEnum 之前打印。

    【讨论】:

      【解决方案2】:

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

      【讨论】:

        猜你喜欢
        • 2013-11-08
        • 1970-01-01
        • 2020-03-20
        • 2015-07-15
        • 1970-01-01
        • 1970-01-01
        • 2017-12-03
        • 2014-03-27
        • 2011-11-13
        相关资源
        最近更新 更多