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.
convStream
và unfoldConvStream
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ẻ.
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. –
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ũ! –
Cảm ơn bạn đã chỉ ra điều này, tôi đã thêm ngữ cảnh lớp loại. –