2011-07-10 30 views
19

Tôi đang cố gắng hiểu cách sử dụng thư viện iteratee với Haskell. Tất cả các bài viết tôi đã thấy cho đến nay dường như tập trung vào việc xây dựng một trực giác cho cách lặp lại có thể được xây dựng, điều đó rất hữu ích, nhưng bây giờ tôi muốn xuống và sử dụng chúng, tôi cảm thấy một chút trên biển. Nhìn vào mã nguồn cho các iterate có giá trị giới hạn đối với tôi.Haskell iteratee: ví dụ đơn giản đã làm việc để xóa khoảng trống ở cuối

Hãy nói rằng tôi có chức năng này mà Trims dấu khoảng trắng từ một dòng:

import Data.ByteString.Char8 

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

Những gì tôi muốn làm là: làm cho điều này trở thành một iteratee, đọc một tập tin và viết nó ra ở một nơi khác với khoảng trắng sau bị tước khỏi mỗi dòng. Làm thế nào tôi sẽ đi về cấu trúc với iteratees? Tôi thấy có chức năng enumLinesBS trong Data.Iteratee.Char mà tôi có thể nói rõ điều này, nhưng tôi không biết liệu tôi có nên sử dụng mapChunks hoặc convStream hoặc cách đóng gói lại chức năng ở trên thành một iteratee.

Trả lời

16

Nếu bạn chỉ muốn mã, nó này:

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

Bình luận:

Đây là một quá trình gồm ba giai đoạn: trước hết bạn chuyển đổi các dòng thô thành một dòng dòng, sau đó bạn áp dụng của bạn để chuyển đổi luồng của dòng đó và cuối cùng bạn tiêu thụ luồng. Kể từ khi rstrip là ở giai đoạn giữa, nó sẽ tạo ra một biến dòng (Enumeratee).

Bạn có thể sử dụng mapChunks hoặc convStream, nhưng mapChunks thì đơn giản hơn. Sự khác biệt là mapChunks không cho phép bạn vượt qua ranh giới đoạn, trong khi convStream là tổng quát hơn. Tôi thích convStream vì không hiển thị bất kỳ triển khai cơ bản nào, nhưng nếu mapChunks đủ mã kết quả thường ngắn hơn.

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

Lưu ý thêm map trong rstripE. Luồng ngoài (là đầu vào để rstrip) có loại [ByteString], vì vậy chúng tôi cần ánh xạ rstrip vào nó.

Để so sánh, đây là những gì nó sẽ trông như thế nào nếu thực hiện với 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 

Đây là lâu hơn, và đó là kém hiệu quả vì nó sẽ chỉ áp dụng chức năng rstrip đến một dòng tại một thời điểm, thậm chí mặc dù nhiều dòng hơn có thể có sẵn. Có thể làm việc trên tất cả các đoạn hiện có, đó là gần gũi hơn với phiên bản mapChunks:

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

Dù sao, với enumeratee tước sẵn, nó dễ dàng sáng tác với enumLinesBS enumeratee:

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

Toán tử thành phần ><> theo cùng thứ tự với toán tử mũi tên >>>. enumLinesBS tách luồng thành các dòng, sau đó cắt rstripE.Bây giờ bạn chỉ cần thêm một người tiêu dùng (mà là một iteratee bình thường), và bạn đã hoàn tất:

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

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

Các fileDriver chức năng phím tắt cho chỉ đơn giản là liệt kê trên một tập tin và chạy kết quả iteratee (tiếc là để tranh luận được chuyển từ enumFile):

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

Phụ lục: đây là trường hợp bạn cần thêm sức mạnh của convStream. Giả sử bạn muốn nối hai dòng thành một. Bạn không thể sử dụng mapChunks. Hãy xem xét khi đoạn là một phần tử singleton, [bytestring]. mapChunks không cung cấp bất kỳ cách nào để truy cập vào đoạn tiếp theo, vì vậy không có gì khác để nối với điều này. Với Tuy nhiên convStream, nó đơn giản:

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

này trông thậm chí còn đẹp hơn trong phong cách applicative,

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

Bạn có thể nghĩ convStream như liên tục tiêu thụ một phần của dòng với iteratee cung cấp, sau đó gửi chuyển đổi phiên bản cho người tiêu dùng bên trong. Đôi khi ngay cả điều này không đủ chung, vì cùng một iteratee được gọi ở mỗi bước. Trong trường hợp đó, bạn có thể sử dụng unfoldConvStream để chuyển trạng thái giữa các lần lặp liên tiếp.

convStreamunfoldConvStream cũng cho phép thực hiện các tác vụ đơn thuần, vì quá trình xử lý luồng lặp lại là một biến thể đơn lẻ.

+0

John, cảm ơn bạn vì câu trả lời cực kỳ chi tiết này! Đây chính xác là những gì tôi cần. –

+0

Hai ghi chú nhỏ: loại rstripE cần một trình phân loại kiểu chữ (Monad m) =>, và hàm rstrip của tôi cần phải dán một dòng mới vào cuối để tích hợp với enumLinesBS. Nếu không, nó hoạt động như một sự quyến rũ! –

+0

Cảm ơn bạn đã chỉ ra điều này, tôi đã thêm ngữ cảnh lớp loại. –

Các vấn đề liên quan