2015-08-31 48 views
11

我目前正在玩Bryan O'Sullivan的resource-pool庫,並且有關於擴展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)元組,其中布爾值指示借用資源應將 放回池中還是銷燬。現在使用`MonadBaseControl` API

我當前的實現看起來是這樣的:

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權利。 你們怎麼看待這個問題?我怎樣才能改善它的地道性?

+0

粗略的眼光看起來很好。有什麼困擾你的呢? – luqui

+1

@luqui困擾我一點的是,我必須運行'runInIO'兩次,這導致了更冗長的代碼。有沒有更好的方法來打開IO monad內部的'ret'(第一個'runInIO'調用的結果)? – bmk

回答

2

我有一種感覺,這種方法存在根本問題。對於其中StM M a等於/同構於a的單子,它將起作用。但對於其他monads,會有問題。我們考慮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,通過調用 該函數即可重置,最後根據該值返回或銷燬資源。

+0

非常感謝你的幫助。我現在可以看到'MaybeT'和'ListT'monads的問題......再次感謝! – bmk