packages feed

mstate (empty) → 0.1

raw patch · 4 files changed

+274/−0 lines, 4 filesdep +basedep +mtlsetup-changed

Dependencies added: base, mtl

Files

+ LICENSE view
@@ -0,0 +1,28 @@+Copyright (c) 2010, Nils Schweinsberg++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++1. Redistributions of source code must retain the above copyright notice, this+   list of conditions and the following disclaimer.++2. Redistributions in binary form must reproduce the above copyright notice,+   this list of conditions and the following disclaimer in the documentation+   and/or other materials provided with the distribution.++3. Neither the name of the author nor the names of its contributors may be used+   to endorse or promote products derived from this software without specific+   prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ Setup.lhs view
@@ -0,0 +1,3 @@+#!/usr/bin/env runhaskell+> import Distribution.Simple+> main = defaultMain
+ mstate.cabal view
@@ -0,0 +1,26 @@+Name:           mstate+Synopsis:       MState: A consistent State monad for concurrent applications.+Description:    MState offers a State monad which can be used in concurrent+                applications. It also manages new threads and waits until the+                whole state monad has been evaluated/executed before it returns+                the state values.++Author:         Nils Schweinsberg+Maintainer:     <mail@n-sch.de>++Version:        0.1+Category:       Concurrent, Monads+License:        BSD3+License-File:   LICENSE+Cabal-Version:  >= 1.6+Build-Type:     Simple++Library+    GHC-Options:        -Wall+    Hs-Source-Dirs:     src+    Build-Depends:+        base    == 4.*,+        mtl     == 1.*++    Exposed-Modules:+        Control.Concurrent.MState
+ src/Control/Concurrent/MState.hs view
@@ -0,0 +1,217 @@+{-# LANGUAGE FlexibleInstances, MultiParamTypeClasses, UndecidableInstances #-}++---------------------------------------------------------------------------+-- |+-- Module      :  Control.Concurrent.MState+-- Copyright   :  (c) Nils Schweinsberg 2010+-- License     :  BSD3-style (see LICENSE)+--+-- Maintainer  :  mail@n-sch.de+-- Stability   :  unstable+-- Portability :  portable+--+-- MState: A consistent State monad for concurrent applications.+--+---------------------------------------------------------------------------++module Control.Concurrent.MState+    ( +      -- * The MState Monad+      MState+    , runMState+    , evalMState+    , execMState+    , mapMState+    , withMState++      -- * Concurrency+    , Forkable (..)+    , forkM++      -- * Example+      -- $example+    ) where++import Control.Monad+import Control.Monad.State.Class+import Control.Monad.Trans++import Control.Concurrent+import Data.IORef++import qualified Control.Exception as E+++-- | The MState is an abstract data definition for a State monad which can be+-- used in concurrent applications. It can be accessed with @evalMState@ and+-- @execMState@. To start a new state thread use @forkM@.+newtype MState t m a = MState { runMState' :: (IORef t, Chan (MVar ())) -> m a }+++-- | The class which is needed to start new threads in the MState monad. Don't+-- confuse this with @forkM@ which should be used to fork new threads!+class (MonadIO m) => Forkable m where+    fork :: m () -> m ThreadId++instance Forkable IO where+    fork = forkIO+++catchMVar :: IO a -> (E.BlockedIndefinitelyOnMVar -> IO a) -> IO a+catchMVar = E.catch+++-- | Read the Chan full of MVars and wait for all MVars to get filled by the+-- threads. On MVar-exception this will skip the current MVar and take the next+-- one (if available).+waitForTermination :: MonadIO m+                   => Chan (MVar ())+                   -> m ()+waitForTermination c = liftIO $ do+    empty <- isEmptyChan c+    catchMVar (unless empty $ do -- Read next threads MVar and wait until it's filled+                                 mv <- readChan c+                                 _  <- takeMVar mv+                                 waitForTermination c)+              (const $ return ())+++-- | Run the MState and return both, the function value and the state value+runMState :: Forkable m+           => MState t m a      -- ^ Action to evaluate+           -> t                 -- ^ Initial state value+           -> m (a,t)+runMState m t = do++    ref <- liftIO $ newIORef t+    c   <- liftIO newChan+    mv  <- liftIO newEmptyMVar++    _  <- runMState' (forkM $ m >>= liftIO . putMVar mv) (ref, c)++    waitForTermination c+    a  <- liftIO $ takeMVar mv+    t' <- liftIO $ readIORef ref+    return (a,t')+++-- | Evaluate the MState monad with the given initial state, throwing away the+-- final state stored in the MVar.+evalMState :: Forkable m+           => MState t m a      -- ^ Action to evaluate+           -> t                 -- ^ Initial state value+           -> m a+evalMState m t = runMState m t >>= return . fst+++-- | Execute the MState monad with a given initial state. Returns the value of+-- the final state.+execMState :: Forkable m+           => MState t m a      -- ^ Action to execute+           -> t                 -- ^ Initial state value+           -> m t+execMState m t = runMState m t >>= return . snd+++-- | Map a stateful computation from one @(return value, state)@ pair to+-- another. See @Control.Monad.State.Lazy.mapState@ for more information.+mapMState :: (MonadIO m, MonadIO n)+          => (m (a,t) -> n (b,t))+          -> MState t m a+          -> MState t n b+mapMState f m = MState $ \s@(r,_) -> do+    ~(b,v') <- f $ do+        a <- runMState' m s+        v <- liftIO $ readIORef r+        return (a,v)+    liftIO $ writeIORef r v'+    return b+++-- | Apply this function to this state and return the resulting state.+withMState :: (MonadIO m)+           => (t -> t)+           -> MState t m a+           -> MState t m a+withMState f m = MState $ \s@(r,_) -> do+    liftIO $ modifyIORef r f+    runMState' m s+++-- | Start a new thread, using @forkIO@. The main process will wait for all+-- child processes to finish.+forkM :: Forkable m+      => MState t m ()         -- ^ State action to be forked+      -> MState t m ThreadId+forkM m = MState $ \s@(_,c) -> do++    -- Add new thread MVar to our waiting channel+    w <- liftIO newEmptyMVar+    liftIO $ writeChan c w+    fork $ runMState' m s >> liftIO (putMVar w ())+++--------------------------------------------------------------------------------+-- Monad instances+--------------------------------------------------------------------------------++instance (Monad m) => Monad (MState t m) where+    return a = MState $ \_ -> return a+    m >>= k  = MState $ \t -> do+        a <- runMState' m t+        runMState' (k a) t+    fail str = MState $ \_ -> fail str++instance (Monad m) => Functor (MState t m) where+    fmap f m = MState $ \t -> do+        a <- runMState' m t+        return (f a)++instance (MonadPlus m) => MonadPlus (MState t m) where+    mzero       = MState $ \_       -> mzero+    m `mplus` n = MState $ \t -> runMState' m t `mplus` runMState' n t++instance (MonadIO m) => MonadState t (MState t m) where+    get     = MState $ \(r,_) -> liftIO $ readIORef r+    put val = MState $ \(r,_) -> liftIO $ writeIORef r val++instance MonadTrans (MState t) where+    lift m = MState $ \_ -> m++instance (MonadIO m) => MonadIO (MState t m) where+    liftIO = lift . liftIO+++{- $example++Example usage:++> import Control.Concurrent+> import Control.Concurrent.MState+> import Control.Monad.State+> +> type MyState a = MState Int IO a+> +> -- Expected state value: 2+> main = print =<< execMState incTwice 0+> +> incTwice :: MyState ()+> incTwice = do+> +>     -- First inc+>     inc+> +>     -- This thread should get killed before it can "inc" our state:+>     kill =<< forkM incDelayed+>     -- This thread should "inc" our state+>     forkM incDelayed+> +>     return ()+> +>   where+>     inc        = get >>= put . (+1)+>     kill       = liftIO . killThread+>     incDelayed = do liftIO $ threadDelay 2000000+>                     inc++-}