2011-07-10 57 views
19

我想了解如何在Haskell中使用iteratee庫。到目前爲止,我所見過的所有文章似乎都集中在如何構建迭代器的直覺上,這很有幫助,但現在我想要放下並實際使用它們,我感覺有點不知所措。查看迭代的源代碼對我來說是有限的價值。Haskell迭代器:簡單的剝離尾部空白的工作示例

比方說,我有這個功能,修剪從行尾部的空白:

import Data.ByteString.Char8 

rstrip :: ByteString -> ByteString 
rstrip = fst . spanEnd isSpace 

我想要做的是:讓這個成iteratee,讀取文件並寫入別的某處從每行刪除尾隨的空白。我將如何去構建與iteratees?我看到Data.Iteratee.Char中有一個enumLinesBS函數,我可以探究這個函數,但我不知道是否應該使用mapChunksconvStream或者如何將上面的函數重新打包爲迭代器。

回答

16

如果你只是想代碼,那就是:

procFile' iFile oFile = fileDriver (joinI $ 
    enumLinesBS ><> 
    mapChunks (map rstrip) $ 
    I.mapM_ (B.appendFile oFile)) 
    iFile 

解說:

這是一個過程分爲三個階段:首先你變換原料流進行流,那麼你申請你函數來轉換該線路流,並最終消耗流。由於rstrip處於中間階段,因此它將創建一個流轉換器(Enumeratee)。

您可以使用mapChunksconvStream,但mapChunks更簡單。區別在於mapChunks不允許您跨越區塊邊界,而convStream則更爲一般。我更喜歡convStream,因爲它沒有公開任何底層實現,但是如果mapChunks就足夠了,結果代碼通常會更短。

rstripE :: Monad m => Enumeratee [ByteString] [ByteString] m a 
rstripE = mapChunks (map rstrip) 

注意在rstripE額外map。外部流(這是rstrip的輸入)的類型爲[ByteString],所以我們需要將rstrip映射到它上面。

爲了便於比較,這是它會是什麼樣子,如果用convStream實現:

rstripE' :: Enumeratee [ByteString] [ByteString] m a 
rstripE' = convStream $ do 
    mLine <- I.peek 
    maybe (return B.empty) (\line -> I.drop 1 >> return (rstrip line)) mLine 

這是更長的時間,而且它的效率較低,因爲它只會在應用這些rstrip功能一行,甚至儘管可能有更多的線路可用。它可以對所有當前可用的塊,這是更接近mapChunks版本的工作:

rstripE'2 :: Enumeratee [ByteString] [ByteString] m a 
rstripE'2 = convStream (liftM (map rstrip) getChunk) 

無論如何,與現有剝離enumeratee,它很容易組成與enumLinesBS enumeratee:

enumStripLines :: Monad m => Enumeratee ByteString [ByteString] m a 
enumStripLines = enumLinesBS ><> rstripE 

組成運算符><>遵循與箭頭運算符>>>相同的順序。 enumLinesBS將流拆分成行,然後rstripE將它們剝離。現在你只需要添加一個用戶(這是正常iteratee),你就大功告成了:

writer :: FilePath -> Iteratee [ByteString] IO() 
writer fp = I.mapM_ (B.appendFile fp) 

processFile iFile oFile = 
    enumFile defaultBufSize iFile (joinI $ enumStripLines $ writer oFile) >>= run 

fileDriver功能快捷鍵簡單地列舉了一個文件,並運行所產生的iteratee(不幸的是,參數順序從enumFile切換):

procFile2 iFile oFile = fileDriver (joinI $ enumStripLines $ writer oFile) iFile 

附錄:這裏是您需要convStream的額外能力的情況。假設你想將每兩行連接成一個。您不能使用mapChunks。考慮塊是單獨元素時,[bytestring]mapChunks沒有提供任何訪問下一個塊的方法,所以沒有別的方法可以與它連接。隨着但是convStream,這很簡單:

concatPairs = convStream $ do 
    line1 <- I.head 
    line2 <- I.head 
    return $ line1 `B.append` line2 

這看起來即使在合用的風格更好,

convStream $ B.append <$> I.head <*> I.head 

你能想到的convStream的不斷消耗流的一部分與所提供的iteratee,然後發送轉變爲內在的消費者。有時甚至這還不夠普遍,因爲在每一步都會調用相同的迭代器。在這種情況下,可以使用unfoldConvStream在連續迭代之間傳遞狀態。

convStreamunfoldConvStream也允許一次性動作,因爲流處理iteratee是一個monad變換器。

+0

約翰,謝謝你這個非常詳細的答案!這正是我需要的。 –

+0

兩個小注釋:rstripE的類型需要一個類型類別限定符(Monad m)=>,並且我的rstrip函數需要在最後粘合一個換行符以與enumLinesBS集成。否則,它就像一個魅力! –

+0

感謝您指出了這一點,我添加了類型上下文。 –