使用`MonadBaseControl` API

时间:2015-08-31 15:17:25

标签: haskell monads monad-transformers

我目前正在和Bryan O'Sullivan的Mouseover to select item in listbox in WPF图书馆一起玩,并且有关于扩展withResource功能的问题。 我想将withResource函数的签名从(MonadBaseControl IO m) => Pool a -> (a -> m b) -> m b更改为(MonadBaseControl IO m) => Pool a -> (a -> m (Bool, b)) -> m b 我想要实现的是,操作应该返回(Bool, b)元组,其中布尔值指示借用的资源是否应该 被放回游泳池或被摧毁。

现在我的当前实现如下:

withResource :: forall m a b. (MonadBaseControl IO m) => Pool a -> (a -> m (Bool, b)) -> m b
{-# SPECIALIZE withResource :: Pool a -> (a -> IO (Bool,b)) -> IO b #-}
withResource pool act = fmap snd result
  where
    result :: m (Bool, b)
    result = control $ \runInIO -> mask $ \restore -> do
      resource <- takeResource pool
      ret <- restore (runInIO (act resource)) `onException`
             destroyResource pool resource

      void . runInIO $ do
        (keep, _) <- restoreM ret :: m (Bool, b)

        if keep
          then liftBaseWith . const $ putResource pool resource
          else liftBaseWith . const $ destroyResource pool resource

      return ret

我有一种感觉,这不是它看起来的样子...... 也许我没有使用MonadBaseControl API权限。 你们怎么看待这个?我怎样才能把它改进为更惯用的?

1 个答案:

答案 0 :(得分:2)

我觉得这种方法存在根本问题。对于StM M aa相等/同构的monad,它将起作用。但对于其他monad将会出现问题。我们来考虑MaybeT IO。类型a -> MaybeT IO (Bool, b)的操作可能会失败,因此不会生成Bool值。

中的代码
  void . runInIO $ do
    (keep, _) <- restoreM ret :: m (Bool, b)
    ...

不会被执行,控制流将停在restoreM。对于ListT IO而言,它会更糟,因为putResourcedestroyResource会被执行多次。考虑这个示例程序,它是函数的简化版本:

{-# LANGUAGE FlexibleContexts, ScopedTypeVariables, RankNTypes, TupleSections #-}
import Control.Monad
import Control.Monad.Trans.Control
import Control.Monad.Trans.List

foo :: forall m b . (MonadBaseControl IO m) => m (Bool, b) -> m b
foo act = fmap snd result
  where
    result :: m (Bool, b)
    result = control $ \runInIO -> do
      ret <- runInIO act

      void . runInIO $ do
        (keep, _) <- restoreM ret :: m (Bool, b)

        if keep
          then liftBaseWith . const $ putStrLn "return"
          else liftBaseWith . const $ putStrLn "destroy"

      return ret

main :: IO ()
main = void . runListT $ foo f
  where
    f = msum $ map (return . (, ())) [ False, True, False, True ]

它打印

destroy
return
destroy
return

对于一个空列表,没有任何内容被打印出来,这意味着你的函数不会调用任何清理工作。

我不得不说我不确定如何以更好的方式实现目标。我试着向签名方向探索

withResource :: forall m a b. (MonadBaseControl IO m)
             => Pool a -> (a -> IO () -> m b) -> m b

其中IO ()参数是一个函数,在执行时,会使当前资源无效并将其标记为要销毁。 (或者,为了更方便,请将IO ()替换为提升m ())。然后在内部,以IO为基础,我只需创建一个帮助MVar,然后通过调用重置 函数,最后,根据值,返回或销毁资源。