Haskell Lazy ByteString+función de progreso de lectura/escritura
io progress (2)
En primer lugar, me gustaría señalar que un buen número de programadores de Haskell consideran al IO perezoso en general con cierta sospecha. Técnicamente viola la pureza, pero de forma limitada (hasta donde yo sé) no se nota cuando se ejecuta un solo programa con una entrada consistente [0] . Por otro lado, muchas personas están de acuerdo con esto, nuevamente porque involucra solo un tipo muy restringido de impureza.
Para crear la ilusión de una estructura de datos perezosa que en realidad se creó con E / S a petición, las funciones como readFile
se implementan utilizando chanchullos readFile
detrás de las escenas. Tejer en la E / S a petición es inherente a la función, y no es realmente extensible por más o menos las mismas razones por las que la ilusión de obtener una ByteString
regular es convincente.
Moviendo manualmente los detalles y escribiendo pseudocódigo, algo así como readFile básicamente funciona así:
lazyInput inp = lazyIO (lazyInput'' inp)
lazyInput'' inp = do x <- readFrom inp
if (endOfInput inp)
then return []
else do xs <- lazyInput inp
return (x:xs)
... donde cada vez que se llama a lazyIO
, difiere la E / S hasta que el valor se usa realmente. Para invocar su función de informe cada vez que se produce la lectura real, debe tejerla directamente, y aunque se podría escribir una versión generalizada de dicha función, a mi conocimiento no existe ninguna.
Dado lo anterior, tiene algunas opciones:
Busque la implementación de las funciones de E / S perezosas que está utilizando e implemente la suya que incluya la función de informe de progreso. Si esto se siente como un truco sucio, es porque prácticamente lo es, pero ahí lo tienes.
Abandone la E / S perezosa y cambie a algo más explícito y composable. Esta es la dirección a la que parece dirigirse la comunidad Haskell en su conjunto, específicamente hacia alguna variación en Iteratos , que le brindan bloques de construcción de procesadores de corriente agradablemente compostables que tienen un comportamiento más predecible. La desventaja es que el concepto aún se encuentra en desarrollo activo, por lo que no existe un consenso sobre la implementación o un único punto de partida para aprender a usarlos.
Abandone la E / S perezosa y cambie a E / S normal antiguo: escriba una acción
IO
que lea un fragmento, imprima la información de informe y procese toda la información que pueda; luego invocarlo en un bucle hasta que finalice. Dependiendo de lo que esté haciendo con la entrada y de cuánto depende de la pereza en su procesamiento, esto podría incluir desde escribir un par de funciones casi triviales hasta construir un conjunto de procesadores de flujo de máquina de estados finitos y obtener 90 % de la manera de reinventar Iteratees.
[0] : la función subyacente aquí se llama unsafeInterleaveIO
, y según mi leal saber y entender, las únicas formas de observar las impurezas desde allí requiere ejecutar el programa en diferentes entradas (en cuyo caso tiene derecho a comportarse de manera diferente, de todos modos, puede ser hacerlo de formas que no tienen sentido en código puro) o cambiar el código de ciertas maneras (es decir, las refactorizaciones que no deberían tener ningún efecto pueden tener efectos no locales).
Aquí hay un ejemplo aproximado de cómo hacer las cosas a la manera de "E / S normal antiguo normal", usando funciones más compostables:
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)
El "128" allí arriba es cuántos bytes leer a la vez. Ejecutar esto en un archivo fuente aleatorio en mi directorio "fragmentos de desbordamiento de pila":
$ 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
$
Estoy aprendiendo Haskell Lazy IO.
Estoy buscando una manera elegante de copiar un archivo grande (8Gb) mientras imprimo el progreso de la copia a la consola.
Considere el siguiente programa simple que copia un archivo silenciosamente.
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
Imagine que hay una función de devolución de llamada que desea usar para informar:
onReadBytes :: Integer -> IO ()
onReadBytes count = putStrLn $ "Bytes read: " ++ (show count)
PREGUNTA: ¿cómo tejer la función ReadBad en The Lazy ByteString para que se vuelva a llamar en la lectura exitosa? O si este diseño no es bueno, ¿cuál es la forma de hacerlo de Haskell?
NOTA: la frecuencia de devolución de llamada no es importante, se puede llamar cada 1024 bytes o cada 1 Mb, no es importante
RESPUESTA: Muchas gracias a camccann por la respuesta. Sugiero leerlo por completo.
Bellow es mi versión del código basado en el código de camccann, puede que le resulte útil.
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
Use Data.ByteString.Lazy.Progress . Le permite imprimir todo tipo de métricas a medida que los datos pasan.