只是好奇如何重寫下面的函數在程序的生命週期中只能調用一次?記憶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
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
,每次給你一個新的參考。
最簡單的做法是在main
開始只是調用一次,通過周圍產生的String
到所有需要它的其他功能:
main = do
header <- getHeader
bigOldThingOne header
bigOldThingTwo header
感謝。我知道這種方式,但覺得有點不舒服,因爲即使沒有使用這個參數給它們,標題也必須傳遞給所有鏈接的函數。 – 2012-02-26 16:01:04
@DavidUnric:你應該看看讀者單子。他們解決了這個問題。 – hammar 2012-02-26 16:03:23
您不應該使用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
您可以使用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
謝謝。如果我想避免unsafePerformIO,所以'備忘錄'將返回一個IO動作,這仍然會工作嗎?我想現在每次評估時都會調用它。 – 2012-02-26 16:49:50
@DavidUnric:不,因爲每次都會得到一個新的空「Map」,所以您每次都會從文件中加載文本。你可以在一個地方創建MVar,然後傳遞它,但是你可以直接通過'Map'。 – hammar 2012-02-26 17:02:38
hammar> Thx的解釋。我重構了代碼,所以頭文件在使用前從第一個IO函數傳遞過來。雖然我會標記你的asnwer,因爲它顯示了我不知道的另一種方法。 – 2012-02-26 17:24:54