21

HaskellLazyIOを貸し出しています。

コピーの進行状況をコンソールに印刷しながら、大きなファイル(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

レポートに使用するコールバック関数があると想像してください。

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

質問: onReadBytes関数をLazy ByteStringに織り込んで、正常に読み取られたときにコールバックされるようにするにはどうすればよいですか?または、この設計が適切でない場合、Haskellの方法は何ですか?

注:コールバックの頻度は重要ではありません。1024バイトごとまたは1Mbごとに呼び出すことができます-重要ではありません

答え:答えてくれた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
4

2 に答える 2

25

まず、かなりの数のHaskellプログラマーが怠惰なIOを一般的に疑わしいと見なしていることに注意したいと思います。これは技術的には純度に違反しますが、限られた方法で(私が知る限り)一貫した入力[0]で単一のプログラムを実行したときに目立たなくなります。一方で、非常に限られた種類の不純物しか含まれていないため、多くの人がそれで大丈夫です。

オンデマンドI/Oで実際に作成される怠惰なデータ構造の錯覚を作成するために、のような機能readFileは、舞台裏で卑劣なシェナニガンを使用して実装されます。ByteStringオンデマンドI/Oの織り込みは機能に固有のものであり、レギュラーを取得するという幻想が説得力があるのとほぼ同じ理由で、実際には拡張可能ではありません。

詳細を手で振って擬似コードを書くと、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」は、一度に読み取るバイト数です。私の「StackOverflowsnippets」ディレクトリにあるランダムなソースファイルでこれを実行します。

$ 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
$
于 2011-07-12T18:49:57.093 に答える
2

Data.ByteString.Lazy.Progressを使用します。データが通過するときに、あらゆる種類のメトリックを印刷できます。

于 2011-11-07T12:41:07.573 に答える