{-# LANGUAGE BangPatterns, GeneralizedNewtypeDeriving, TemplateHaskell, Trustworthy #-} module Polysemy.Fresh (-- * Effect Fresh(..) -- * Actions , fresh -- * Interpretations , freshToIO -- * Unsafe Interpretations , runFreshEnumUnsafe , runFreshUnsafePerformIO ) where import Data.Unique import Polysemy.Internal import Polysemy.Internal.Union import System.IO.Unsafe (unsafePerformIO) import Polysemy import Polysemy.State ----------------------------------------------------------------------------- -- | 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 makeSem ''Fresh ----------------------------------------------------------------------------- -- | Runs a 'Fresh' effect through generating 'Unique's using 'IO'. freshToIO :: Member (Embed IO) r => Sem (Fresh Unique ': r) a -> Sem r a freshToIO = interpret $ \Fresh -> embed newUnique {-# INLINE freshToIO #-} ----------------------------------------------------------------------------- -- | 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 'Polysemy.Error.runError' or 'Polysemy.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 certain 'Polysemy.Final.Final'/@lower-@ interpeters -- such as 'Polysemy.Error.lowerError' or 'Polysemy.Final.MTL.errorToFinal', -- as well as interpreters for 'Polysemy.Async.Async'. -- -- Prefer 'freshToIO' whenever possible. -- If you can't use 'runFreshEnumUnsafe' safely, nor use 'freshToIO', consider -- 'runFreshUnsafePerformIO'. runFreshEnumUnsafe :: forall n a r . Enum n => Sem (Fresh n ': r) a -> Sem r a runFreshEnumUnsafe = (fmap snd .) $ (runState @n (toEnum 0) .) $ reinterpret $ \Fresh -> do s <- get put $! succ s return s {-# INLINE runFreshEnumUnsafe #-} ----------------------------------------------------------------------------- -- | Runs a 'Fresh' effect through generating 'Unique's using -- 'unsafePerformIO'. -- -- Ironically, despite the fact that this uses 'unsafePerformIO', and -- 'runFreshUnsafe' uses no unsafe operations whatsoever, this is still -- typically safer to use than 'runFreshUnsafe', although 'runFreshUnsafe' -- is perhaps more efficient. -- -- The worst thing that this particular use of 'unsafePerformIO' could result -- in is the loss of referential transparency, as rerunning an interpreter stack -- using 'runFreshUnsafePerformIO' will create different 'Unique's. This should -- never matter. -- -- This could be potentially be less efficient than 'runFreshUnsafe'. -- -- If you ever observe that multiple invocations of 'fresh' produce the same -- 'Unique' under 'runFreshUnsafePerformIO', then open an issue over at the -- GitHub repository. runFreshUnsafePerformIO :: Sem (Fresh Unique ': r) a -> Sem r a runFreshUnsafePerformIO = usingSem $ \u -> case decomp u of Right (Weaving Fresh s _ ex _) -> do let !uniq = unsafePerformIO (newUnique' u) {-# NOINLINE uniq #-} pure $ ex (uniq <$ s) Left g -> liftSem (hoist runFreshUnsafePerformIO g) -- KingoftheHomeless: I've tried very hard to prevent optimizations from -- sharing the call to 'unsafePerformIO'. -- The inlining of 'interpret' is so that I can give -- 'u' to 'newUnique'', and thus prevent 'uniq' from floating outside the -- lambda. This interpreter might even be safe to INLINE, but I'm not taking -- any chances. {-# NOINLINE runFreshUnsafePerformIO #-} newUnique' :: Union (Fresh Unique ': r) (Sem (Fresh Unique ': r)) a -> IO Unique newUnique' (Union _ _) = newUnique {-# NOINLINE newUnique' #-}