monadology-0.3: src/Control/Monad/Ology/Specific/LifecycleT.hs
module Control.Monad.Ology.Specific.LifecycleT
( LifecycleT(..)
, Lifecycle
, runLifecycle
, lifecycleOnCloseIO
, lifecycleOnClose
, lifecycleGetCloser
, forkLifecycle
, lifecycleMonitor
-- * With
, With
, withLifecycle
, lifecycleWith
-- * LifeState
, LifeState
, pattern NoLifeState
, lifeStateModify
, closeLifeState
, getLifeState
, addLifeState
, modifyLifeState
) where
import Control.Monad.Ology.General
import Control.Monad.Ology.Specific.StateT
import Import
-- | This represents all the actions that need to be done when closing the lifecycle.
newtype LifeState =
MkLifeState (Maybe (IO (IO Any)))
pattern NoLifeState :: LifeState
pattern NoLifeState = MkLifeState Nothing
lifeStateModify :: (IO --> IO) -> LifeState -> LifeState
lifeStateModify _ (MkLifeState Nothing) = MkLifeState Nothing
lifeStateModify m (MkLifeState (Just ioioa)) = MkLifeState $ Just $ m $ fmap m ioioa
closeIOAny :: IO Any -> IO ()
closeIOAny ioa = do
Any b <- ioa
if b
then closeIOAny ioa
else return ()
closeLifeState' :: LifeState -> IO Any
closeLifeState' (MkLifeState (Just ioioa)) = do
ioa <- ioioa
closeIOAny ioa
return $ Any True
closeLifeState' (MkLifeState Nothing) = return $ Any False
closeLifeState :: LifeState -> IO ()
closeLifeState ls = do
_ <- closeLifeState' ls
return ()
varLifeState :: MVar LifeState -> LifeState
varLifeState var =
MkLifeState $
Just $
return $ do
ls <- takeMVar var
putMVar var mempty
closeLifeState' ls
instance Semigroup LifeState where
MkLifeState Nothing <> q = q
p <> MkLifeState Nothing = p
MkLifeState (Just p) <> MkLifeState (Just q) = MkLifeState $ Just $ p <> q
instance Monoid LifeState where
mempty = MkLifeState Nothing
-- | This is for managing the automatic closing of opened resources.
newtype LifecycleT m a = MkLifecycleT
{ unLifecycleT :: MVar LifeState -> m a
}
instance Functor m => Functor (LifecycleT m) where
fmap ab (MkLifecycleT f) = MkLifecycleT $ \var -> fmap ab $ f var
instance TransConstraint Functor LifecycleT where
hasTransConstraint = Dict
instance Applicative m => Applicative (LifecycleT m) where
pure t = MkLifecycleT $ \_ -> pure t
(MkLifecycleT ocab) <*> (MkLifecycleT oca) = MkLifecycleT $ \var -> ocab var <*> oca var
instance TransConstraint Applicative LifecycleT where
hasTransConstraint = Dict
instance Monad m => Monad (LifecycleT m) where
return = pure
(MkLifecycleT va) >>= f =
MkLifecycleT $ \var -> do
a <- va var
unLifecycleT (f a) var
instance TransConstraint Monad LifecycleT where
hasTransConstraint = Dict
instance MonadTrans LifecycleT where
lift ma = MkLifecycleT $ \_ -> ma
instance MonadFail m => MonadFail (LifecycleT m) where
fail s = lift $ fail s
instance TransConstraint MonadFail LifecycleT where
hasTransConstraint = Dict
instance MonadException m => MonadException (LifecycleT m) where
type Exc (LifecycleT m) = Exc m
throwExc e = lift $ throwExc e
catchExc :: forall a. LifecycleT m a -> (Exc m -> LifecycleT m a) -> LifecycleT m a
catchExc (MkLifecycleT f) handler = MkLifecycleT $ \var -> catchExc (f var) $ \e -> unLifecycleT (handler e) var
instance TransConstraint MonadException LifecycleT where
hasTransConstraint = Dict
instance MonadThrow e m => MonadThrow e (LifecycleT m) where
throw e = lift $ throw e
instance TransConstraint (MonadThrow e) LifecycleT where
hasTransConstraint = Dict
instance MonadCatch e m => MonadCatch e (LifecycleT m) where
catch (MkLifecycleT f) handler = MkLifecycleT $ \var -> catch (f var) $ \e -> unLifecycleT (handler e) var
instance TransConstraint (MonadCatch e) LifecycleT where
hasTransConstraint = Dict
instance MonadFix m => MonadFix (LifecycleT m) where
mfix f = MkLifecycleT $ \var -> mfix $ \a -> unLifecycleT (f a) var
instance TransConstraint MonadFix LifecycleT where
hasTransConstraint = Dict
instance MonadIO m => MonadIO (LifecycleT m) where
liftIO ioa = lift $ liftIO ioa
instance TransConstraint MonadIO LifecycleT where
hasTransConstraint = Dict
instance MonadTransHoist LifecycleT where
hoist f (MkLifecycleT g) = MkLifecycleT $ \var -> f $ g var
instance MonadTransTunnel LifecycleT where
type Tunnel LifecycleT = Identity
tunnel ::
forall m r. Monad m
=> ((forall m1 a. Monad m1 => LifecycleT m1 a -> m1 (Identity a)) -> m (Identity r))
-> LifecycleT m r
tunnel f = MkLifecycleT $ \var -> fmap runIdentity $ f $ \a -> fmap Identity $ unLifecycleT a var
instance MonadTransUnlift LifecycleT where
liftWithUnlift call = MkLifecycleT $ \var -> call $ \(MkLifecycleT f) -> f var
getDiscardingUnlift =
return $
MkWUnlift $ \(MkLifecycleT f) -> do
var <- liftIO $ newMVar mempty
f var
addLifeState :: MonadIO m => LifeState -> LifecycleT m ()
addLifeState (MkLifeState Nothing) = return ()
addLifeState ls =
MkLifecycleT $ \var -> do
dangerousMVarRunStateT var $ do
s <- get
put $ ls <> s
-- | Add a closing action.
lifecycleOnCloseIO :: MonadIO m => IO () -> LifecycleT m ()
lifecycleOnCloseIO closer =
addLifeState $
MkLifeState $
Just $ do
closer
return $ return $ Any False
-- | Add a closing action.
lifecycleOnClose :: MonadAskUnliftIO m => m () -> LifecycleT m ()
lifecycleOnClose closer = do
MkWRaised unlift <- lift askUnliftIO
lifecycleOnCloseIO $ unlift closer
-- | Convert a lifecycle to a function that uses the \"with\" pattern.
withLifecycle ::
forall m a. (MonadException m, MonadTunnelIO m)
=> LifecycleT m a
-> With m a
withLifecycle (MkLifecycleT f) run = do
var <- liftIO $ newMVar mempty
finally (f var >>= run) $ liftIO $ closeLifeState $ varLifeState var
-- | Run the lifecycle, then close all resources in reverse order they were opened.
runLifecycle ::
forall m. (MonadException m, MonadTunnelIO m)
=> LifecycleT m --> m
runLifecycle lc = withLifecycle lc return
-- | Fork a thread that will complete in this lifecycle. Closing will wait for the thread to finish.
forkLifecycle :: MonadUnliftIO m => m () -> LifecycleT m ThreadId
forkLifecycle action = do
var <- liftIO newEmptyMVar
lifecycleOnCloseIO $ takeMVar var
lift $ liftIOWithUnlift $ \unlift -> forkIO $ finally (unlift action) $ putMVar var ()
-- | Runs a lifecycle, but instead of running the closing actions, return them as a 'LifeState'.
getLifeState ::
forall m a. MonadIO m
=> LifecycleT m a
-> m (a, LifeState)
getLifeState (MkLifecycleT f) = do
var <- liftIO $ newMVar mempty
t <- f var
return (t, varLifeState var)
modifyLifeState ::
forall m. MonadIO m
=> (LifeState -> LifeState)
-> LifecycleT m --> LifecycleT m
modifyLifeState ss la = do
(a, ls) <- lift $ getLifeState la
addLifeState $ ss ls
return a
-- | Runs the given lifecycle, returning a closer.
-- This is how you close things out of order.
--
-- The closer is an idempotent action that will close the lifecycle only if it hasn't already been closed.
-- The closer will also be run as the closer of the resulting lifecycle.
lifecycleGetCloser ::
forall m a. MonadIO m
=> LifecycleT m a
-> LifecycleT m (a, IO ())
lifecycleGetCloser lc = do
(a, ls) <- lift $ getLifeState lc
var <- liftIO $ newMVar ()
let
earlycloser :: IO ()
earlycloser = do
mu <- tryTakeMVar var
case mu of
Just () -> closeLifeState ls
Nothing -> return ()
lifecycleOnCloseIO earlycloser
return (a, earlycloser)
-- | Returned action returns 'True' if still alive, 'False' if closed.
lifecycleMonitor :: MonadIO m => LifecycleT m (IO Bool)
lifecycleMonitor = do
ref <- liftIO $ newIORef True
lifecycleOnCloseIO $ writeIORef ref False
return $ readIORef ref
-- | Convert a function that uses the \"with\" pattern to a lifecycle.
lifecycleWith :: (MonadCoroutine m, MonadAskUnliftIO m) => With m t -> LifecycleT m t
lifecycleWith withX = do
(t, closer) <- lift $ unpickWith withX
lifecycleOnClose closer
return t
-- | This is the expected most common use.
type Lifecycle = LifecycleT IO