packages feed

monad-effect-0.2.0.0: src/Control/System.hs

{-# LANGUAGE AllowAmbiguousTypes, UndecidableInstances, UndecidableSuperClasses #-}
module Control.System
  (
  -- * Module and System
    Module(..)
  , WithSystem(..)

  , EventLoop(..)
  , EventLoopSystem(..)

  -- * read from module / get state
  , askModule, asksModule
  , queryModule, queriesModule
  , localModule
  , getModule, getsModule
  , putModule, modifyModule

  -- * Loadable
  , Loadable(..)
  , Dependency, Dependency'

  -- * Small utils
  , detectFlag, detectAllFlags

  ) where

import Control.Applicative
import Control.Concurrent.STM
import Control.Monad.Effect
import Data.Kind
import Data.Maybe
import Data.Text (Text)
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)

type family DependencyW' c (mod :: Type) (deps :: [Type]) (mods :: [Type]) :: Constraint where
  DependencyW' c mod '[] mods = (In' c mod (mod : mods))
  DependencyW' c mod (dep ': deps) mods = (In' c dep mods, In' c dep (mod : mods), DependencyW' c 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 family Dependency' c (mod :: Type) (deps :: [Type]) (mods :: [Type]) :: Constraint where
  Dependency' c mod deps mods = (ConsFDataList c (mod : mods), DependencyW' c mod deps mods)

-- | Run a System of EffT' given initData
--
class Loadable c mod mods ies where
  {-# MINIMAL withModule #-}
  withModule :: ConsFDataList c (mod : mods) => ModuleInitData mod -> EffT' c (mod : mods) ies IO a -> EffT' c mods ies IO a

class EventLoop c mod mods es where
  beforeEvent :: EffT' c (mod : mods) es IO ()
  beforeEvent = return ()
  {-# INLINE beforeEvent #-}

  afterEvent  :: EffT' c (mod : mods) es IO ()
  afterEvent = return ()
  {-# INLINE afterEvent #-}

  moduleEvent :: EffT' c (mod : mods) es IO (STM (ModuleEvent mod))
  moduleEvent = return empty
  {-# INLINE moduleEvent #-}

  handleEvent :: ModuleEvent mod -> EffT' c (mod : mods) es IO ()
  handleEvent _ = return ()
  {-# INLINE handleEvent #-}

------------------------------------------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 WithSystem c mods initEs where
  withSystem :: ConsFDataList c mods => SystemInitData c mods -> EffT' c mods initEs IO a -> EffT' c '[] initEs IO a

class EventLoopSystem c mods es where
  listenToEvents :: ConsFDataList c mods => EffT' c mods es IO (STM (SystemEvent mods))

  handleEvents :: ConsFDataList c mods => SystemEvent mods -> EffT' c mods es IO ()

  beforeSystem :: ConsFDataList c mods => EffT' c mods es IO ()

  afterSystem  :: ConsFDataList c mods => EffT' c mods es IO ()

-- | base case for system
instance WithSystem c '[] ies where
  withSystem _ = id
  -- return (fNil, fNil)
  {-# INLINE withSystem #-}

instance EventLoopSystem c '[] es where
  listenToEvents = return empty
  {-# INLINE listenToEvents #-}

  handleEvents _ = return ()
  {-# INLINE handleEvents #-}

  beforeSystem = return ()
  {-# INLINE beforeSystem #-}

  afterSystem = return ()
  {-# INLINE afterSystem #-}

-- | Inductive instance for system
instance (SystemModule mod, WithSystem c mods ies, Loadable c mod mods ies) => WithSystem c (mod ': mods) ies where
  withSystem (x :*** xs) = withSystem @c @mods @ies xs . withModule @c @mod @mods @ies x
  {-# INLINE withSystem #-}

instance (SystemModule mod, EventLoop c mod mods es, EventLoopSystem c mods es) => EventLoopSystem c (mod ': mods) es where
  beforeSystem = do
    embedMods $ beforeSystem @c @mods @es
    beforeEvent @c @mod @mods @es
  {-# INLINE beforeSystem #-}

  afterSystem = do
    embedMods $ afterSystem @c @mods @es
    afterEvent @c @mod @mods @es
  {-# INLINE afterSystem #-}

  listenToEvents = do
    tailEvents <- embedMods $ listenToEvents @c @mods @es
    headEvent  <- moduleEvent @c @mod @mods @es
    return $ UHead <$> headEvent <|> UTail <$> tailEvents
  {-# INLINE listenToEvents #-}

  handleEvents (UHead x) = handleEvent @c @mod @mods @es x
  handleEvents (UTail xs) = embedMods $ handleEvents @_ @mods @es xs
  {-# INLINE handleEvents #-}

detectFlag :: String -> (String -> Either Text a) -> [String] -> Maybe (Either Text a)
detectFlag flag parser = listToMaybe . detectAllFlags flag parser

detectAllFlags :: String -> (String -> Either Text a) -> [String] -> [Either Text a]
detectAllFlags _ _ [] = []
detectAllFlags flag parser list = go list
  where
    go (x:y:xs) | x == flag = parser y : go xs
                | otherwise = go (y:xs)
    go _ = []