packages feed

eventuo11y-0.6.0.0: src/Observe/Event/Class.hs

{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE InstanceSigs #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE QuantifiedConstraints #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE StandaloneKindSignatures #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeInType #-}
{-# LANGUAGE UnboxedTuples #-}
{-# LANGUAGE UndecidableInstances #-}

-- |
-- Description : Typeclass for implicit 'EventBackend' passing
-- Copyright   : Copyright 2022 Shea Levy.
-- License     : Apache-2.0
-- Maintainer  : shea@shealevy.com
--
-- Core typeclass for an mtl-style handling of 'EventBackend's.
-- See "Observe.Event.Explicit" for an explicit, function-based
-- interface.
module Observe.Event.Class
  ( MonadEvent (..),
    EnvBackend,
    TransEventMonad (..),

    -- * EventT
    EventT (..),
    runEventT,
    eventLift,

    -- * Kind aliases
    EventMonadKind,
    ReferenceKind,
    SelectorKind,
    FunctorKind,
  )
where

import Control.Applicative
import Control.Monad
import Control.Monad.Allocate
import Control.Monad.Base
import Control.Monad.Catch
import Control.Monad.Cont.Class
import Control.Monad.Error.Class
import Control.Monad.IO.Unlift
import Control.Monad.Primitive
import Control.Monad.Reader
import Control.Monad.State.Class
import Control.Monad.Trans.Control
import Control.Monad.With
import Control.Monad.Writer.Class
import Control.Monad.Zip
import Control.Natural.Control
import Data.Coerce
import Data.Functor
import Data.Functor.Contravariant
import Data.GeneralAllocate
import Data.Kind
import Observe.Event.Backend

-- | Monads suitable for 'Event'-based instrumentation, with implicit 'EventBackend' management.
--
-- See "Observe.Event.Explicit" for 'Event'-based instrumentation with explicit 'EventBackend'
-- passing.
--
-- Note that @em@ is an indexed monad of 'EventMonadKind'.
type MonadEvent :: EventMonadKind -> Constraint
class (forall r s. Monad (em r s), Monad (BackendMonad em)) => MonadEvent em where
  -- | The monad of the implicitly carried 'EventBackend'
  type BackendMonad em :: Type -> Type

  -- |
  liftBackendMonad :: BackendMonad em a -> em r s a

  -- | Access the implicitly carried 'EventBackend'
  backend :: em r s (EnvBackend em r s)

  -- | Run an instrumented action with a modified 'EventBackend'
  withModifiedBackend ::
    -- | Modify the 'EventBackend'
    --
    -- Note that the modification may change
    -- the reference and selector types.
    (EnvBackend em r s -> EnvBackend em r' s') ->
    -- | Action to run with the modified backend available.
    em r' s' a ->
    em r s a

-- | The type of the implicit 'EventBackend' of a 'MonadEvent'
type EnvBackend :: EventMonadKind -> ReferenceKind -> SelectorKind -> Type
type EnvBackend em = EventBackend (BackendMonad em)

-- | Make a monad into a 'MonadEvent'.
newtype EventT m r s a = EventT (ReaderT (EventBackend m r s) m a)
  deriving newtype
    ( Functor,
      Applicative,
      Monad,
      MonadError e,
      MonadState s',
      MonadWriter w,
      MonadFail,
      MonadFix,
      MonadIO,
      MonadZip,
      Contravariant,
      Alternative,
      MonadPlus,
      MonadCont,
      MonadUnliftIO,
      MonadBase b,
      MonadBaseControl b,
      MonadAllocate,
      MonadCatch,
      MonadThrow,
      MonadMask,
      PrimMonad
    )

-- | Run an 'EventT' with an initial 'EventBackend'.
runEventT :: (Monad m) => EventT m r s a -> EventBackend m r s -> m a
runEventT = coerce

-- | Lift @m@ into 'EventT' @m@.
eventLift :: forall m r s. Applicative m => StatelessControlTransformation m (EventT m r s)
eventLift = statelessControlTransformation $ \useRunInM ->
  coerce @(EventBackend m r s -> _) $ \b -> useRunInM $ flip coerce b

instance MonadReader r m => MonadReader r (EventT m ref s) where
  ask = toNatural eventLift ask
  local modR go = statelessTransWith eventLift $ \runInM ->
    local modR (runInM go)
  reader = toNatural eventLift . reader

instance (MonadWith m) => MonadWith (EventT m r s) where
  type WithException (EventT m r s) = WithException m
  stateThreadingGeneralWith ::
    forall a b releaseReturn.
    GeneralAllocate (EventT m r s) (WithException m) releaseReturn b a ->
    (a -> EventT m r s b) ->
    EventT m r s (b, releaseReturn)
  stateThreadingGeneralWith (GeneralAllocate allocA) go = coerce $ \b -> do
    let allocA' :: (forall x. m x -> m x) -> m (GeneralAllocated m (WithException m) releaseReturn b a)
        allocA' restore = do
          let restore' :: forall x. EventT m r s x -> EventT m r s x
              restore' mx = coerce @(EventBackend m r s -> m x) $ restore . coerce @_ @(EventBackend m r s -> _) mx
          GeneralAllocated a releaseA <- coerce (allocA restore') b
          let releaseA' relTy = coerce @(EventT m r s releaseReturn) @(EventBackend m r s -> m releaseReturn) (releaseA relTy) b
          pure $ GeneralAllocated a releaseA'
    stateThreadingGeneralWith (GeneralAllocate allocA') (flip coerce b . go)

instance (Monad m) => MonadEvent (EventT m) where
  type BackendMonad (EventT m) = m
  liftBackendMonad :: forall a r s. m a -> EventT m r s a
  liftBackendMonad = coerce @(EventBackend m r s -> _) . const

  backend :: forall r s. EventT m r s (EventBackend m r s)
  backend = coerce @(EventBackend m r s -> m _) pure

  withModifiedBackend :: forall r s r' s' a. (EventBackend m r s -> EventBackend m r' s') -> EventT m r' s' a -> EventT m r s a
  withModifiedBackend modBackend e'ma =
    let e'ma' :: EventBackend m r' s' -> m a
        e'ma' = coerce e'ma
     in coerce (e'ma' . modBackend)

-- | Apply a 'MonadTrans'former to an 'EventMonadKind' to get a transformed 'EventMonadKind'
--
-- When @t@ is 'MonadTransControl' and @em@ is 'MonadEvent', 'TransEventMonad' @t@ @em@ is
-- 'MonadEvent' and has all of the relevant instances conferred by @t@.
type TransEventMonad :: (FunctorKind -> FunctorKind) -> EventMonadKind -> EventMonadKind
newtype TransEventMonad t em r s a = TransEventMonad {unTransEventMonad :: t (em r s) a}
  deriving newtype
    ( Functor,
      Applicative,
      Monad,
      MonadError e,
      MonadState s',
      MonadWriter w,
      MonadFail,
      MonadFix,
      MonadIO,
      MonadZip,
      Contravariant,
      Alternative,
      MonadPlus,
      MonadCont,
      MonadUnliftIO,
      MonadBase b,
      MonadBaseControl b,
      MonadAllocate,
      MonadCatch,
      MonadThrow,
      MonadMask,
      PrimMonad,
      MonadReader r'
    )

instance MonadWith (t (em r s)) => MonadWith (TransEventMonad t em r s) where
  type WithException (TransEventMonad t em r s) = WithException (t (em r s))
  stateThreadingGeneralWith ::
    forall a b releaseReturn.
    GeneralAllocate (TransEventMonad t em r s) (WithException (t (em r s))) releaseReturn b a ->
    (a -> TransEventMonad t em r s b) ->
    TransEventMonad t em r s (b, releaseReturn)
  stateThreadingGeneralWith (GeneralAllocate allocA) go = coerce $ do
    stateThreadingGeneralWith (GeneralAllocate allocA') $ coerce go
    where
      allocA' :: (forall x. t (em r s) x -> t (em r s) x) -> t (em r s) (GeneralAllocated (t (em r s)) (WithException (t (em r s))) releaseReturn b a)
      allocA' restore =
        coerce (allocA restore') <&> \case
          GeneralAllocated a releaseA -> GeneralAllocated a $ coerce @(GeneralReleaseType (WithException (t (em r s))) b -> TransEventMonad t em r s releaseReturn) releaseA
        where
          restore' :: forall x. TransEventMonad t em r s x -> TransEventMonad t em r s x
          restore' = coerce @(t (em r s) x -> t (em r s) x) restore

instance (MonadEvent em, MonadTransControl t, forall r s. Monad (t (em r s))) => MonadEvent (TransEventMonad t em) where
  type BackendMonad (TransEventMonad t em) = BackendMonad em
  liftBackendMonad :: forall a r s. BackendMonad em a -> TransEventMonad t em r s a
  liftBackendMonad = coerce @(_ -> t (em r s) a) $ lift . liftBackendMonad

  backend :: forall r s. TransEventMonad t em r s (EnvBackend em r s)
  backend = coerce @(t (em r s) _) $ lift backend

  withModifiedBackend :: forall r s r' s' a. (EnvBackend em r s -> EnvBackend em r' s') -> TransEventMonad t em r' s' a -> TransEventMonad t em r s a
  withModifiedBackend modBackend go = coerce @(t (em r s) _) $
    controlT @_ @_ @a $ \runInEm ->
      withModifiedBackend modBackend . runInEm $ coerce @_ @(t (em r' s') a) go

-- | The kind of indexed monads aware of 'Event' instrumentation
--
-- See 'MonadEvent'.
type EventMonadKind = ReferenceKind -> SelectorKind -> FunctorKind

-- | The kind of 'Event' references
type ReferenceKind = Type

-- | The kind of 'Event' selectors
type SelectorKind = Type -> Type

-- | The kind of 'Functor's
type FunctorKind = Type -> Type