嵌套的Iteratees

时间:2011-09-26 20:28:54

标签: haskell monads iterate

我正在使用特定的数据库,在成功的情况下 查询,您可以使用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

这个故事并没有在这里结束。存在一组群体:

Stream 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解决方案有效,但看起来很幼稚,我觉得我应该对此进行建模 使用嵌套的迭代,而不是一个大的iteratee blob,这是如何 我的解决方案目前正在实施。

我一直在看Data.Iteratee 0.8.6.2的代码,我有点困惑 当谈到嵌套的东西时。

嵌套迭代正确的行动方案吗?如果是这样,那么如何使用嵌套迭代对此进行建模?

此致

1 个答案:

答案 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