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 также допускают монадические действия, так как итерирующая обработка потока является монадным преобразователем.

Другие вопросы по тегам