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

时间:2011-10-07 05:35:46

标签: haskell iterate

Enumerator的定义是:

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

文档指出,Iteratee的comsume数据,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比这更复杂......它显然会检查以确保IterateeContinueEOF没有Iteratee如果错误的话。)

runIterateeStep一起运行时产生Step。然后将此Stream提供给我的枚举器,该枚举器为其提供Iteratee,以便它可以继续。我的枚举器返回结果延续。

有一点让我很突出:这段代码在-- | 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 monad中运行。这意味着它可以使用数据,对吗?

type Enumeratee ao ai m b = Step ai m b -> Iteratee ao m (Step ai m b)

文档指出,当枚举器同时充当源和接收器时,应使用Enumeratee代替:

Enumerator

然而,显然我没有;我可以在enumStreamWeird的定义中使用输入,正如我的Enumerator函数所示。

我的问题是:

  • 如果您尝试在enumStreamWeird内“消费”数据,会发生什么情况,例如Enumerator?数据来自哪里?

  • 即使我们没有足够的疯狂来消耗枚举器中的数据,代表枚举器在底层monad中执行操作是有效的,而不是代表迭代者读取数据我们'重新生产?

后一个问题与我的主要问题可能不太相关,但我试图理解{{1}}如何做它的作用。

2 个答案:

答案 0 :(得分:3)

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

如何将一个枚举器送入一个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运行iteratee直到它准备好输入。然后,它将iteratee的第一个Step传递给枚举器。调查员从那里接管。

最后一句非常重要。枚举器可以用它的iteratee做任何想做的事情。如果它想要,它可以完全丢弃iteratee。但是,枚举器通常为迭代器提供它具有的输入,然后将控制权交给迭代器。

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

中定义

和一个为其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` consumeEnum做什么?通过查看consumeEnum自己的实现可以回答这个问题。首先,它需要一个项目并丢弃它。然后它将火炬交给iter3,这需要另外三件物品。

然而,回顾一下feed组合子。首先运行iter3,然后将其Step传递给consumeEnum。这意味着在控制到达"Gimmie a string!"之前将打印consumeEnum

答案 1 :(得分:1)

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