packages feed

hs-bindgen-1.0.0.0: src-internal/HsBindgen/Eff.hs

module HsBindgen.Eff (
    Eff     -- opaque
  , Support -- opaque
  , wrapEff
  , unwrapEff
  , runFoldIdentity
  , runFoldReader
  , runFoldState
  ) where

import Control.Monad.Reader (MonadReader, Reader, ReaderT (..))
import Control.Monad.State (MonadState (state), State)
import Data.IORef (IORef, atomicModifyIORef, newIORef, readIORef)
import Data.Tuple (swap)

import HsBindgen.Imports

{-------------------------------------------------------------------------------
  'Eff' monad

  We work mostly in @IO@, and limited by @MonadUnliftIO@,
  but sometimes we need more effects: @Eff@ wrapping @ReaderT r IO@
  pattern can mimic many other monads.

  NOTE: This is intentionally /not/ a member of 'MonadCatch'. We use 'Eff'
  primarily for folding, and exception handling in folding requires special
  care. See detailed discussion of 'foldWithHandler' in the @clang@ bindings.
-------------------------------------------------------------------------------}

newtype Eff m a = Eff (ReaderT (Support m) IO a)
  deriving newtype (
      Functor
    , Applicative
    , Monad
    , MonadIO
    , MonadUnliftIO
    )

getEff :: Eff m a -> ReaderT (Support m) IO a
getEff (Eff ma) = ma

wrapEff :: (Support m -> IO a) -> Eff m a
wrapEff = Eff . ReaderT

unwrapEff :: Eff m a -> Support m -> IO a
unwrapEff = runReaderT . getEff

-- | 'Control.Monad.Reader.ReaderT' argument required to support @m@
type family Support (m :: Star -> Star) :: Star

--
-- 'Identity'
--

type instance Support Identity = ()

runFoldIdentity :: Eff Identity a -> IO a
runFoldIdentity = ($ ()) . unwrapEff

--
-- 'Reader'
--

type instance Support (Reader r) = r

deriving newtype instance MonadReader r (Eff (Reader r))

runFoldReader :: r -> Eff (Reader r) a -> IO a
runFoldReader env = ($ env) . unwrapEff

--
-- 'State'
--

type instance Support (State s) = IORef s

instance MonadState s (Eff (State s)) where
  state f = wrapEff $ \ref -> atomicModifyIORef ref (swap . f)

runFoldState :: s -> Eff (State s) a -> IO (a, s)
runFoldState s f = do
    ref <- newIORef s
    a   <- unwrapEff f ref
    (a,) <$> readIORef ref