记忆IO功能?

时间:2012-02-26 15:19:36

标签: haskell

只是好奇如何重写以下在程序生命周期内只调用一次的函数?

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

从各种功能中多次调用上述功能。 如果使用相同的参数调用函数,如何防止重新打开文件,即。文件名?

4 个答案:

答案 0 :(得分: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 pragma,以确保仅创建一次引用。如果没有这个,编译器可能会将其内联到getHeader,每次都为您提供一个新的引用。

答案 1 :(得分:4)

最简单的方法是在main的开头调用一次,然后将生成的String传递给需要它的所有其他函数:

main = do
    header <- getHeader
    bigOldThingOne header
    bigOldThingTwo header

答案 2 :(得分:4)

您可以使用monad-memo包将任何monad包装到MemoT转换器中。备忘录表将在您的monadic函数中隐式传递。然后使用startEvalMemoT将memoized monad转换为普通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

答案 3 :(得分: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