in-other-words-0.1.0.0: src/Control/Effect/Fresh.hs
module Control.Effect.Fresh
( -- * Effects
Fresh(..)
-- * Actions
, fresh
-- * Interpretations
, freshToIO
, runFreshEnumIO
-- * Unsafe interpretations
, runFreshEnum
-- * Simple variants of interpretations
, runFreshEnumIOSimple
-- * Threading constraints
, StateThreads
-- * Carriers
, FreshToIOC
, FreshEnumC
) where
import Data.Unique
import Data.IORef
import Control.Effect
import Control.Effect.State
-- For coercion purposes
import Control.Effect.Internal.Utils
import Control.Effect.Carrier.Internal.Interpret
import Control.Effect.Carrier.Internal.Compose
import Control.Effect.Carrier.Internal.Intro
import Control.Monad.Trans.Identity
-- | An effect for creating unique objects which may be used as references,
-- a la 'Unique'. Polymorphic code making use of 'Fresh' is expected
-- to place constraints upon @uniq@ as necessary.
--
-- Any interpreter for 'Fresh' has the responsibilty of ensuring
-- that any call to 'fresh' produces an object that /never/
-- compares equal to an object produced by a previous call to 'fresh'.
data Fresh uniq m a where
Fresh :: Fresh uniq m uniq
fresh :: Eff (Fresh uniq) m => m uniq
fresh = send Fresh
{-# INLINE fresh #-}
data FreshToIOH
instance Eff (Embed IO) m
=> Handler FreshToIOH (Fresh Unique) m where
effHandler Fresh = embed newUnique
{-# INLINEABLE effHandler #-}
type FreshToIOC = InterpretC FreshToIOH (Fresh Unique)
-- | Runs a 'Fresh' effect through generating 'Unique's using 'IO'.
freshToIO :: Eff (Embed IO) m
=> FreshToIOC m a
-> m a
freshToIO = interpretViaHandler
{-# INLINE freshToIO #-}
-- | Run a 'Fresh' effect through atomic operations in 'IO'
-- by specifying an 'Enum' to be used as the type of unique objects.
--
-- This is a safe variant of 'runFreshEnum'.
--
-- This has a higher-rank type, as it makes use of 'InterpretReifiedC'.
-- __This makes 'runFreshEnumIO' very difficult to use partially applied.__
-- __In particular, it can't be composed using @'.'@.__
--
-- If performance is secondary, consider using the slower
-- 'runFreshEnumIOSimple', which doesn't have a higher-rank type.
runFreshEnumIO :: forall uniq m a
. ( Enum uniq
, Eff (Embed IO) m
)
=> InterpretReifiedC (Fresh uniq) m a
-> m a
runFreshEnumIO m = do
ref <- embed $ newIORef (toEnum @uniq 0)
(`interpret` m) $ \case
Fresh -> embed $ atomicModifyIORef' ref (\s -> (succ s, s))
{-# INLINE runFreshEnumIO #-}
-- | Run a 'Fresh' effect though atomic operations in 'IO'
-- by specifying an 'Enum' to be used as the type of unique objects.
--
-- This is a less performant version of 'runFreshEnumIO' that doesn't have
-- a higher-rank type, making it much easier to use partially applied.
runFreshEnumIOSimple :: forall uniq m a p
. ( Enum uniq
, Eff (Embed IO) m
, Threaders '[ReaderThreads] m p
)
=> InterpretSimpleC (Fresh uniq) m a
-> m a
runFreshEnumIOSimple m = do
ref <- embed $ newIORef (toEnum @uniq 0)
(`interpretSimple` m) $ \case
Fresh -> embed $ atomicModifyIORef' ref (\s -> (succ s, s))
{-# INLINE runFreshEnumIOSimple #-}
data FreshEnumH
instance (Enum uniq, Eff (State uniq) m)
=> Handler FreshEnumH (Fresh uniq) m where
effHandler Fresh = state' (\s -> (succ s, s))
{-# INLINEABLE effHandler #-}
type FreshEnumC uniq = CompositionC
'[ ReinterpretC FreshEnumH (Fresh uniq) '[State uniq]
, StateC uniq
]
-- | Run a 'Fresh' effect purely by specifying an 'Enum' to be used as the
-- type of unique objects.
--
-- __Beware:__ This is safe only if:
--
-- 1. This is run after all interpreters which may revert local state
-- or produce multiple, inconsistent instances of local state.
-- This includes interpreters that may backtrack or produce multiple results
-- (such as 'Control.Error.Error.runError' or 'Control.Effect.NonDet.runNonDet').
--
-- 2. You don't use any interpreter which may cause the final monad
-- to revert local state or produce multiple, inconsistent instances
-- of local state. This includes 'Control.Effect.Error.errorToIO' and
-- 'Control.Effect.Conc.asyncToIO'.
--
-- Prefer 'freshToIO' whenever possible.
runFreshEnum :: forall uniq m a p
. ( Enum uniq
, Threaders '[StateThreads] m p
, Carrier m
)
=> FreshEnumC uniq m a
-> m a
runFreshEnum =
evalState (toEnum 0)
.# reinterpretViaHandler
.# runComposition
{-# INLINE runFreshEnum #-}