プログラムの有効期間中に一度だけ呼び出される次の関数を書き直すにはどうすればいいですか?メモIO機能?
getHeader :: FilePath -> IO String
getHeader fn = readFile fn >>= return . take 13
上記の関数は、さまざまな関数から数回呼び出されます。 関数が同じパラメータで呼び出された場合、ファイルの再オープンを防ぐ方法。ファイル名 ?
プログラムの有効期間中に一度だけ呼び出される次の関数を書き直すにはどうすればいいですか?メモIO機能?
getHeader :: FilePath -> IO String
getHeader fn = readFile fn >>= return . take 13
上記の関数は、さまざまな関数から数回呼び出されます。 関数が同じパラメータで呼び出された場合、ファイルの再オープンを防ぐ方法。ファイル名 ?
私はより多くの機能液を模索することをお勧めしますたとえば、必要なヘッダーを前面に読み込んで、Map
のようなデータ構造で渡します。明示的に渡すのが不便な場合は、Reader
またはState
モナドトランスを使用して処理することができます。 を使用して、データ構造を保持するためのグローバルな可変参照を作成することで、これを実現することができます。
import Control.Concurrent.MVar
import qualified Data.Map as Map
import System.IO.Unsafe (unsafePerformIO)
memo :: MVar (Map.Map FilePath String)
memo = unsafePerformIO (newMVar Map.empty)
{-# NOINLINE memo #-}
getHeader :: FilePath -> IO String
getHeader fn = modifyMVar memo $ \m -> do
case Map.lookup fn m of
Just header -> return (m, header)
Nothing -> do header <- take 13 `fmap` readFile fn
return (Map.insert fn header m, header)
ここでは、スレッドの安全性のためにMVar
を使用しました。あなたがそれを必要としない場合は、IORef
を代わりに利用することができます。
また、memo
にNOINLINE
プラグマがあることに注意して、参照が1回だけ作成されるようにしてください。これがなければ、コンパイラはそれをgetHeader
にインライン展開し、毎回新しい参照を与えます。
最も簡単な方法は、ちょうどmain
の冒頭で一度、それを呼び出して、それを必要とする他のすべての機能に周りの結果String
を渡すことです:
main = do
header <- getHeader
bigOldThingOne header
bigOldThingTwo header
感謝を。私はこの方法を知っていましたが、このパラメータを使用しなくてもヘッダーはすべての連鎖関数に渡されなければならないため、少し不快です。 –
@DavidUnric:あなたは読者のモナドを調べるべきです。彼らはその問題を正確に解決する。 – hammar
これを解決するにはunsafePerformIOを使用しないでください。あなたが記述したものを正確に行う正しい方法は、Maybeを保持するIORefを作成し、最初はNothingを含むことです。次に、値をチェックするIO関数を作成し、Nothingの場合は計算を実行し、その結果をJustとして保存します。もしそれがちょうど見つかったら、それは価値を再利用する。
これはすべてIORef参照を渡す必要があります。これは文字列自体を渡すのと同じくらい厄介です。そのため、Readerモナドを明示的または暗黙的に使用して文字列自体を渡すことを全員が直接お勧めします。
unsafePerformIOの正当な使用は非常に少なく、これはその1つではありません。その道を下ってはいけません。そうしないと、予期せぬ事をやっているときに、ハスケルと戦っていることが分かります。 unsafePerformIOを「巧妙なトリック」として使用するすべてのソリューションは、常に破滅的に終わります(readFileを含む)。
サイドノート - あなたのgetHeader機能を簡素化することができます。
getHeader path = fmap (take 13) (readFile path)
それとも
getHeader path = take 13 <$> readFile path
あなたはMemoT
変圧器に任意のモナドをラップするmonad-memoパッケージを使用することができます。メモのテーブルは、あなたのモナドの機能を暗黙のうちに渡します。その後、通常のIO
にメモ化モナドを変換するstartEvalMemoT
を使用します。
{-# LANGUAGE NoMonomorphismRestriction #-}
import Control.Monad.Memo
getHeader :: FilePath -> IO String
getHeader fn = readFile fn >>= return . take 13
-- | 'memoized' version of getHeader
getHeaderm :: FilePath -> MemoT String String IO String
getHeaderm fn = memo (lift . getHeader) fn
-- | 'memoized' version of Prelude.print
printm a = memo (lift . print) a
-- | This will not print the last "Hello"
test = do
printm "Hello"
printm "World"
printm "Hello"
main :: IO()
main = startEvalMemoT test
ありがとうございます。私がunsafePerformIOを避けたいのであれば、 'メモ'はIOアクションを返します。これはまだ動作しますか?私はそれが今評価される必要があるたびに呼び出されるだろうと思う。 –
@DavidUnric:いいえ、毎回新しい空の 'Map'が得られるので、メモ処理はうまくいきません。毎回ファイルからテキストをロードします。ある場所に 'MVar'を作成して渡すことができますが、' Map'を直接渡すこともできます。 – hammar
ハマー>説明のためのThx。コードをリファクタリングして、ヘッダーが使用される直前の最初のIO関数から渡されるようにしました。私は気づいていない別のアプローチを示しているので、私はあなたのasnwerをマークするつもりですが。 –