2012-02-26 74 views
5

只是好奇如何重寫下面的函數在程序的生命週期中只能調用一次?記憶IO功能?

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

上面的函數被從多個函數調用幾次。 如何防止重新打開文件,如果函數被調用相同的參數,即。文件名 ?

回答

7

我會鼓勵你尋求一個更實用的解決方案例如,通過加載您需要的頭文件並將它們傳遞給一些數據結構,例如Map。如果明確地傳遞它不方便,您可以使用ReaderState monad變壓器爲您處理。

也就是說,您可以通過使用unsafePerformIO創建一個全局可變引用來保存您的數據結構,從而以您想要的方式完成此操作。

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代替。

此外,請注意NOINLINE編譯指示memo以確保該引用僅創建一次。如果沒有這個,編譯器可能會將它內聯到getHeader,每次給你一個新的參考。

+0

謝謝。如果我想避免unsafePerformIO,所以'備忘錄'將返回一個IO動作,這仍然會工作嗎?我想現在每次評估時都會調用它。 – 2012-02-26 16:49:50

+1

@DavidUnric:不,因爲每次都會得到一個新的空「Map」,所以您每次都會從文件中加載文本。你可以在一個地方創建MVar,然後傳遞它,但是你可以直接通過'Map'。 – hammar 2012-02-26 17:02:38

+1

hammar> Thx的解釋。我重構了代碼,所以頭文件在使用前從第一個IO函數傳遞過來。雖然我會標記你的asnwer,因爲它顯示了我不知道的另一種方法。 – 2012-02-26 17:24:54

4

最簡單的做法是在main開始只是調用一次,通過周圍產生的String到所有需要它的其他功能:

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

感謝。我知道這種方式,但覺得有點不舒服,因爲即使沒有使用這個參數給它們,標題也必須傳遞給所有鏈接的函數。 – 2012-02-26 16:01:04

+1

@DavidUnric:你應該看看讀者單子。他們解決了這個問題。 – hammar 2012-02-26 16:03:23

2

您不應該使用unsafePerformIO來解決這個問題。正確描述你所描述的正確方法是創建一個包含Maybe的IORef,最初包含Nothing。然後你創建一個IO函數來檢查這個值,如果它是Nothing則執行計算,並將結果存儲爲Just。如果它發現Just它會重新使用該值。

所有這些都需要傳遞IORef引用,這與傳遞字符串本身非常麻煩,這就是爲什麼每個人都直接推薦只是傳遞字符串本身,無論是顯式還是隱式地使用Reader monad。

對於unsafePerformIO,合法使用的數量非常少,這不是其中之一。不要走這條路,否則你會發現自己與Haskell作鬥爭時,它會繼續做出意想不到的事情。每個使用unsafePerformIO作爲「聰明技巧」的解決方案總是以災難性的方式結束(包括readFile)。

附註 - 您可以簡化getHeader功能:

getHeader path = fmap (take 13) (readFile path) 

或者

getHeader path = take 13 <$> readFile path 
4

您可以使用monad-memo包裝來包裝任何單子到MemoT變壓器。備忘錄表將隱式傳遞,儘管您的一元功能。然後使用startEvalMemoT到memoized單子轉換成普通IO

{-# 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