我正在使用特定的数据库,在成功的情况下 查询,您可以使用a访问结果数据的一组块 具体命令:
getResultData :: IO (ResponseCode, ByteString)
现在getResultData将返回响应代码和一些数据 响应代码如下所示:
response = GET_DATA_FAILED | OPERATION_SUCCEEDED | NO_MORE_DATA
ByteString是一个,部分或全部块:
Data http://desmond.imageshack.us/Himg189/scaled.php?server=189&filename=chunksjpeg.png&res=medium
这个故事并没有在这里结束。存在一组群体:
从getResultData接收到NO_MORE_DATA响应后,调用 getNextItem将迭代流,允许我开始调用 再次获取getResultData。一旦getNextItem返回STREAM_FINISHED,那就是 她写的所有;我有我的数据。
现在,我希望使用Date.Iteratee或Data.Enumerator重新构建此现象。因为我的 现有的Data.Iteratee解决方案有效,但看起来很幼稚,我觉得我应该对此进行建模 使用嵌套的迭代,而不是一个大的iteratee blob,这是如何 我的解决方案目前正在实施。
我一直在看Data.Iteratee 0.8.6.2的代码,我有点困惑 当谈到嵌套的东西时。
嵌套迭代正确的行动方案吗?如果是这样,那么如何使用嵌套迭代对此进行建模?
此致
答案 0 :(得分:3)
我认为嵌套迭代是正确的方法,但是这种情况有一些独特的问题,这使得它与大多数常见的例子略有不同。
大块和小组
第一个问题是使数据源正确。基本上,您描述的逻辑分区将为您提供相当于[[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
)来评估输出的迭代,所以你可以在这里做同样的事情:
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 m a)
)的这种用法略显不常见,但是当您想要顺序处理来自多个枚举器的数据时,它尤其有用。关键是要认识到run
外部迭代将为您提供一个准备好接收更多数据的迭代。这是一个在这种情况下运行良好的模型,因为您可以独立枚举每个组,但将它们作为单个流处理。
一个警告:内部迭代将处于它所处的任何状态。假设一个组的最后一个块可能小于一个完整的块,例如
Group A Group B Group C
1024, 1024, 512 1024, 1024, 1024 1024, 1024, 1024
在这种情况下会发生的是,因为group
正在将数据组合成大小为1024的块,它将组A的最后一个块与组B的前512个字节组合。这不是问题与foldStream
示例有关,因为该代码终止了内部迭代(使用joinI
)。这意味着这些团体是真正独立的,所以你必须这样对待它们。如果要像procStream
一样组合组,则必须考虑整个流。如果是这种情况,那么您需要使用比group
更复杂的东西。
Data.Iteratee与Data.Enumerator
没有讨论任何一个包的优点,更不用说IterIO(我承认有偏见),我想指出我认为两者之间最重要的区别:抽象流。
在Data.Iteratee中,消费者Iteratee ByteString m a
使用一定长度的名义ByteString进行操作,同时可以访问单个ByteString
块。
在Data.Enumerator中,消费者Iteratee ByteString m a
在名义[ByteString]上运行,一次可以访问一个或多个元素(字节串)。
这意味着大多数Data.Iteratee操作都是以元素为中心的,即Iteratee ByteString
它们将在单个Word8
上运行,而Data.Enumerator操作是以块为中心的,操作于ByteString
。
您可以考虑Data.Iteratee.Iteratee [s] m a
=== Data.Enumerator.Iteratee s m a
。