{-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE UndecidableInstances #-} ----------------------------------------------------------------------------- -- | -- Maintainer : me@joelt.io -- Stability : experimental -- Portability : portable -- -- A monad whose actions produce an output. -- -- This module builds output strictly. For a lazy version, see -- "Control.Monad.Writer.Concurrent.Lazy". ----------------------------------------------------------------------------- module Control.Monad.Writer.Concurrent.Strict ( module Control.Monad.Writer, -- *** The WriterC monad transformer WriterC, -- *** Running WriterC actions runWriterC, execWriterC, mapWriterC, -- *** Running concurrent operations on a single input runWritersC, execWritersC, -- *** Lifting other operations liftCallCC, liftCatch ) where import Control.Applicative import Control.Arrow (first) import Control.Concurrent.Lifted.Fork import Control.Concurrent.MVar import Control.Concurrent.STM import Control.Exception (throwIO) import Control.Monad import Control.Monad.Catch import Control.Monad.Fix import Control.Monad.IO.Class import Control.Monad.Trans.Class import Control.Monad.Reader import Control.Monad.Writer -- --------------------------------------------------------------------------- -- | A concurrent monad transformer collecting output of type @w@. -- -- This is very similar to @transformers@' 'WriterT', with the exception of -- the 'MonadIO' constraint on every instance, which is necessary to -- perform STM actions. newtype WriterC w m a = WriterC { _runWriterC :: TVar w -> m (a, TVar w) } instance MonadTrans (WriterC w) where lift m = WriterC $ \w -> do a <- m return (a, w) instance MonadIO m => MonadIO (WriterC w m) where liftIO i = WriterC $ \w -> do a <- liftIO i return (a, w) instance Functor m => Functor (WriterC w m) where fmap f m = WriterC $ \w -> fmap (first f) $ _runWriterC m w instance (Functor m, Monad m) => Applicative (WriterC w m) where pure = return (<*>) = ap instance (Functor m, MonadPlus m) => Alternative (WriterC w m) where empty = mzero (<|>) = mplus instance Monad m => Monad (WriterC w m) where return a = WriterC $ \w -> return (a, w) m >>= k = WriterC $ \w -> do (a, w') <- _runWriterC m w _runWriterC (k a) w' instance MonadPlus m => MonadPlus (WriterC w m) where mzero = WriterC $ const mzero m `mplus` n = WriterC $ \w -> _runWriterC m w `mplus` _runWriterC n w instance MonadFix m => MonadFix (WriterC w m) where mfix f = WriterC $ \w -> mfix $ \(a, _) -> _runWriterC (f a) w instance (Monoid w, MonadReader r m) => MonadReader r (WriterC w m) where ask = lift ask local f m = WriterC $ \w -> local f $ _runWriterC m w reader = lift . reader instance (Monoid w, MonadIO m) => MonadWriter w (WriterC w m) where writer (a, w) = WriterC $ \tw -> do liftIO . atomically $ modifyTVar' tw (<> w) return (a, tw) listen m = WriterC $ \tw -> do (a, tw') <- _runWriterC m tw w <- liftIO $ readTVarIO tw' return ((a, w), tw') pass m = WriterC $ \tw -> do ((a, f), tw') <- _runWriterC m tw liftIO . atomically $ modifyTVar' tw' f return (a, tw') instance (MonadIO m, MonadCatch m) => MonadCatch (WriterC w m) where throwM = liftIO . throwIO catch = liftCatch catch mask a = WriterC $ \w -> mask $ \u -> _runWriterC (a $ q u) w where q u (WriterC f) = WriterC (u . f) uninterruptibleMask a = WriterC $ \w -> uninterruptibleMask $ \u -> _runWriterC (a $ q u) w where q u (WriterC f) = WriterC (u . f) instance (Monoid w, MonadFork m) => MonadFork (WriterC w m) where fork = liftFork fork forkOn i = liftFork (forkOn i) forkOS = liftFork forkOS liftFork :: Monad m => (m () -> m a) -> WriterC w m () -> WriterC w m a liftFork f (WriterC m) = WriterC $ \w -> do tid <- f . voidM $ m w return (tid, w) where voidM = (>> return ()) -- | Unwrap a concurrent Writer monad computation as a function. runWriterC :: MonadIO m => WriterC w m a -- ^ computation to execute -> TVar w -- ^ output channel -> m (a, w) -- ^ return value and collected output runWriterC m tw = do (a, w) <- _runWriterC m tw w' <- liftIO $ readTVarIO w return (a, w') -- | Unwrap a concurrent Writer monad computation as a function, discarding -- the return value. -- -- * @'execWriterC' m w = 'liftM' 'snd' ('runWriterC' m w)@ execWriterC :: MonadIO m => WriterC w m a -- ^ computation to execute -> TVar w -- ^ output channel -> m w -- ^ collected output execWriterC m tw = liftM snd $ runWriterC m tw -- | Map both the return value and output of a computation using the given -- function. mapWriterC :: (m (a, TVar w) -> n (b, TVar w)) -> WriterC w m a -> WriterC w n b mapWriterC f m = WriterC $ \w -> f (_runWriterC m w) -- | Lift a @callCC@ operation to the new monad. liftCallCC :: ((((a, TVar w) -> m (b, TVar w)) -> m (a, TVar w)) -> m (a, TVar w)) -> ((a -> WriterC w m b) -> WriterC w m a) -> WriterC w m a liftCallCC callCC f = WriterC $ \w -> callCC $ \c -> _runWriterC (f (\a -> WriterC $ \_ -> c (a, w))) w -- | Lift a @catchError@ operation to the new monad. liftCatch :: (m (a, TVar w) -> (e -> m (a, TVar w)) -> m (a, TVar w)) -> WriterC w m a -> (e -> WriterC w m a) -> WriterC w m a liftCatch catchError m h = WriterC $ \w -> _runWriterC m w `catchError` \e -> _runWriterC (h e) w -- | Run multiple Writer operations on the same value, returning the -- resultant output and the value produced by each operation. runWritersC :: (MonadFork m, Monoid w) => [WriterC w m a] -- ^ writer computations to execute -> m ([a], w) -- ^ return values and output runWritersC ms = do output <- liftIO $ newTVarIO mempty mvs <- mapM (const (liftIO newEmptyMVar)) ms forM_ (zip mvs ms) $ \(mv, operation) -> fork $ do (res, _) <- runWriterC operation output liftIO $ putMVar mv res items <- forM mvs (liftIO . takeMVar) out <- liftIO $ readTVarIO output return (items, out) -- | Run multiple Writer operations on the same value, returning the -- resultant output. execWritersC :: (MonadFork m, Monoid w) => [WriterC w m a] -- ^ writer computations to execute -> m w -- ^ output execWritersC = liftM snd . runWritersC