在Haskell中记忆IO计算

时间:2013-04-09 19:44:01

标签: haskell concurrency memoization

我有一个功能

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}}如果可用的话。这个putf a模式是这样的,即两个不同的线程在看到它尚未运行之后都不会运行计算{{1}}。

1 个答案:

答案 0 :(得分:1)

我认为你想要的东西是不可能的,但结果却是。

https://stackoverflow.com/a/9458721/1798971

我仍然无法弄清楚为什么会有效!