{-# 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 #-}