import System.IO main = do file <- openFile "1.txt" ReadMode content <- hGetContents file print content hClose file -- :
すべてが非常にありふれたものです-ファイルを開き、内容を読み、表示し、ファイルを閉じます。 hGetContentsの機能(またはHaskell機能)は、それが遅延していることです。 つまり、ファイルのデータはすぐに全体が読み込まれるのではなく、必要に応じて読み込まれます。 たとえば、そのようなプログラム
import System.IO main = do file <- openFile "1.txt" ReadMode content <- hGetContents file print $ take 3 content hClose file -- : 3
ファイルの最初の3文字のみを考慮します。
何と言ってもいい、素晴らしい機能です! 可変コンテンツ (ファイルコンテンツ)を操作することは非常に便利ですが、同時にHaskellは必要なものだけを取得し、それ以上のものは取得しないことを知っています。
次の例を考えてみましょう
import System.IO main = do file <- openFile "1.txt" ReadMode content <- hGetContents file hClose file print content -- :
ここで、最後の2行を再配置しました。 結果は、画面に表示される空の文字列になります。 なぜこれが起こったのですか? その理由は、ファイルが既に閉じられた後にファイルのコンテンツにアクセスしようとしているからです。 hGetContentsが呼び出されたとき、データはどこにも読み込まれず、このファイルへのリンクが変数contentに保存されました。 そして、コンテンツcontentが必要になったときに、ファイルが既に閉じられていることがわかりました。
ファイルを閉じる前にコンテンツ変数を使用するだけでいいのです。
ここでの問題は、ファイルを閉じる前にHaskellにコンテンツ変数の計算を「取得」できないことです(誰かがメソッドを知っていれば、書きます)。 たとえば、ファイルを開き、内容を読み取り、構造を解析し、ファイルを閉じて、結果の構造を結果として表示する関数を記述する必要があります。
{-# OPTIONS_GHC -XScopedTypeVariables #-} import System.IO parseFile :: (Read a) => String -> IO a parseFile fileName = do file <- openFile fileName ReadMode content <- hGetContents file rezult <- return $ read content hClose file return rezult main = do a :: Int <- parseFile "1.txt" print a --:
これは基本的な例のようですが、機能しません。 同じ理由で。 ファイルの内容を読み取り関数に渡したという事実は、後で延期されたため、何も変わりませんでした。 そして、私たちがcontentで何をするにしても、これはHaskellがファイルからコンテンツを読むことを強制しません(もちろん、表示しない限り)。
もちろん、プログラム全体をopenFileとhCloseの間に置くこともできますが、ファイルから内容を読み取り、それを修正して同じファイルに書き込む必要がある場合はどうでしょうか。
私は長い間この例を考えていましたが、「ここで何かがおかしい」という気持ちは私を一人にさせませんでした。 それはHaskellの純度に違反していませんか。 基本的に正しいプログラムを作成しましたが、正しい結果が得られません。 彼はいつこの変数に目を向けるのか、そして彼はボンネットの下で何をするのか、私は何を考えるべきですか? これはHaskellではありません。式には申し訳ありませんが、ある種のC ++です。
その結果、この「機能」は機能ではなくバグであるという結論に達しました。 私があなたを納得させていないなら、ここにいくつかの理由があります:
- 1.ラムダ計算では、ラムダ項を削減するためのさまざまな戦略があります。 完全ベータ削減 、 通常の計算順序、 名前による呼び出し ( 必要に応じて haskellは最適化バージョンの呼び出しを使用)、 値による呼び出し (精力的な計算)。 ラムダ項は、計算戦略に関係なく、存在する場合、同じ正規形に縮小されると述べる定理があります。 つまり 計算の順序(遅延またはエネルギッシュ)については、同じ結果が得られます。 確かに、ラムダ項の例があり、その計算は精力的な戦略ではサイクルになりますが、怠zyなものではサイクルになりません(たとえば、無限リストを操作する)。 しかし、両方の計算が完了すると、同じ結果が得られるはずです!
(ちなみに、怠theなバージョンは固定されているが、エネルギッシュなバージョンは存在しない逆の例は存在しません。この意味で、怠zyな戦略が最も「正確」です)
最後の例を検討し、Haskellがエネルギー的に計算すると想像すると、プログラムが正しく動作し、ファイルの内容を返すことがわかります。 つまり エネルギッシュモードでは1つの結果、レイジーモードでは別の結果になります。 プログラムはラムダ項ではなく、純粋な関数ではないと主張するかもしれません。 ある意味では、これは本当です。 しかし、モナドが何のために発明されたかを思い出しましょう。 外部世界の状態を入力として取り、新しい状態を出力する純粋な関数としてプログラムを提示するためではありませんか?
- 2.それでも、なぜそうなのですか? Haskellでは、すべての機能がクリーンです。 計算の結果は、渡された引数のみに依存します。 これは、「後で」計算を延期できることを意味し、この結果は変わりません。 hGetContentsを呼び出す場合、関数の結果は引数だけでなく、現在のシステムの状態にも依存します。 システムの状態に依存する「後の」計算のために延期する権利はありますか? 理想的には、プログラムは次のように動作するはずです:ファイルを開き、ファイルを閉じ、 printを呼び出し、過去(ファイルがまだ開いていた)に戻り、その内容を読み、未来に戻り、表示します。
なぜ私はこれすべてですか? 誤解しないでください。 私はHaskellが大好きです。 その純度、怠iness、機能性など、言葉では表現できないものがたくさんあります。 突然、以前考えていたほど「きれい」ではないことがわかりました。 この機能は良いように思える複雑な感覚がありましたが、どういうわけかそれは「汚い」ものになりました。
たぶん私はただの妄想です。 あなたの意見、バグまたは機能を聞きたいですか?