packages feed

effectful-0.0.0.0: src/Effectful/Internal/Monad.hs

{-# LANGUAGE UndecidableInstances #-}
{-# OPTIONS_HADDOCK not-home #-}
-- | The 'Eff' monad.
--
-- This module is intended for internal use only, and may change without warning
-- in subsequent releases.
module Effectful.Internal.Monad
  ( -- * Monad
    Eff(..)
  , runEff
  , impureEff
  , impureEff_

  -- * IO
  , IOE
  , runIOE

  -- * Helpers
  , runEffect
  , evalEffect
  , execEffect
  , getEffect
  , putEffect
  , stateEffect
  , localEffect
  , listenEffect
  , readerEffectM
  , stateEffectM
  ) where

import Control.Concurrent (myThreadId)
import Control.Monad.Base
import Control.Monad.Catch
import Control.Monad.IO.Class
import Control.Monad.IO.Unlift
import Control.Monad.Trans.Control
import GHC.Magic (oneShot)

import Effectful.Internal.Env
import Effectful.Internal.Has

type role Eff nominal representational

newtype Eff (es :: [Effect]) a = Eff { unEff :: Env es -> IO a }

runEff :: Eff '[] a -> IO a
runEff (Eff m) = m =<< emptyEnv

impureEff :: (Env es -> IO a) -> Eff es a
impureEff m = Eff (oneShot m)

impureEff_ :: IO a -> Eff es a
impureEff_ m = impureEff $ \_ -> m

----------------------------------------
-- Base

instance Functor (Eff es) where
  fmap f (Eff m) = impureEff $ \es -> f <$> m es

instance Applicative (Eff es) where
  pure = impureEff_ . pure
  Eff mf <*> Eff mx = impureEff $ \es -> mf es <*> mx es

instance Monad (Eff es) where
  Eff m >>= k = impureEff $ \es -> do
    a <- m es
    unEff (k a) es

----------------------------------------
-- Exception

instance MonadThrow (Eff es) where
  throwM e = impureEff_ $ throwM e

instance MonadCatch (Eff es) where
  catch (Eff m) handler = impureEff $ \es -> do
    size <- sizeEnv es
    m es `catch` \e -> do
      checkSizeEnv size es
      unEff (handler e) es

instance MonadMask (Eff es) where
  mask k = impureEff $ \es -> mask $ \restore ->
    unEff (k $ (\f (Eff m) -> impureEff $ f . m) restore) es

  uninterruptibleMask k = impureEff $ \es -> uninterruptibleMask $ \restore ->
    unEff (k $ (\f (Eff m) -> impureEff $ f . m) restore) es

  generalBracket acquire release use = impureEff $ \es -> mask $ \restore -> do
    size <- sizeEnv es
    resource <- unEff acquire es
    b <- restore (unEff (use resource) es) `catch` \e -> do
      checkSizeEnv size es
      _ <- unEff (release resource $ ExitCaseException e) es
      throwM e
    checkSizeEnv size es
    c <- unEff (release resource $ ExitCaseSuccess b) es
    pure (b, c)

----------------------------------------
-- IO

data IOE = IOE

runIOE :: Eff '[IOE] a -> Eff '[] a
runIOE = evalEffect IOE

instance IOE :> es => MonadIO (Eff es) where
  liftIO = impureEff_

instance IOE :> es => MonadBase IO (Eff es) where
  liftBase = impureEff_

instance IOE :> es => MonadBaseControl IO (Eff es) where
  type StM (Eff es) a = a
  liftBaseWith = runInIO
  restoreM = pure

instance IOE :> es => MonadUnliftIO (Eff es) where
  withRunInIO = runInIO

-- | Run 'Eff' computations in 'IO'.
runInIO :: ((forall r. Eff es r -> IO r) -> IO a) -> Eff es a
runInIO f = impureEff $ \es -> do
  tid0 <- myThreadId
  f $ \(Eff m) -> do
    tid <- myThreadId
    -- If the lifting function is called from a different thread, we need to
    -- clone the environment, otherwise multiple threads will attempt to modify
    -- it in different ways and things will break horribly.
    if tid0 == tid
      then m es
      else m =<< cloneEnv es

----------------------------------------
-- Helpers

runEffect :: e -> Eff (e : es) a -> Eff es (a, e)
runEffect e0 (Eff m) = impureEff $ \es0 -> do
  size <- sizeEnv es0
  bracket (unsafeConsEnv e0 es0)
          (unsafeTrimEnv size)
          (\es -> (,) <$> m es <*> getEnv es)

evalEffect :: e -> Eff (e : es) a -> Eff es a
evalEffect e (Eff m) = impureEff $ \es0 -> do
  size <- sizeEnv es0
  bracket (unsafeConsEnv e es0)
          (unsafeTrimEnv size)
          (\es -> m es)

execEffect :: e -> Eff (e : es) a -> Eff es e
execEffect e0 (Eff m) = impureEff $ \es0 -> do
  size <- sizeEnv es0
  bracket (unsafeConsEnv e0 es0)
          (unsafeTrimEnv size)
          (\es -> m es *> getEnv es)

getEffect :: e :> es => Eff es e
getEffect = impureEff $ \es -> getEnv es

putEffect :: e :> es => e -> Eff es ()
putEffect e = impureEff $ \es -> unsafePutEnv e es

stateEffect :: e :> es => (e -> (a, e)) -> Eff es a
stateEffect f = impureEff $ \es -> unsafeStateEnv f es

localEffect :: e :> es => (e -> e) -> Eff es a -> Eff es a
localEffect f (Eff m) = impureEff $ \es -> do
  bracket (unsafeStateEnv (\e -> (e, f e)) es)
          (\e -> unsafePutEnv e es)
          (\_ -> m es)

listenEffect :: (e :> es, Monoid e) => Eff es a -> Eff es (a, e)
listenEffect (Eff m) = impureEff $ \es -> mask $ \restore -> do
  e0 <- unsafeStateEnv (\e -> (e, mempty)) es
  -- If an exception is thrown, restore e0 and keep parts of e1.
  a <- restore (m es) `onException` unsafeModifyEnv (\e -> e0 `mappend` e) es
  e1 <- unsafeStateEnv (\e -> (e, e0 `mappend` e)) es
  pure (a, e1)

readerEffectM :: e :> es => (e -> Eff es a) -> Eff es a
readerEffectM f = impureEff $ \es -> getEnv es >>= \e -> unEff (f e) es

stateEffectM :: e :> es => (e -> Eff es (a, e)) -> Eff es a
stateEffectM f = impureEff $ \es -> do
  (a, e) <- (\e -> unEff (f e) es) =<< getEnv es
  unsafePutEnv e es
  pure a