fused-effects-1.1.2.7: src/Control/Carrier/Accum/IORef.hs
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE UndecidableInstances #-}
{- | A carrier for 'Accum' effects.
This carrier performs its append operations strictly and thus avoids the space leaks inherent in lazy writer monads.
These appends are left-associative; as such, @[]@ is a poor choice of monoid for computations that entail many calls to 'add'.
The [Seq](http://hackage.haskell.org/package/containersdocs/Data-Sequence.html) or [DList](http://hackage.haskell.org/package/dlist) monoids may be a superior choice.
This carrier also uses an 'IORef' to store its accumulator, which allows it a 'MonadUnliftIO' instance, but precludes backtracking when run in conjunction with 'Control.Effect.NonDet'.
@since 1.1.2.0
-}
module Control.Carrier.Accum.IORef
( -- * Accum carrier
runAccum
, execAccum
, evalAccum
, AccumC(AccumC)
-- * Accum effect
, module Control.Effect.Accum
) where
import Control.Algebra
import Control.Applicative (Alternative(..))
import Control.Effect.Accum
import Control.Monad (MonadPlus(..))
import Control.Monad.Fail as Fail
import Control.Monad.Fix
import Control.Monad.IO.Class
import Control.Monad.Trans.Class
import Data.IORef
import qualified Data.Semigroup as S
import Control.Monad.IO.Unlift (MonadUnliftIO)
import Control.Carrier.Reader
-- | Run an 'Accum' effect with a 'Semigroup'-based log.
--
-- @
-- 'runAccum' w0 ('pure' a) = 'pure' (w0, a)
-- @
-- @
-- 'runAccum' w0 ('add' w) = 'pure' (w0 <> w, ())
-- @
-- @
-- 'runAccum' w0 ('add' w >> 'look') = 'pure' (w0 <> w, w0 <> w)
-- @
--
-- @since 1.1.2.0
runAccum :: MonadIO m => w -> AccumC w m a -> m (w, a)
runAccum start go = do
ref <- liftIO (newIORef start)
result <- runReader ref . runAccumC $ go
final <- liftIO (readIORef ref)
pure (final, result)
{-# INLINE runAccum #-}
-- | Run a 'Accum' effect with a 'Semigroup'-based log,
-- producing the final log and discarding the result value.
--
-- @
-- 'execAccum' w = 'fmap' 'fst' . 'runAccum' w
-- @
--
-- @since 1.1.2.0
execAccum :: MonadIO m => w -> AccumC w m a -> m w
execAccum w = fmap fst . runAccum w
{-# INLINE execAccum #-}
-- | Run a 'Accum' effect with a 'Semigroup'-based log,
-- producing the result value and discarding the final log.
--
-- @
-- 'evalAccum' w = 'fmap' 'snd' . 'runAccum' w
-- @
--
-- @since 1.1.2.0
evalAccum :: MonadIO m => w -> AccumC w m a -> m a
evalAccum w = fmap snd . runAccum w
{-# INLINE evalAccum #-}
-- | @since 1.1.2.0
newtype AccumC w m a = AccumC { runAccumC :: ReaderC (IORef w) m a }
deriving (Alternative, Applicative, Functor, Monad, Fail.MonadFail, MonadFix, MonadIO, MonadPlus, MonadTrans, MonadUnliftIO)
instance (Algebra sig m, S.Semigroup w, MonadIO m) => Algebra (Accum w :+: sig) (AccumC w m) where
alg hdl sig ctx = case sig of
L accum -> do
ref <- AccumC (ask @(IORef w))
(<$ ctx) <$> case accum of
Add w' -> liftIO (modifyIORef' ref (S.<> w'))
Look -> liftIO (readIORef ref)
R other -> AccumC (alg (runAccumC . hdl) (R other) ctx)
{-# INLINE alg #-}