haskell 嵌套迭代对象

4sup72z8  于 2023-04-30  发布在  其他
关注(0)|答案(1)|浏览(123)

我正在使用一个特定的数据库,在成功查询后,您可以使用特定命令访问一组结果数据块:

getResultData :: IO (ResponseCode, ByteString)

现在getResultData将返回一个响应代码和一些数据,其中响应代码如下所示:

response = GET_DATA_FAILED | OPERATION_SUCCEEDED | NO_MORE_DATA

ByteString是一个、一些或所有块:

故事并没有在这里结束。存在组流:

一旦从getResultData接收到NO_MORE_DATA响应,对getNextItem的调用将迭代流,允许我再次启动对getResultData的调用。一旦getNextItem返回STREAM_FINISHED,这就是她所写的全部内容;我有数据。
现在,我想用任何一种日期来重塑这种现象。Iteratee或Data。枚举器。因为我的现有数据。Iteratee解决方案工作,但它似乎非常天真,我觉得我应该用嵌套的可迭代对象来建模,而不是一个大的可迭代对象blob,这就是我的解决方案目前的实现方式。
我一直在看数据的代码。Iterateewww.example. www.example.com ,当涉及到嵌套的东西时,我有点困惑。

嵌套的迭代对象是正确的操作过程吗?如果是这样的话,如何用嵌套的可迭代对象来建模呢?

问候

ncecgwcz

ncecgwcz1#

我认为嵌套的可迭代对象是正确的方法,但是这个例子有一些独特的问题,使它与大多数常见的例子略有不同。

分块和分组

第一个问题是正确的数据源。基本上,您所描述的逻辑划分将给予您一个等价于[[ByteString]]的流。如果您创建一个枚举器来直接产生这个,那么流中的每个元素都将是一个完整的块组,这可能是您希望避免的(出于内存原因)。您可以将所有内容扁平化为一个[ByteString],但这样就需要重新引入边界,这将是非常浪费的,因为数据库正在为您做这件事。
现在先忽略组流,看起来您需要自己将数据划分成块。我将其建模为:

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]迭代者消费者,并返回一个消费普通字节串的迭代者。枚举数的返回类型通常并不重要;它只是一个迭代对象,你可以用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"

这种嵌套的可迭代体(i.例如Iteratee s1 m (Iteratee s2 m a))有点不常见,但当您希望顺序处理来自多个Enumerator的数据时,它特别有用。关键是要认识到run外部迭代器将给予您一个准备好接收更多数据的迭代器。在这种情况下,这是一个很好的模型,因为您可以独立地枚举每个组,但将它们作为单个流进行处理。
一个警告:内部被迭代者将处于其被保留的任何状态。假设组的最后一个块可能小于完整块,e.g的。

Group A               Group B               Group C
   1024, 1024, 512       1024, 1024, 1024      1024, 1024, 1024

在这种情况下,由于group将数据组合成大小为1024的块,因此它将合并组A的最后一个块和组B的前512个字节。这在foldStream示例中不是问题,因为该代码终止了内部被迭代者(使用joinI)。这意味着这些组是真正独立的,所以你必须这样对待它们。如果你想像procStream那样合并这些组,你必须考虑整个流。如果是这种情况,那么您需要使用比group更复杂的东西。

数据。迭代对象vs数据。枚举器

在不讨论任何一个包的优点的情况下,更不用说IterIO了(我承认我有偏见),我想指出我认为两者之间最重要的区别:流的抽象。
在Data.Iteratee中,使用者Iteratee ByteString m a对某个长度的概念性ByteString进行操作,同时可以访问ByteString的单个块。
在Data.Enumerator中,消费者Iteratee ByteString m a对概念性的[ByteString]进行操作,可以同时访问一个或多个元素(字节串)。
这意味着大多数数据。迭代对象操作是以元素为中心的,也就是说,对于Iteratee ByteString,它们将在单个Word8上操作,而Data。枚举器操作是以块为中心的,在ByteString上操作。
Data.Iteratee.Iteratee [s] m a == Data.Enumerator.Iteratee s m a

相关问题