8

Haskell関数を使いたい

readFile :: FilePath -> IO String

ファイルの内容を文字列に読み込みます。ドキュメントで、「ファイルは、getContents と同様に、オンデマンドで遅延して読み取られる」と読んだことがあります。

これを完全に理解しているかどうかはわかりません。たとえば、私が書いたとします。

s <- readFile "t.txt"

このアクションが実行されると:

  • ファイルが開かれます。
  • s 内の文字は、実際には式を評価するために必要になるとすぐに (しかしすぐには) ファイルから読み込まれます (たとえば、ファイルのlength s すべての内容を評価するとファイルが閉じられます)。
  • 最後の文字が読み取られるとすぐに、この呼び出しに関連付けられたファイル ハンドルreadFileが (自動的に) 閉じられます。

3 番目のステートメントは正しいですか? readFileでは、自分でファイル ハンドルを閉じずに呼び出すことはできますか? 結果文字列全体を消費 (訪問) しない限り、ハンドルは開いたままになりますか?

編集

ここに私の疑問に関するいくつかの追加情報があります。次のものがあるとします。

foo :: String -> IO String
foo filename = do
                  s <- readFile "t.txt"
                  putStrLn "File has been read."
                  return s

が実行されるときputStrLn、私は(直感的に)それを期待します

  1. sfile の全コンテンツを含みますt.txt
  2. ファイルの読み取りに使用されたハンドルが閉じられました。

そうでない場合:

  • 実行時に何がs含まputStrLnれますか?
  • 実行時のファイルハンドルの状態はputStrLn
  • putStrLn実行時sにファイルの内容全体が含まれていない場合、この内容が実際に読み取られるのはいつで、ファイルはいつ閉じられますか?
4

2 に答える 2

9

3 番目のステートメントは正しいですか?

「最後の文字が読み取られるとすぐに」、ファイルは閉じられません。少なくとも通常はそうではありませんが、読み取り中にあった半閉じた状態のまま残ります。IO-manager/runtime次にそのようなアクションを実行するときに閉じます。ファイルをすばやく開いて読み取る場合、OS の制限が高すぎなければ、そのラグによってファイル ハンドルが不足する可能性があります。

ただし、ほとんどの使用例 (私の限られた経験では) では、ファイル ハンドルを閉じるだけで十分です。[レイジー IO はすべての場合において非常に危険であると考え、同意しない人がいます。確かに落とし穴がありますが、IMO の危険性はしばしば誇張されています。]

readFileでは、自分でファイル ハンドルを閉じずに呼び出すことはできますか?

はい、 を使用しreadFileている場合、ファイルの内容が完全に読み取られた場合、またはファイル ハンドルが参照されなくなったことに気付いた場合、ファイル ハンドルは自動的に閉じられます。

結果文字列全体を消費 (訪問) しない限り、ハンドルは開いたままになりますか?

完全ではありませんreadFile。ファイル ハンドルを半閉じた状態にしhGetContentsます。

計算により、hGetContents hdlによって管理されるチャネルまたはファイルの未読部分に対応する文字のリストが返されますhdl。これは中間状態であるセミクローズに置かれます。この状態では、hdlは事実上閉じられていますが、アイテムはhdlオンデマンドで読み取られ、 によって返される特別なリストに蓄積されます。hGetContents hdl.


foo :: String -> IO String
foo filename = do
              s <- readFile "t.txt"
              putStrLn "File has been read."
              return s

ああ、それは反対側の遅延 IO の落とし穴の 1 つです。ここでは、内容が読み取られる前にファイルが閉じられます。戻るとfoo、ファイル ハンドルは参照されなくなり、閉じられます。foos resultのコンシューマーは、それsが空の文字列であることを検出します。これhGetContentsは、ファイルから実際に読み取ろうとすると、ハンドルが既に閉じられているためです。

の振る舞いと の振る舞いを混同しreadFile

bracket (openFile file ReadMode) hClose hGetContents

そこの。が参照されなくreadFileなった後にのみファイルハンドルを閉じるため、ここでは期待どおりに正しく動作します。s

が実行されるときputStrLn、私は(直感的に)それを期待します

  1. sfile の全コンテンツを含みますt.txt
  2. ファイルの読み取りに使用されたハンドルが閉じられました。

いいえ、sファイルハンドルからいくつかの文字を取得するためのレシピ以外にはまだ何も含まれていません。ファイル ハンドルは半分閉じていますが、閉じていません。ファイルの内容が完全に読み取られるかs、範囲外になると閉じられます。

そうでない場合:

  • 実行時に何がs含まputStrLnれますか?
  • 実行時のファイルハンドルの状態はputStrLn
  • putStrLn実行時sにファイルの内容全体が含まれていない場合、この内容が実際に読み取られるのはいつで、ファイルはいつ閉じられますか?

最初の 2 つの質問には回答済みで、3 番目の質問に対する回答は「コンテンツが消費されたときにファイルが読み込まれる」であり、コンテンツ全体が読み込まれるか、参照されなくなったときに閉じられます。

上記のbracket呼び出しとは異なります。他のアクションが例外をスローした場合でもbracket、最終操作 (ここでは )hCloseが実行されることが保証されるため、多くの場合、その使用が推奨されます。ただし、が戻っhCloseたときに が実行され、 が実際に閉じられたファイル ハンドルからコンテンツを取得できなくなります。ただし、例外が発生した場合、必ずしもファイル ハンドルを閉じるとは限りません。brackethGetContentsreadFile

これは、遅延 IO の危険性または癖の 1 つです。ファイルは、その内容が要求されるまで読み取られません。遅延 IO を間違って使用すると、手遅れになり、内容を取得できなくなります。

これは、多くの (またはほとんどの) 人が何度か陥る罠ですが、それに噛まれた後は、IO を非遅延にする必要がある場合をすぐに学習し、そのような場合は非遅延で実行します。

代替手段 (反復子、列挙子、コンジット、パイプなど) は、[実装者が間違いを犯さない限り] これらのトラップを回避しますが、遅延 IO が完全に問題ない場合に使用するのはあまり適切ではありません。その一方で、彼らは怠惰が望まれないケースをより良く扱います。

于 2012-12-13T21:27:57.393 に答える
5

sputStrLn が実行されると、ファイル t.txt の内容全体が含まれていると (直感的に) 期待できます。

ここで遅延 IO を使用しているという事実について考える必要があります。ファイルからの読み取りは、未評価の文字列計算を作成するだけで、後で必要になった場合にファイルを読み取ります。

遅延 IO を使用すると、値が必要になるまで IO を延期できます。

ファイルの最後の文字が読み取られるか、開いているファイルへのすべての参照 (s値など) が削除されると、開いているファイルはガベージ コレクターによって閉じられます。

于 2012-12-13T21:54:40.440 に答える