最新のソースから GHC をインストールしたところ、「クローズされたハンドルの遅延読み取り」に関するエラー メッセージが表示されます。これは何を意味するのでしょうか?
1 に答える
基本的な遅延 I/O プリミティブhGetContents
はString
遅延を生成します。プログラムが実際に要求する文字列の部分を生成するために、必要に応じてハンドルから読み取るだけです。ただし、ハンドルが閉じられると、ハンドルから読み取ることができなくなり、まだ読み取られていない文字列の一部を検査しようとすると、この例外が発生します。たとえば、次のように書くとします。
main = do
most <- withFile "myfile" ReadMode
(\h -> do
s <- hGetContents h
let (first12,rest) = splitAt 12 s
print first12
return rest)
putStrLn most
GHC が開いmyfile
て、バインドした文字列への遅延読み取りをセットアップしs
ます。実際にはファイルからの読み取りを開始しません。次に、遅延計算を設定して、文字列を 12 文字で分割します。次にprint
、その計算を強制し、GHCmyfile
は少なくとも 12 文字の長さのチャンクを読み込み、最初の 12 文字を出力します。次に、完了するとファイルを閉じwithFile
、残りを印刷しようとします。ファイルが GHC バッファリングされたチャンクよりも長い場合、チャンクの最後に到達すると、遅延読み取り例外が発生します。
この問題を回避する方法
ファイルを閉じる前、または から戻る前に、必要なものをすべて実際に読んだことを確認する必要がありますwithFile
。渡す関数withFile
が何らかの IO を実行し、定数 ( など()
) を返す場合、これについて心配する必要はありません。遅延読み取りから実際の値を生成する必要がある場合は、戻る前にその値を十分に強制する必要があります。Control.DeepSeq
上記の例では、モジュールの関数または演算子を使用して、文字列を強制的に「通常の形式」にすることができます。
return $!! rest
withFile
これにより、ファイルを閉じる前に残りの文字列が実際に読み取られるようになります。このアプローチは、クラス$!!
のインスタンスである限り、ファイルの内容から計算された値を返す場合にも完全に機能します。NFData
この場合、および他の多くの場合、次のように、ファイルの内容を処理するための残りのコードを に渡される関数に単純に移動することをお勧めしますwithFile
。
main = withFile "myfile" ReadMode
(\h -> do
s <- hGetContents h
let (first12,rest) = splitAt 12 s
print first12
putStrLn rest)
代替として考慮すべきもう 1 つの関数は、 ですreadFile
。readFile
ファイルの読み取りが完了するまで、ファイルを開いたままにします。ただし、ファイルの内容全体を実際に要求することがわかっている場合にのみreadFile
、 を使用してください。そうしないと、ファイル記述子が漏洩する可能性があります。
歴史
Haskell レポートによると、ハンドルが閉じられると、文字列の内容は固定されます。
これまで GHC は、ハンドルが閉じられたときにバッファリングされたものの最後で文字列を単純に終了していました。たとえば、ハンドルを閉じる前に文字列の最初の 10 文字を検査し、GHC が追加の 634 文字をバッファリングしたが、ファイルの最後に達していない場合、644 文字の通常の文字列が得られます。これは、新規ユーザーの間でよくある混乱の原因であり、製品コードで時折発生するバグの原因でした。
GHC 7.10.1 の時点で、この動作は変更されています。レイジーから読み取っているハンドルを閉じると、通常の:""
. そのため、ファイルが閉じられたポイントを超えて文字列を検査しようとすると、エラー メッセージが表示されます。