13

次のコードスニペットがあり、これを渡しwithFileます。

text <- hGetContents hand 
let code = parseCode text
return code

ここで、handは有効なファイルハンドルであり、で開かれReadModeparseCode入力を読み取って多分を返す私自身の関数です。そのままでは、関数は失敗し、Nothingを返します。代わりに、私が書く場合:

text <- hGetContents hand 
putStrLn text
let code = parseCode text
return code

必要に応じて、Justを取得します。

openFileと私がそうするならhClose、私は同じ問題を抱えています。なぜこうなった?どうすればきれいに解決できますか?

ありがとう

4

3 に答える 3

13

hGetContents怠惰ではありません。目的の効果を得るには、他のものと適切に構成する必要があります。名前が変更された場合、exposeContentsToEvaluationAsNeededForTheRestOfTheActionまたは単にlisten

withFileファイルを開き、何かを実行し(または、必要に応じて何も実行しません。どのような場合でも、まさに必要なことを実行します)、ファイルを閉じます。

「怠惰なIO」の謎をすべて引き出すだけでは十分ではありませんが、ブラケットのこの違いを考慮してください。

 good file operation = withFile file ReadMode (hGetContents >=> operation >=> print)
 bad file operation = (withFile file ReadMode hGetContents) >>= operation >>= print

-- *Main> good "lazyio.hs" (return . length)
-- 503
-- *Main> bad "lazyio.hs" (return . length)
-- 0

大雑把に言えばbad、ファイルが何かをする前に、ファイルを開いたり閉じたりします。goodファイルを開いてから閉じるまでのすべてを実行します。あなたの最初の行動はに似ていましたbadwithFileハンドルに依存する、実行したいすべてのアクションを管理する必要があります。

小さなファイルなどで作業している場合は、厳密さの強制者は必要ありませんString。構成がどのように機能するかを理解するだけです。繰り返しbadますが、ファイルを閉じる前に「行う」のはすべてですexposeContentsToEvaluationAsNeededForTheRestOfTheAction。で、私が念頭に置いている残りのアクションを使用してgood作成exposeContentsToEvaluationAsNeededForTheRestOfTheActionし、ファイルを閉じます。

パトリックが言及したおなじみのlength+seqトリック、またはlength+evaluateは知っておく価値があります。での2番目のアクションputStrLn txtはバリアントでした。しかし、怠惰なIOがあなたのケースにとって間違っていない限り、再編成の方が優れています。

$ time ./bad
bad: Prelude.last: empty list  
                        -- no, lots of Chars there
real    0m0.087s

$ time ./good
'\n'                -- right
()
real    0m15.977s

$ time ./seqing 
Killed               -- hopeless, attempting to represent the file contents
    real    1m54.065s    -- in memory as a linked list, before finding out the last char

言うまでもなく、ByteStringとTextは知っておく価値がありますが、評価を念頭に置いた再編成の方が優れています。なぜなら、それらを使用してもLazyバリアントが必要になることが多く、構成の形式間の同じ区別を把握する必要があるからです。この種のIOが不適切な(巨大な)クラスのケースの1つを扱っている場合は、すべて素晴らしいを見てenumeratorくださいconduit

于 2012-05-07T18:57:37.583 に答える
9

hGetContentsレイジーIOを使用します。文字列をさらに強制するとファイルから読み取るだけであり、返される文字列全体を評価するときにのみファイルハンドルを閉じます。withFile問題は、それを;で囲んでいることです。代わりに、直接(またはもっと簡単に)を使用openFileしてhGetContentsくださいreadFile。文字列を完全に評価すると、ファイルは引き続き閉じられます。このようなものでうまくいくはずです。事前に文字列全体を強制することで、ファイルが完全に読み取られ、すぐに閉じられるようにします。

import Control.Exception (evaluate)

readCode :: FilePath -> IO Code
readCode fileName = do
    text <- readFile fileName
    evaluate (length text)
    return (parseCode text)

このような直感的でない状況は、最近人々が怠惰なIOを避ける傾向がある理由の1つですが、残念ながら、の定義を変更することはできませんhGetContents。のstrictIOバージョンはstricthGetContentsパッケージで利用できますが、その1つの機能だけをパッケージに依存する価値はないでしょう。

ここで文字列を2回トラバースすることによるオーバーヘッドを回避したい場合はString、とにかく、よりも効率的なタイプの使用を検討する必要があります。このText型には、ベースのIO機能の多くと同等の厳密なIOがあります(Unicodeテキストではなくバイナリデータを処理している場合)。StringByteString

于 2012-05-07T16:56:43.583 に答える
0

を使用して、の内容を強制的textに評価できます。

length text `seq` return code

最後の行として。

于 2012-05-07T16:51:29.883 に答える