[英]Nested Iteratees
我正在使用一个特定的数据库,在成功查询后,您可以使用特定命令访问结果数据的一组块:
getResultData :: IO (ResponseCode, ByteString)
现在getResultData将返回响应代码和一些响应代码如下所示的数据:
response = GET_DATA_FAILED | OPERATION_SUCCEEDED | NO_MORE_DATA
ByteString是一个,部分或全部块:
数据http://desmond.imageshack.us/Himg189/scaled.php?server=189&filename=chunksjpeg.png&res=medium
故事并没有在这里结束。 存在一组群体:
流http://desmond.imageshack.us/Himg695/scaled.php?server=695&filename=chunkgroupsjpeg.png&res=medium
一旦从getResultData接收到NO_MORE_DATA响应,对getNextItem的调用将迭代流,允许我再次启动对getResultData的调用。 一旦getNextItem返回STREAM_FINISHED,那就是她所写的全部内容; 我有我的数据。
现在,我希望使用Date.Iteratee或Data.Enumerator重新构建此现象。 因为我现有的Data.Iteratee解决方案有效,但它似乎非常幼稚,我觉得好像我应该使用嵌套的iteratees来建模,而不是一个大的iteratee blob,这就是我的解决方案当前的实现方式。
我一直在看Data.Iteratee 0.8.6.2的代码,当涉及到嵌套的东西时我有点困惑。
嵌套迭代是正确的行动方案吗? 如果是这样,那么如何使用嵌套迭代对此进行建模?
问候
我认为嵌套迭代是正确的方法,但是这种情况有一些独特的问题,这使得它与大多数常见的例子略有不同。
大块和团体
第一个问题是使数据源正确。 基本上你所描述的逻辑分区会给你一个等于[[ByteString]]
的流。 如果您创建一个枚举器来直接生成它,则流中的每个元素都将是一组完整的块,这可能是您希望避免的(出于内存原因)。 您可以将所有内容[ByteString]
为单个[ByteString]
,但是之后您需要重新引入边界,因为db正在为您执行此操作,这将非常浪费。
暂时忽略组流,您需要自己将数据分成块。 我将其建模为:
enumGroup :: Enumerator ByteString IO a
enumGroup = enumFromCallback cb ()
where
cb () = do
(code, data) <- getResultData
case code of
OPERATION_SUCCEEDED -> return $ Right ((True, ()), data)
NO_MORE_DATA -> return $ Right ((False, ()), data)
GET_DATA_FAILED -> return $ Left MyException
由于块是固定大小的,因此您可以使用Data.Iteratee.group
轻松地将其分块。
enumGroupChunked :: Iteratee [ByteString] IO a -> IO (Iteratee ByteString IO a)
enumGroupChunked = enumGroup . joinI . group groupSize
将其类型与Enumerator
进行比较
type Enumerator s m a = Iteratee s m a -> m (Iteratee s m a)
所以enumGroupChunked
基本上是一个改变流类型的花哨的枚举器。 这意味着它需要一个[ByteString] iteratee使用者,并返回一个消耗普通字节串的iteratee。 通常,调查员的返回类型无关紧要; 它只是一个用run
(或tryRun
)来评估输出的tryRun
,所以你可以在这里做同样的事情:
evalGroupChunked :: Iteratee [ByteString] IO a -> IO a
evalGroupChunked i = enumGroupChunked i >>= run
如果您对每个组执行更复杂的处理,最简单的方法是在enumGroupChunked
函数中。
团体流
现在这已经不在了,如何处理群组流? 答案取决于您想要如何消费它们。 如果你想基本上独立地处理流中的每个组,我会做类似的事情:
foldStream :: Iteratee [ByteString] IO a -> (b -> a -> b) -> b -> IO b
foldStream iter f acc0 = do
val <- evalGroupChunked iter
res <- getNextItem
case res of
OPERATION_SUCCEEDED -> foldStream iter f $! f acc0 val
NO_MORE_DATA -> return $ f acc0 val
GET_DATA_FAILED -> error "had a problem"
但是,假设您想要对整个数据集进行某种流处理,而不仅仅是单个组。 也就是说,你有一个
bigProc :: Iteratee [ByteString] IO a
您想要在整个数据集上运行。 这是枚举器的返回迭代有用的地方。 一些早期的代码现在会略有不同:
enumGroupChunked' :: Iteratee [ByteString] IO a
-> IO (Iteratee ByteString IO (Iteratee [ByteString] IO a))
enumGroupChunked' = enumGroup . group groupSize
procStream :: Iteratee [ByteString] IO a -> a
procStream iter = do
i' <- enumGroupChunked' iter >>= run
res <- getNextItem
case res of
OPERATION_SUCCEEDED -> procStream i'
NO_MORE_DATA -> run i'
GET_DATA_FAILED -> error "had a problem"
嵌套迭代器(即Iteratee s1 m (Iteratee s2 ma)
)的这种用法略显不常见,但是当您想要顺序处理来自多个枚举器的数据时,它尤其有用。 关键是要认识到run
外部迭代将为您提供一个可以接收更多数据的迭代。 这是一个在这种情况下运行良好的模型,因为您可以独立枚举每个组,但将它们作为单个流处理。
一个警告:内部迭代将处于它所处的任何状态。假设一个组的最后一个块可能小于一个完整的块,例如
Group A Group B Group C
1024, 1024, 512 1024, 1024, 1024 1024, 1024, 1024
在这种情况下会发生的是,因为group
将数据组合成大小为1024的块,它将组A的最后一个块与组B的前512个字节组合。这不是foldStream
示例的问题,因为代码终止内部iteratee(使用joinI
)。 这意味着这些团体是真正独立的,所以你必须这样对待它们。 如果要在procStream
组合组,则必须考虑整个流。 如果这是你的情况,那么你需要使用比group
更复杂的东西。
Data.Iteratee与Data.Enumerator
没有讨论任何一个软件包的优点,更不用说IterIO (我承认有偏见),我想指出我认为两者之间最重要的区别:流的抽象。
在Data.Iteratee中,消费者Iteratee ByteString ma
在一定长度的名义ByteString上操作,同时可以访问单个ByteString
块。
在Data.Enumerator中,消费者Iteratee ByteString ma
在名义上的[ByteString]上操作,同时可以访问一个或多个元素(字节串)。
这意味着大多数Data.Iteratee操作都是以元素为中心的,即使用Iteratee ByteString
它们将在单个Word8
,而Data.Enumerator操作是以块为中心的,在ByteString
。
你可以想到Data.Iteratee.Iteratee [s] ma
=== Data.Enumerator.Iteratee sma
。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.