只是好奇如何重写下面的函数在程序的生命周期中只能调用一次?记忆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