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

Haskell Lazy ByteString + функция чтения/записи

Я лечу Haskell Lazy IO.

Я ищу элегантный способ скопировать большой файл (8Gb) при печати процесса копирования на консоль.

Рассмотрим следующую простую программу, которая тихо копирует файл.

module Main where

import System
import qualified Data.ByteString.Lazy as B

main = do [from, to] <- getArgs
          body <- B.readFile from
          B.writeFile to body

Imgine есть функция обратного вызова, которую вы хотите использовать для отчетности:

onReadBytes :: Integer -> IO ()
onReadBytes count = putStrLn $ "Bytes read: " ++ (show count)

ВОПРОС: , как переплетать функцию onReadBytes в Lazy ByteString, чтобы она была возвращена на успешное чтение? Или, если этот проект не очень хорош, то каков способ Haskell сделать это?

ПРИМЕЧАНИЕ: частота обратного вызова не важна, ее можно вызывать каждые 1024 байта или каждые 1 Мб - не важно

ОТВЕТ: Огромное спасибо камканне за ответ. Я предлагаю прочитать его полностью.

Bellow - это моя версия кода, основанная на кодеке на карте, вам может показаться полезным.

module Main where

import System
import System.IO
import qualified Data.ByteString.Lazy as B

main = do [from, to] <- getArgs
          withFile from ReadMode $ \fromH ->
            withFile to WriteMode $ \toH ->
              copyH fromH toH $ \x -> putStrLn $ "Bytes copied: " ++ show x

copyH :: Handle -> Handle -> (Integer -> IO()) -> IO ()
copyH fromH toH onProgress =
    copy (B.hGet fromH (256 * 1024)) (write toH) B.null onProgress
    where write o x  = do B.hPut o x
                          return . fromIntegral $ B.length x

copy :: (Monad m) => m a -> (a -> m Integer) -> (a -> Bool) -> (Integer -> m()) -> m()
copy = copy_ 0

copy_ :: (Monad m) => Integer -> m a -> (a -> m Integer) -> (a -> Bool) -> (Integer -> m()) -> m()
copy_ count inp outp done onProgress = do x <- inp
                                          unless (done x) $
                                            do n <- outp x
                                               onProgress (n + count)
                                               copy_ (n + count) inp outp done onProgress
4b9b3361

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

Ответ 2

Используйте Data.ByteString.Lazy.Progress. Он позволяет печатать все виды показателей по мере прохождения данных.