13

いくつかのデータを含むファイルがあります。このデータは決して変更されず、IO モナドの外で利用できるようにしたいと考えています。どうやってやるの?

例 (これは単なる例であり、私のデータは計算できないことに注意してください):

素数.txt:

2 3 5 7 13

コード.hs:

primes :: [Int]
primes = map read . words . unsafePerformIO . readFile $ "primes.txt"

これは の「合法的な」使用unsafePerformIOですか? 代替手段はありますか?

4

5 に答える 5

21

TemplateHaskell を使用して、コンパイル時にファイルを読み込むことができます。ファイルのデータは、実際の文字列としてプログラムに格納されます。

1 つのモジュール (Text/Literal/TH.hsこの例では) で、次のように定義します。

module Text.Literal.TH where

import Language.Haskell.TH
import Language.Haskell.TH.Quote

literally :: String -> Q Exp
literally = return . LitE . StringL

lit :: QuasiQuoter
lit = QuasiQuoter { quoteExp = literally }

litFile :: QuasiQuoter
litFile = quoteFile lit

モジュールでは、次のことができます。

{-# LANGUAGE QuasiQuotes #-}
module MyModule where

import Text.Literal.TH (litFile)

primes :: [Int]
primes = map read . words $ [litFile|primes.txt|]

プログラムをコンパイルすると、GHC はprimes.txtファイルを開き、パーツがある場所にその内容を挿入します[litFile|primes.txt|]

于 2012-10-03T21:26:25.917 に答える
6

そのような使い方unsafePerformIOは良くありません。

宣言primes :: [Int]は、それprimesが数字のリストであると述べています。何にも依存しない特定の数字のリスト。

ただし、実際には、定義がたまたま評価されるときのファイル「primes.txt」の状態に依存します。誰かがこのファイルを変更primesして、持っているように見える値を変更する可能性がありますが、そのタイプによっては不可能なはずです。

メモリに完全に保存するのではなく、必要に応じて再計算する必要があると判断する仮想的な最適化が存在するprimes場合 (結局のところ、その型は、再計算するたびに同じものを取得することを示しています)、primes2 つの異なるものがあるように見えることさえあります。プログラムの 1 回の実行中の値。unsafePerformIOこれは、 を使用してコンパイラに嘘をつく場合に発生する可能性のある問題の一種です。

実際には、上記のすべてが問題になる可能性はほとんどありません。

しかし、理論的に正しいのはprimes、グローバル定数を作成しないことです (定数ではないため)。代わりに、パラメータ化が必要な計算を行い (つまりprimes、引数として取ります)、外側のプログラムでファイルを読み取り、プログラムがファイルから抽出しIOた純粋な値を渡すことで純粋な計算を呼び出します。IO両方の長所を活かすことができます。コンパイラに嘘をつく必要はありません。また、プログラム全体を .xml に入れる必要もありませんIO。Reader モナドなどの構造を使用しprimesて、どこでも手動で渡す必要がないようにすることができます (それが役立つ場合)。

unsafePerformIOそのため、継続したい場合に使用できます。理論的には間違っていますが、実際に問題が発生する可能性は低いです。

または、実際に何が起こっているかを反映するようにプログラムをリファクタリングすることもできます。

または、primes本当にグローバル定数であり、プログラム ソースに大量のデータを文字どおり含めたくない場合は、dflemstr で示されているように TemplateHaskell を使用できます。

于 2012-10-04T06:25:13.817 に答える
4

あなたのプログラムは、このファイルがいつロードされるかを正確に定義していません。ファイルが存在しない場合、例外がスローされますが、それがどこで発生するかは正確にはわかりません。(つまり、あなたのプログラムがすでに観測可能な実世界の処理を行った後である可能性があります。) 誰かがファイルの内容を変更しようと決めた場合にも、同様の注意が適用されます。それがいつ読まれるのか正確にはわからないので、どのコンテンツが得られるかはわかりません。(ファイルが変更されない場合、問題になる可能性は低いです。)

代替案について: 1 つの可能性は、グローバルな可変変数を作成し [それ自体はやや悪意があります]、メイン I/O スレッドからこの変数にファイルの内容を挿入することです。このようにして、ファイルは明確に定義された時点で読み込まれます。[あなたも遅延 I/O を使用していることに気付きました。そのため、ファイルがいつ開かれるかを定義しているだけです。]

実際、「正しい」ことは、データを必要とするすべての関数にデータを手動でスレッド化することです。そうしたくない理由は理解できます。それ苦痛です。ただし、これを手動で行うことを避けるために、おそらくある種の状態モナドを使用するでしょう...

于 2012-10-03T21:15:00.643 に答える
4

はい、問題ないはずです。安全のためにプラグマを追加でき{-# NOINLINE primes #-}ます — GHC が CAF をインライン化するかどうかはわかりません。

私が考えることができる唯一の代替手段は、コンパイル時に (Template Haskell を使用して) 同じことを行い、本質的に素数をバイナリに埋め込むことです。ただし、私はあなたのバージョンを好みます —primesリストは実際には遅延して読み取られ、作成されることに注意してください!

于 2012-10-03T20:28:19.807 に答える