Подтвердить что ты не робот

Haskell iteratee: простой обработанный пример удаления завершающих пробелов

Я пытаюсь понять, как использовать библиотеку iteratee с Haskell. Все статьи, которые я видел до сих пор, похоже, сосредоточены на создании интуиции для того, как можно построить итерации, что полезно, но теперь, когда я хочу спуститься и фактически использовать их, я немного чувствую себя в море. Глядя на исходный код для итераций, для меня было ограниченное значение.

Скажем, у меня есть эта функция, которая обрезает конечную пробел из строки:

import Data.ByteString.Char8

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

То, что я хотел бы сделать, это сделать это в итерационном режиме, прочитать файл и записать его где-нибудь еще с завершающим пробелом, отделенным от каждой строки. Как бы я начал структурировать это с помощью итераций? Я вижу там функцию enumLinesBS в Data.Iteratee.Char, которую я мог бы вложить в это, но я не знаю, следует ли мне использовать mapChunks или convStream или как переупаковать вышеперечисленную функцию в iteratee.

4b9b3361

Ответ 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)

Во всяком случае, с доступным списком enumeratee, он легко состоит из 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 - это ярлыки для простого перечисления над файлом и запуска результирующего 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 как о постоянном потреблении части потока с предоставленным итерацией, а затем передавать преобразованную версию внутреннему потребителю. Иногда даже это недостаточно общее, так как на каждом шаге вызывается тот же самый итератор. В этом случае вы можете использовать unfoldConvStream для передачи состояния между последовательными итерациями.

convStream и unfoldConvStream также допускают монодичные действия, поскольку итерация обработки потока является монадным трансформатором.