Ленивый ввод-вывод
Ленивый ввод-вывод работает так
readFile :: FilePath -> IO ByteString
где ByteString
гарантированно читается только по частям. Для этого мы могли бы (почти) написать
-- given `readChunk` which reads a chunk beginning at n
readChunk :: FilePath -> Int -> IO (Int, ByteString)
readFile fp = readChunks 0 where
readChunks n = do
(n', chunk) <- readChunk fp n
chunks <- readChunks n'
return (chunk <> chunks)
но здесь мы отмечаем, что действие ввода-вывода readChunks n'
выполняется до возврата даже частичного результата, доступного как chunk
. Это значит, что мы совсем не ленивы. Для борьбы с этим мы используем unsafeInterleaveIO
readFile fp = readChunks 0 where
readChunks n = do
(n', chunk) <- readChunk fp n
chunks <- unsafeInterleaveIO (readChunks n')
return (chunk <> chunks)
что заставляет readChunks n'
возвращаться немедленно, а IO
действие выполняется только тогда, когда этот переход принудительно выполняется.
Это опасная часть: с помощью unsafeInterleaveIO
мы отложили набор IO
действий до недетерминированных моментов в будущем, которые зависят от того, как мы потребляем наши фрагменты ByteString
.
Исправление проблемы с сопрограммами
Что мы хотели бы сделать, так это вставить шаг обработки фрагмента между вызовом readChunk
и рекурсией на readChunks
.
readFileCo :: Monoid a => FilePath -> (ByteString -> IO a) -> IO a
readFileCo fp action = readChunks 0 where
readChunks n = do
(n', chunk) <- readChunk fp n
a <- action chunk
as <- readChunks n'
return (a <> as)
Теперь у нас есть возможность выполнять произвольные IO
действий после загрузки каждого маленького фрагмента. Это позволяет нам выполнять гораздо больше работы постепенно, без полной загрузки ByteString
в память. К сожалению, это не очень композиционно — нам нужно построить наше потребление action
и передать его нашему ByteString
производителю, чтобы он заработал.
Ввод-вывод на основе каналов
По сути, это то, что решает pipes
— он позволяет нам легко составлять эффективные сопрограммы. Например, теперь мы пишем нашу программу чтения файлов как Producer
, которую можно рассматривать как "потоковую передачу" фрагментов файла, когда ее эффект наконец запускается.
produceFile :: FilePath -> Producer ByteString IO ()
produceFile fp = produce 0 where
produce n = do
(n', chunk) <- liftIO (readChunk fp n)
yield chunk
produce n'
Обратите внимание на сходство между этим кодом и кодом readFileCo
выше — мы просто заменяем вызов действия сопрограммы на yield
ing chunk
, который мы создали до сих пор. Этот вызов yield
создает тип Producer
вместо необработанного действия IO
, которое мы можем скомпоновать с другими типами Pipe
s, чтобы построить хороший конвейер потребления, называемый Effect IO ()
.
Все это построение каналов выполняется статически без фактического вызова каких-либо действий IO
. Вот как pipes
упрощает написание сопрограмм. Все эффекты срабатывают одновременно, когда мы вызываем runEffect
в нашем действии main
IO
.
runEffect :: Effect IO () -> IO ()
Аттопарсек
Итак, почему вы хотите подключить attoparsec
к pipes
? Ну, attoparsec
оптимизирован для ленивого синтаксического анализа. Если вы производите фрагменты, подаваемые парсеру attoparsec
, эффективным способом, то вы окажетесь в тупике. Ты мог бы
- Используйте строгий ввод-вывод и загрузите всю строку в память только для того, чтобы лениво использовать ее с помощью вашего синтаксического анализатора. Это просто, предсказуемо, но неэффективно.
- Используйте ленивый ввод-вывод и потеряйте возможность рассуждать о том, когда ваши производственные эффекты ввода-вывода будут фактически запущены, что приведет к возможным утечкам ресурсов или закрытым исключениям дескриптора в соответствии с графиком потребления ваших проанализированных элементов. Это более эффективно, чем (1), но может легко стать непредсказуемым; или,
- Используйте
pipes
(или conduit
) для создания системы сопрограмм, в которую входит ваш ленивый парсер attoparsec
, позволяющий ему обрабатывать как можно меньше входных данных, в то же время создавая максимально лениво анализируемые значения по всему потоку.
person
J. Abrahamson
schedule
30.03.2014