我有一个功能
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
初始化为v :: TMVar (Maybe b)
。
2)返回的操作首先获取与a
关联的f a
,然后获取它,并执行计算Maybe
以获取结果或返回存储在take
中的值{1}}如果可用的话。这个put
和f a
模式是这样的,即两个不同的线程在看到它尚未运行之后都不会运行计算{{1}}。
答案 0 :(得分:1)