我正在学习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 Mb调用一次 - 不重要
答案:非常感谢camccann的回答。我建议完全阅读。
Bellow是基于camccann代码的代码版本,您可能会发现它很有用。
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
答案 0 :(得分:25)
首先,我想指出,相当多的Haskell程序员一般都会怀疑懒惰的IO。它在技术上违反了纯度,但是在一致的输入 [0] 上运行单个程序时,(在我所知的情况下)并不明显。另一方面,很多人都很喜欢它,因为它只涉及非常有限的杂质。
为了创建实际使用按需I / O创建的惰性数据结构的错觉,像readFile
这样的函数是在幕后使用偷偷摸摸的恶作剧来实现的。在按需I / O中编织是函数所固有的,并且它实际上并不是可扩展的,因为从它获得常规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
时,它会延迟I / O直到实际使用该值。要在每次实际读取时调用您的报告功能,您需要直接编织它,并且虽然可以编写此类函数的通用版本,但据我所知,不存在。
鉴于上述情况,您有几个选择:
查找您正在使用的惰性I / O函数的实现,并实现您自己的包含进度报告功能的函数。如果这感觉像是一个肮脏的黑客,那是因为它几乎是,但你去了。
放弃懒惰的I / O并切换到更明确和可组合的东西。这是Haskell社区整体似乎正朝着这个方向发展的方向,特别是对Iteratees的一些变化,它为您提供了具有更可预测行为的可组合的小流处理器构建块。缺点是这个概念仍处于积极发展阶段,因此没有就实施达成共识或学习使用它们的单一起点。
放弃懒惰的I / O并切换到普通的常规I / O:编写一个读取块的IO
动作,打印报告信息,并尽可能多地处理输入;然后在循环中调用它直到完成。根据您对输入所做的工作以及您在处理过程中依赖于懒惰的程度,这可能涉及从编写几个几乎无关紧要的函数到构建一堆有限状态机流处理器以及获得90重塑Iteratees的方法的百分之几。
[0] :这里的基础函数称为unsafeInterleaveIO
,据我所知,观察杂质的唯一方法是需要在不同的输入上运行程序(在这种情况下,无论如何它都有权以不同的方式表现,它可能是以纯代码中没有意义的方式进行,或者以某种方式改变代码(即,应该没有效果的重构可以具有非本地的效果)。
以下是使用更多可组合函数执行“普通旧常规I / O”方式的粗略示例:
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
$
答案 1 :(得分:2)
使用Data.ByteString.Lazy.Progress。它允许您在数据通过时打印各种指标。