只是好奇如何重写以下在程序生命周期内只调用一次的函数?
getHeader :: FilePath -> IO String
getHeader fn = readFile fn >>= return . take 13
从各种功能中多次调用上述功能。 如果使用相同的参数调用函数,如何防止重新打开文件,即。文件名?
答案 0 :(得分:7)
我建议您寻求更实用的解决方案,例如,在前面加载您需要的标头并在某些数据结构中传递它们,例如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
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