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