monad-effect-0.1.0.0: src/Control/System.hs
{-# LANGUAGE AllowAmbiguousTypes, UndecidableInstances #-}
module Control.System
(
-- * Module and System
Module(..)
, System(..), Loadable(..)
, runSystemWithInitData
, askModule, asksModule
, queryModule, queriesModule
, localModule
, getModule, getsModule
, putModule, modifyModule
-- * Loadable
, ModuleInitDataHardCode
, LoadableEnv(..), LoadableArgs(..), SystemEnv(..), SystemArgs(..)
, SystemInitDataHardCode'
, SystemInitDataHardCode
, SystemInitDataHardCodeL
, Dependency
) where
import Control.Applicative
import Control.Concurrent.STM
import Control.Monad.Effect
import Data.Kind
import Data.TypeList
type family DependencyW (mod :: Type) (deps :: [Type]) (mods :: [Type]) :: Constraint where
DependencyW mod '[] mods = mod `In` (mod : mods)
DependencyW mod (dep ': deps) mods = (dep `In` mods, dep `In` (mod : mods), DependencyW mod deps mods)
-- | A type family that can be used to generate the constraints
-- to make specifying module dependencies easier
type family Dependency (mod :: Type) (deps :: [Type]) (mods :: [Type]) :: Constraint where
Dependency mod deps mods = (ConsFDataList FData (mod : mods), DependencyW mod deps mods)
type SystemInitDataHardCode' c mods = c ModuleInitDataHardCode mods
type SystemInitDataHardCode mods = SystemInitDataHardCode' FData mods
type SystemInitDataHardCodeL mods = SystemInitDataHardCode' FList mods
-- | Run a System of EffT' given initData
--
-- If error happens at initialization, it will return Left SystemError
-- If error happens during normal flow, it will return Right (RFailure SystemError)
runSystemWithInitData :: forall mods es m c a. (ConsFDataList c mods, System c mods, MonadIO m)
=> SystemInitData c mods
-> EffT' c mods es m a
-> m
(Either SystemError -- ^ if error happens at initialization
( Result es a -- ^ if error happens during event loop
, SystemState c mods)
)
runSystemWithInitData initData eff = do
liftIO (runEffT0 (initAllModules @c @mods initData)) >>= \case
RSuccess (rs, ss) -> Right <$> runEffT rs ss eff
RFailure (EHead e) -> return $ Left e
-- | Specifies that the module can load after mods are loaded
-- in practice we could use
-- instance SomeModuleWeNeed `In` mods => Loadable mods SomeModuleToLoad
class Loadable c mod mods where
{-# MINIMAL initModule #-}
initModule :: ModuleInitData mod -> EffT' c mods '[SystemError] IO (ModuleRead mod, ModuleState mod)
beforeEvent :: EffT' c (mod : mods) NoError IO ()
beforeEvent = return ()
{-# INLINE beforeEvent #-}
afterEvent :: EffT' c (mod : mods) NoError IO ()
afterEvent = return ()
{-# INLINE afterEvent #-}
moduleEvent :: EffT' c (mod : mods) NoError IO (STM (ModuleEvent mod))
moduleEvent = return empty
{-# INLINE moduleEvent #-}
handleEvent :: ModuleEvent mod -> EffT' c (mod : mods) NoError IO ()
handleEvent _ = return ()
{-# INLINE handleEvent #-}
releaseModule :: EffT' c (mod : mods) NoError IO () -- ^ release resources, quit module
releaseModule = return ()
{-# INLINE releaseModule #-}
data family ModuleInitDataHardCode mod :: Type
class Loadable c mod mods => LoadableEnv c mod mods where
readInitDataFromEnv :: ModuleInitDataHardCode mod -> EffT' c '[] '[SystemError] IO (ModuleInitData mod)
-- ^ Read module init data from environment, or other means
-- one can change this to SystemInitData mods -> IO (ModuleInitData mod)
class Loadable c mod mods => LoadableArgs c mod mods where
readInitDataFromArgs :: ModuleInitDataHardCode mod -> [String] -> EffT' c '[] '[SystemError] IO (ModuleInitData mod)
-- ^ Read module init data from command line arguments, or using comand line arguments to read other things
-- one can change this to SystemInitData mods -> [String] -> IO (ModuleInitData mod)
------------------------------------------system : a list of modules------------------------------------------
-- | System is a list of modules loaded in sequence with dependency verification
--
-- the last module in the list is the first to be loaded
-- and also the first to execute beforeEvent and afterEvent
class System c mods where
initAllModules :: ConsFDataList c mods => SystemInitData c mods -> EffT' c '[] '[SystemError] IO (SystemRead c mods, SystemState c mods)
listenToEvents :: ConsFDataList c mods => EffT' c mods '[] IO (STM (SystemEvent mods))
handleEvents :: ConsFDataList c mods => SystemEvent mods -> EffT' c mods '[] IO ()
beforeSystem :: ConsFDataList c mods => EffT' c mods '[] IO ()
afterSystem :: ConsFDataList c mods => EffT' c mods '[] IO ()
releaseSystem :: ConsFDataList c mods => EffT' c mods '[] IO ()
-- ^ safely release all resources system acquired
-- Warning: releaseSystem is done in reverse order of initAllModules
-- i.e. the head of the list is the first to be released
class System c mods => SystemEnv c mods where
readSystemInitDataFromEnv :: ConsFDataList c mods => SystemInitDataHardCode' c mods -> EffT' c '[] '[SystemError] IO (SystemInitData c mods)
class System c mods => SystemArgs c mods where
readSystemInitDataFromArgs :: ConsFDataList c mods => SystemInitDataHardCode' c mods -> [String] -> EffT' c '[] '[SystemError] IO (SystemInitData c mods)
-- | base case for system
instance System c '[] where
initAllModules _ = return (fNil, fNil)
{-# INLINE initAllModules #-}
listenToEvents = return empty
{-# INLINE listenToEvents #-}
handleEvents _ = return ()
{-# INLINE handleEvents #-}
beforeSystem = return ()
{-# INLINE beforeSystem #-}
afterSystem = return ()
{-# INLINE afterSystem #-}
releaseSystem = return ()
{-# INLINE releaseSystem #-}
instance SystemEnv c '[] where
readSystemInitDataFromEnv _ = return fNil
{-# INLINE readSystemInitDataFromEnv #-}
instance SystemArgs c '[] where
readSystemInitDataFromArgs _ _ = do
return fNil
{-# INLINE readSystemInitDataFromArgs #-}
-- | Inductive instance for system
instance (SystemModule mod, System c mods, Loadable c mod mods) => System c (mod ': mods) where
initAllModules (x :*** xs) = do
(rs, ss) <- initAllModules xs
(er, ss') <- liftIO $ runEffT rs ss $ initModule @c @mod x
case er of
RSuccess (r', s') -> return (r' :*** rs, s' :*** ss')
RFailure (EHead e) -> effThrowIn e
{-# INLINE initAllModules #-}
beforeSystem = do
embedEffT $ beforeSystem @c @mods
beforeEvent @c @mod
{-# INLINE beforeSystem #-}
afterSystem = do
embedEffT $ afterSystem @c @mods
afterEvent @c @mod
{-# INLINE afterSystem #-}
listenToEvents = do
tailEvents <- embedEffT $ listenToEvents @c @mods
headEvent <- moduleEvent @c @mod
return $ UHead <$> headEvent <|> UTail <$> tailEvents
{-# INLINE listenToEvents #-}
handleEvents (UHead x) = handleEvent @c @mod x
handleEvents (UTail xs) = embedEffT $ handleEvents @_ @mods xs
{-# INLINE handleEvents #-}
releaseSystem = do
releaseModule @c @mod
embedEffT $ releaseSystem @c @mods
{-# INLINE releaseSystem #-}
instance (SubList c mods (mod:mods), SystemModule mod, SystemEnv c mods, Loadable c mod mods, LoadableEnv c mod mods) => SystemEnv c (mod ': mods) where
readSystemInitDataFromEnv (im :*** ims) = do
xs <- readSystemInitDataFromEnv @c @mods ims
x <- readInitDataFromEnv @c @mod @mods im
return $ x :*** xs
{-# INLINE readSystemInitDataFromEnv #-}
instance (SubList c mods (mod:mods), SystemModule mod, SystemArgs c mods, Loadable c mod mods, LoadableArgs c mod mods) => SystemArgs c (mod ': mods) where
readSystemInitDataFromArgs (im :*** ims) args = do
xs <- readSystemInitDataFromArgs @c @mods ims args
x <- readInitDataFromArgs @c @mod @mods im args
return $ x :*** xs
{-# INLINE readSystemInitDataFromArgs #-}