簡體   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