如果枚举器尝试消耗输入会发生什么?

发布于 2024-12-08 18:05:42 字数 2571 浏览 1 评论 0 原文

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 中“使用”数据,就像 enumStreamWeird 那样,会发生什么?数据从哪里来?

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

后一个问题可能与我的主要问题不太相关,但我试图了解 Enumerator 是如何完成它的工作的。

The definition of Enumerator is:

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

The documentation states that while Iteratees comsume data, Enumerators 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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

你的往事 2024-12-15 18:05:42

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

如何将枚举器提供给迭代器

让我们看看如何将枚举器提供给迭代器:

-- | 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 运行 iteratee,直到它准备好输入。然后,它传递迭代器的第一个 步骤到枚举器。枚举器从那里接管。

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

示例 1:向迭代器提供枚举器

假设我们有一个迭代器,它请求三个字符串并打印它们:

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 .

和一个为其 iteratee 提供单个字符串的枚举器:

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

当为 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:消耗输入的枚举器

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

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

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:

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

Note: feed is a special case of >>==.

First, feed runs the iteratee until it is ready for input. Then, it passes the iteratee's first Step 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:

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_ is defined in Data.Enumerator.List.

and an enumerator that feeds its iteratee a single string:

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

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 by getString's second case.

Example 2: An enumerator that consumes input

Consider an enumerator that does consume input:

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

What does iter3 `feed` consumeEnum do? That can sort of be answered by looking at consumeEnum's own implementation. First it needs an item and discards it. Then it hands the torch to iter3, which needs three more items.

However, look back to the feed combinator. It starts by running iter3, then passes its Step to consumeEnum. This means "Gimmie a string!" will be printed before control reaches consumeEnum.

扛起拖把扫天下 2024-12-15 18:05:42

枚举器消耗数据没有任何问题。它是一个 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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文