2013-02-06 41 views
4

Control.Proxy教程为pipes-3.1.0包代理,作者提供了这样的功能:使用管道/并发MVars

cache :: (Proxy p, Ord key) => key -> p key val key val IO r 
cache = runIdentityK (loop M.empty) where 
    loop _map key = case M.lookup key _map of 
     Nothing -> do 
      val <- request key 
      key2 <- respond val 
      loop (M.insert key val _map) key2 
     Just val -> do 
      lift $ putStrLn "Used cache!" 
      key2 <- respond val 
      loop _map key2 

因为我想有一个并发应用程序缓存请求,我有以下数据键入

newtype Cache k v = Cache (MVar (M.Map k v))

,现在我想要一个新的cache功能与签名

cache :: (Proxy p, Ord k) => Cache k v -> k -> p k v k v IO r 
cache (Cache c) k = readMVar c >>= \m -> runIdentityK $ loop m k 
    where loop m key = case M.lookup key m of 
      Nothing -> do 
       val <- request key 
       respond val >>= loop (M.insert key val m) 
      Just val -> respond val >>= loop m 

然而,这未能类型检查,因为readMVar是在IO单子,并runIdentityKProxy p => p k v k v IO r单子。当然,我可以将readMVar放入此代理单元,因为它是一个超过IO的变压器,但我找不到合适的组合器。

+1

与克里斯迪福克相比,彼得的版本也比死锁安全得多!想象一下'request key'也会尝试访问同一个Cache。 –

回答

5

该解决方案是一个简单的lift。我以前曾想过使用它,但显然没有足够努力。这里是一个粗略的,类型检查版本我想要的cache

cache = runIdentityK . loop 
    where loop (Cache c) key = lift (takeMVar c) >>= \m -> case M.lookup key m of 
      Nothing -> do 
       val <- request key 
       lift . putMVar c $ M.insert key val m 
       respond val >>= loop (Cache c) 
      Just val -> do 
       lift $ putMVar c m 
       respond val >>= loop (Cache c) 
+0

当有例外时,这看起来相当不安全。取放应该用Mvar取代。 –

+0

由于他需要在采集和释放之间进行管道操作,因此使用变量将不起作用,但是他可以使用管道安全支架来安全地采集和释放异常情况。 –

3

这只是为增加lift一样简单。但是,您的实施似乎并不符合您的预期。您在开始时只读取MVar一次,然后再也不使用它,只需在循环中传递更新后的地图即可。如果不同线程通过MVar看到更改,则还必须更新它。一个建议(编译,但我没有测试它是如何工作的):

cache :: (Proxy p, Ord k) => Cache k v -> k -> p k v k v IO r 
cache (Cache c) k = runIdentityK loop k 
    where 
     loop key = do 
     m <- lift (readMVar c) 
     case M.lookup key m of 
      Nothing -> do 
       val <- request key 
       lift $ modifyMVar_ c (return . M.insert key val) 
       respond val >>= loop 
      Just val -> respond val >>= loop 
+0

Chris的版本可能更好,因为它通过使用'takeMVar' /'putMVar'来避免可能的竞争条件(尽管对于缓存来说它可能不是问题)。 –

+0

关于Petr的注释:竞态条件是两个线程调用'readMVar',然后两个线程调用'modifyMVar'。如果使用相同的'key'完成,那么'request'是两次而不是一次。这两个插入应该是相同的'val',所以正确性不会受到伤害。 –