我有一个功能
f :: MonadIO m => a -> m b
它接受一些输入并返回将产生输出的 IO 计算。我想“记住”
f
所以我只对每个输入执行一次这些计算。例如,如果f :: String -> IO String
f s = putStrLn ("hello " ++ s) >> return s
那么我想要一个函数
memoize
以至于do
mf <- memoize f
s <- mf "world"
t <- mf "world"
return (s,t)
版画
"hello world"
恰好一次并返回 ("world", "world")
.我正在编写的程序是多线程的,因此即使不同的线程都在调用 mf
,该属性也应该保持不变。 .以下是我到目前为止提出的(可怕的)解决方案。我的问题是是否以及如何改进。
memoize :: (MonadIO m, Ord a) => (a -> m b) -> m (a -> m b)
memoize f = do
cache <- liftIO $ newTVarIO Map.empty
return $ \a -> do
v <- liftIO $ atomically $ lookupInsert cache a
b <- maybe (f a) return =<< liftIO (atomically $ takeTMVar v)
liftIO $ atomically $ putTMVar v $ Just b
return b
where
lookupInsert :: Ord a => TVar (Map a (TMVar (Maybe b))) -> a -> STM (TMVar (Maybe b))
lookupInsert cache a = do
mv <- Map.lookup a <$> readTVar cache
case mv of
Just v -> return v
Nothing -> do
v <- newTMVar Nothing
modifyTVar cache (Map.insert a v)
return v
这里发生了一些事情:
1)
cache
有类型 TVar (Map a (TMVar (Maybe b)))
.它将输入映射到 TMVar
包含计算值或 Nothing
(这表示尚未计算值)。函数lookupInsert
检查 cache
,并插入一个新的 TMVar
初始化为 Nothing
如果已经不存在。2)返回的action首先获得
v :: TMVar (Maybe b)
关联到 a
,然后获取它,或者执行计算 f a
获取结果或返回存储在 Maybe
中的值如果可用。此 take
和 put
模式使得两个不同的线程不会同时运行计算 f a
在看到它尚未运行后。
最佳答案
我以为你想要的是不可能的,但事实证明它是。
https://stackoverflow.com/a/9458721/1798971
我仍然无法弄清楚为什么会这样!
关于haskell - 在 Haskell 中内存 IO 计算,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/15911084/