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
比这更复杂......它显然会检查以确保Iteratee
在Continue
后EOF
没有Iteratee
如果错误的话。)
即runIteratee
与Step
一起运行时产生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}}如何做它的作用。
答案 0 :(得分:3)
是的,枚举器可以使用数据。枚举器基本上接受一个迭代,并在它被送入一些项后将其转换为相同的迭代。如果枚举器要求输入,则生成的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。但是,枚举器通常为迭代器提供它具有的输入,然后将控制权交给迭代器。
假设我们有一个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!"
中定义
和一个为其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
的第二种情况处理的。
考虑一个消耗输入的枚举器:
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。