Во-первых, я хотел бы отметить, что довольно много программистов на Haskell относятся к ленивому IO в целом с некоторым подозрением.Технически это нарушает чистоту, но ограниченным образом, что (насколько я знаю) не заметно при запуске единой программы на согласованном вводе [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 в целом, в частности, к некоторому варианту Итерации , который дает вам хорошо компонуемые небольшие строительные блоки потокового процессора, которые имеют более предсказуемое поведение.Недостатком является то, что концепция все еще находится в активной разработке, поэтому нет единого мнения о реализации или единой отправной точке для обучения их использованию.
Откажитесь от ленивого ввода-вывода и переключитесь на обычный старый обычныйВвод / вывод: напишите действие IO
, которое читает блок, печатает отчетную информацию и обрабатывает столько данных, сколько может;затем вызвать его в цикле, пока не будет сделано.В зависимости от того, что вы делаете с вводом, и от того, насколько сильно вы полагаетесь на лень в своей обработке, это может включать в себя что угодно - от написания пары почти тривиальных функций до построения группы потоковых процессоров с конечным числом машин и получения 90% пути к повторному изобретению Итераторов.
[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's 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» - это сколькобайт для чтения за один раз.Выполнение этого в произвольном исходном файле в моей директории "Stack Overflow snippets":
$ 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
$