Ответ 1
Во-первых, я хотел бы отметить, что довольно много программистов Haskell относятся к ленивому ИО вообще с некоторым подозрением. Это технически нарушает чистоту, но ограниченным образом, что (насколько мне известно) не заметно при запуске одной программы на последовательном входе [0]. С другой стороны, много людей в порядке с этим, опять же потому, что оно связано только с очень ограниченным видом нечистоты.
Чтобы создать иллюзию ленивой структуры данных, которая фактически создана с помощью операций ввода-вывода по требованию, функции, подобные readFile
, реализуются с использованием скрытых шенидов за кулисами. Ткачество в режиме ввода-вывода по требованию является неотъемлемой функцией, и оно не является действительно расширяемым по почти тем же причинам, что иллюзия получения регулярного ByteString
от него убедительна.
Ручная обработка деталей и запись псевдокода, что-то вроде readFile в основном работает следующим образом:
lazyInput inp = lazyIO (lazyInput' inp)
lazyInput' inp = do x <- readFrom inp
if (endOfInput inp)
then return []
else do xs <- lazyInput inp
return (x:xs)
... где вызывается каждый раз lazyIO
, он откладывает ввод/вывод до тех пор, пока значение не будет фактически использовано. Чтобы вызывать функцию отчетности каждый раз, когда происходит фактическое чтение, вам нужно будет сплести его напрямую, и, хотя, возможно, существует обобщенная версия такой функции, насколько мне известно, не существует.
Учитывая вышеизложенное, у вас есть несколько вариантов:
-
Посмотрите на реализацию ленивых функций ввода-вывода, которые вы используете, и реализуйте свои собственные, которые включают в себя функцию отчета о ходе выполнения. Если это похоже на грязный хак, это потому, что это в значительной степени, но там вы идете.
-
Отказаться от ленивого ввода-вывода и перейти к чему-то более явному и сложному. Это направление, в котором, по-видимому, входит сообщество Haskell в целом, в частности, к некоторым вариантам Iteratees, которые дают вам красиво настраиваемый небольшой поток процессорные блоки, которые имеют более предсказуемое поведение. Недостатком является то, что концепция все еще находится в активном развитии, поэтому нет консенсуса в отношении реализации или единой отправной точки для обучения их использованию.
-
Отбросить ленивый ввод-вывод и переключиться на обычный старый регулярный ввод-вывод: записать действие
IO
, которое читает фрагмент, печатает информацию о отчетах и обрабатывает как можно больше ввода; затем вызовите его в цикле до завершения. В зависимости от того, что вы делаете с помощью ввода, и насколько вы полагаетесь на лень в своей обработке, это может включать в себя что угодно: от написания нескольких почти тривиальных функций до создания пучка поточных процессоров конечных автоматов и получения 90 % от способа изобретать Iteratees.
[0]: Основная функция здесь называется unsafeInterleaveIO
, и, насколько мне известно, единственные способы наблюдать за ней примесь требуют либо запуска программы на другом входе (в которой в случае, если он имеет право вести себя по-разному, это может быть просто так, что это не имеет смысла в чистом коде) или при изменении кода определенным образом (т.е. рефакторинг, который не должен иметь эффекта, может иметь нелокальные эффекты),
Вот пример того, как делать "обычный старый обычный ввод-вывод", используя более сложные функции:
import System
import System.IO
import qualified Data.ByteString.Lazy as B
main = do [from, to] <- getArgs
-- withFile closes the handle for us after the action completes
withFile from ReadMode $ \inH ->
withFile to WriteMode $ \outH ->
-- run the loop with the appropriate actions
runloop (B.hGet inH 128) (processBytes outH) B.null
-- note the very generic type; this is useful, because it proves that the
-- runloop function can only execute what it given, not do anything else
-- behind our backs.
runloop :: (Monad m) => m a -> (a -> m ()) -> (a -> Bool) -> m ()
runloop inp outp done = do x <- inp
if done x
then return ()
else do outp x
runloop inp outp done
-- write the output and report progress to stdout. note that this can be easily
-- modified, or composed with other output functions.
processBytes :: Handle -> B.ByteString -> IO ()
processBytes h bs | B.null bs = return ()
| otherwise = do onReadBytes (fromIntegral $ B.length bs)
B.hPut h bs
onReadBytes :: Integer -> IO ()
onReadBytes count = putStrLn $ "Bytes read: " ++ (show count)
В "128" вверх есть количество байтов для чтения за раз. Запустив это в случайном исходном файле в моем каталоге "Переполнения стека":
$ runhaskell ReadBStr.hs Corec.hs temp
Bytes read: 128
Bytes read: 128
Bytes read: 128
Bytes read: 128
Bytes read: 128
Bytes read: 128
Bytes read: 128
Bytes read: 128
Bytes read: 128
Bytes read: 128
Bytes read: 83
$