dejafu/Control/Monad/Conc/Class.hs
Michael Walker c0ac24773e Fix comment
2015-01-09 02:29:21 +00:00

93 lines
3.7 KiB
Haskell
Executable File

{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE MultiParamTypeClasses #-}
-- |The monad-conc package provides a class of monads which implement
-- concurrency with (from the perspective of the programmer)
-- nondeterministic interleaving.
--
-- The major difference between this and the Par family of monads (in
-- monad-par) is that results may be implicitly dependent on
-- interleaving, giving rise to data races between threads, whereas
-- Par was explicitly designed to give rise to deterministic
-- concurrency.
module Control.Monad.Conc.Class where
import Control.Concurrent (forkIO)
import Control.Concurrent.MVar (MVar, readMVar, newEmptyMVar, putMVar, tryPutMVar, takeMVar, tryTakeMVar)
import Control.Monad (unless, void)
-- | @ConcFuture@ is the monad-conc alternative of 'ParFuture'. It
-- abstracts Conc monads which support futures. In itself, this is not
-- enough to implement nondeterminism, however the class is provided
-- to remove the 'NFData' constraints imposed by 'ParFuture'.
class Monad m => ConcFuture future m | m -> future where
-- | Create a concurrent computation for the provided action, and
-- return a future which can be used to query the result.
spawn :: m a -> m (future a)
-- | Block until a value is present in the future, and then return
-- it. This does not \"remove\" the value from the future, multiple
-- 'get's are possible, unlike 'takeMVar' for example.
readCVar :: future a -> m a
instance ConcFuture MVar IO where
spawn ma = do
cvar <- newEmptyCVar
fork $ ma >>= putCVar cvar
return cvar
readCVar = readMVar
-- | @ConcCVar@ builds on futures by allowing `CVar`s which threads
-- can read from and write to, possibly multiple times. This is the
-- key difference with the `Par` monads, where it is illegal to write
-- multiple times to the same `IVar`, which removes the possibility of
-- data races.
--
-- A minimal implementation consists of 'fork', 'newEmptyCVar',
-- 'tryPutCVar', and 'tryTakeCVar'. The default implementations of
-- 'takeCVar' and 'putCVar', however, are very inefficient, and should
-- probably always be overridden to make use of
-- implementation-specific blocking functionality.
class ConcFuture cvar m => ConcCVar cvar m | m -> cvar where
-- | Forks a computation to happen concurrently. Communication may
-- happen over `CVar`s.
fork :: m () -> m ()
-- | Creates a new empty `CVar`.
newEmptyCVar :: m (cvar a)
-- | Put a value into a `CVar`. If there is already a value there,
-- this will block until that value has been 'take'n, at which point
-- the value will be stored.
--
-- > putCVar cvar a = tryPutCVar cvar a >>= \b -> unless b $ putCVar cvar a
putCVar :: cvar a -> a -> m ()
putCVar cvar a = tryPutCVar cvar a >>= \b -> unless b $ putCVar cvar a
-- | Attempt to put a value in a `CVar`, returning `True` (and
-- filling the `CVar`) if there was nothing there, otherwise
-- returning `False`
tryPutCVar :: cvar a -> a -> m Bool
-- | Take a value from a `CVar`. This \"empties\" the `CVar`,
-- allowing a new value to be 'put' in. This will block if there is
-- no value in the `CVar` already, until one has been 'put'.
--
-- > takeCVar cvar = tryTakeCVar cvar >>= maybe (takeCVar cvar) return
takeCVar :: cvar a -> m a
takeCVar cvar = tryTakeCVar cvar >>= maybe (takeCVar cvar) return
-- | Attempt to take a value from a `CVar`, returning `Just value`
-- (and emptying the `CVar`) if there was something there, otherwise
-- returning `Nothing`.
tryTakeCVar :: cvar a -> m (Maybe a)
instance ConcCVar MVar IO where
fork = void . forkIO
newEmptyCVar = newEmptyMVar
putCVar = putMVar
tryPutCVar = tryPutMVar
takeCVar = takeMVar
tryTakeCVar = tryTakeMVar