2012-02-26 76 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