Ответ 1
У меня такое чувство, что существует фундаментальная проблема с этим подходом. Для монад, для которых StM M a
равно/изоморфно a
, это сработает. Но для других монадов возникнет проблема. Пусть рассмотрим MaybeT IO
. Действие типа a -> MaybeT IO (Bool, b)
может завершиться неудачей, поэтому не будет получено значение Bool
. И код в
void . runInIO $ do
(keep, _) <- restoreM ret :: m (Bool, b)
...
не будет выполняться, поток управления остановится на restoreM
. А для ListT IO
это будет еще хуже, так как putResource
и destroyResource
будут выполняться несколько раз. Рассмотрим эту примерную программу, которая является упрощенной версией вашей функции:
{-# 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
, который был бы reset путем вызова
функция, а в конце, в зависимости от значения, возвратите или уничтожьте ресурс.