繁体   English   中英

嵌套的Iteratees

[英]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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM