2012-02-26 43 views
5

プログラムの有効期間中に一度だけ呼び出される次の関数を書き直すにはどうすればいいですか?メモIO機能?

getHeader :: FilePath -> IO String 
getHeader fn = readFile fn >>= return . take 13 

上記の関数は、さまざまな関数から数回呼び出されます。 関数が同じパラメータで呼び出された場合、ファイルの再オープンを防ぐ方法。ファイル名 ?

答えて

7

私はより多くの機能液を模索することをお勧めしますたとえば、必要なヘッダーを前面に読み込んで、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を代わりに利用することができます。

また、memoNOINLINEプラグマがあることに注意して、参照が1回だけ作成されるようにしてください。これがなければ、コンパイラはそれをgetHeaderにインライン展開し、毎回新しい参照を与えます。

+0

ありがとうございます。私がunsafePerformIOを避けたいのであれば、 'メモ'はIOアクションを返します。これはまだ動作しますか?私はそれが今評価される必要があるたびに呼び出されるだろうと思う。 –

+1

@DavidUnric:いいえ、毎回新しい空の 'Map'が得られるので、メモ処理はうまくいきません。毎回ファイルからテキストをロードします。ある場所に 'MVar'を作成して渡すことができますが、' Map'を直接渡すこともできます。 – hammar

+1

ハマー>説明のためのThx。コードをリファクタリングして、ヘッダーが使用される直前の最初のIO関数から渡されるようにしました。私は気づいていない別のアプローチを示しているので、私はあなたのasnwerをマークするつもりですが。 –

4

最も簡単な方法は、ちょうどmainの冒頭で一度、それを呼び出して、それを必要とする他のすべての機能に周りの結果Stringを渡すことです:

main = do 
    header <- getHeader 
    bigOldThingOne header 
    bigOldThingTwo header 
+0

感謝を。私はこの方法を知っていましたが、このパラメータを使用しなくてもヘッダーはすべての連鎖関数に渡されなければならないため、少し不快です。 –

+1

@DavidUnric:あなたは読者のモナドを調べるべきです。彼らはその問題を正確に解決する。 – hammar

2

これを解決するには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 
4

あなたは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 
関連する問題