Hastell iteratee: простой проработанный пример удаления конечных пробелов
Я пытаюсь понять, как использовать библиотеку iteratee с Haskell. Кажется, что все статьи, которые я видел до сих пор, сосредоточены на построении интуиции о том, как могут быть построены итераторы, что полезно, но теперь, когда я хочу опускаться и фактически использовать их, я чувствую себя немного в море. Глядя на исходный код для итераторов, я имел ограниченную ценность.
Допустим, у меня есть эта функция, которая удаляет завершающий пробел из строки:
import Data.ByteString.Char8
rstrip :: ByteString -> ByteString
rstrip = fst . spanEnd isSpace
Я хотел бы сделать следующее: превратить это в итератора, прочитать файл и записать его где-нибудь в другом месте с конечным пробелом, убранным из каждой строки. Как бы я пошел структурировать это с итераторами? Я вижу, что есть enumLinesBS
функция в Data.Iteratee.Char, которую я мог бы добавить в это, но я не знаю, следует ли мне использовать mapChunks
или же convStream
или как упаковать вышеупомянутую функцию в итерируемого.
1 ответ
Если вы просто хотите код, это так:
procFile' iFile oFile = fileDriver (joinI $
enumLinesBS ><>
mapChunks (map rstrip) $
I.mapM_ (B.appendFile oFile))
iFile
Комментарий:
Это трехэтапный процесс: сначала вы преобразуете необработанный поток в поток строк, затем применяете свою функцию для преобразования этого потока строк, и, наконец, вы используете поток. поскольку rstrip
находится в средней стадии, это будет создание потокового преобразователя (Enumeratee).
Вы можете использовать либо mapChunks
или же convStream
, но mapChunks
проще Разница в том, что mapChunks
не позволяет вам пересечь границы чанка, в то время как convStream
является более общим. я предпочитаю convStream
потому что он не раскрывает какую-либо из базовой реализации, но если mapChunks
Достаточно, результирующий код обычно короче.
rstripE :: Monad m => Enumeratee [ByteString] [ByteString] m a
rstripE = mapChunks (map rstrip)
Обратите внимание на дополнительные map
в rstripE
, Внешний поток (который является входом для 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)
В любом случае, при наличии доступного для перебора участника, его легко составить с помощью enumLinesBS
enumeratee:
enumStripLines :: Monad m => Enumeratee ByteString [ByteString] m a
enumStripLines = enumLinesBS ><> rstripE
Оператор композиции ><>
следует в том же порядке, что и оператор стрелки >>>
, enumLinesBS
разбивает поток на строки, затем rstripE
раздевает их. Теперь вам просто нужно добавить потребителя (который является обычным итератором), и все готово:
writer :: FilePath -> Iteratee [ByteString] IO ()
writer fp = I.mapM_ (B.appendFile fp)
processFile iFile oFile =
enumFile defaultBufSize iFile (joinI $ enumStripLines $ writer oFile) >>= run
fileDriver
функции - это ярлыки для простого перечисления по файлу и запуска получающегося итерируемого (к сожалению, порядок аргументов переключен с enumFile):
procFile2 iFile oFile = fileDriver (joinI $ enumStripLines $ writer oFile) iFile
Приложение: вот ситуация, когда вам понадобится дополнительная сила convStream. Предположим, вы хотите объединить каждые 2 строки в одну. Вы не можете использовать 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
как непрерывное потребление части потока с предоставленным итератором, затем отправка преобразованной версии внутреннему потребителю. Иногда даже это не является достаточно общим, так как один и тот же итератор вызывается на каждом шаге. В этом случае вы можете использовать unfoldConvStream
передать состояние между последовательными итерациями.
convStream
а также unfoldConvStream
также допускают монадические действия, так как итерирующая обработка потока является монадным преобразователем.