marvin-0.2.1: src/Marvin/Util/Mutable.hs
{-|
Module : $Header$
Description : Mutable references for marvin.
Copyright : (c) Justus Adam, 2016
License : BSD3
Maintainer : dev@justus.science
Stability : experimental
Portability : POSIX
-}
module Marvin.Util.Mutable where
import Control.Concurrent.MVar
import Control.Monad.IO.Class
import Data.IORef
-- | A mutable reference to a value of type @v@
--
-- This is like a pointer in c, the value behind which can be mutated by several functions.
-- So long as they retain this reference they are able to retrieve the updated value.
type Mutable v = IORef v
-- | Create a new mutable reference of type @v@ from an initial value.
newMutable :: MonadIO m => a -> m (Mutable a)
newMutable = liftIO . newIORef
-- | Retrieve the value behind by a mutable reference.
readMutable :: MonadIO m => Mutable a -> m a
readMutable = liftIO . readIORef
-- | Set the value inside a mutable reference.
writeMutable :: MonadIO m => Mutable a -> a -> m ()
writeMutable m = liftIO . writeIORef m
-- | Change the value behind a mutable reference.
modifyMutable :: MonadIO m => Mutable a -> (a -> a) -> m ()
modifyMutable m = liftIO . modifyIORef m
-- | A value that can be shared on multiple concurrent Threads.
--
-- This value works like a channel. It can either be empty or full.
-- If it is empty 'writeSynchronized' fills it, otherwise the write blocks.
-- If it is full 'takeSynchronized' empties, otherwise it blocks until it is filled.
-- 'readSynchronized' does not empty it and also blocks if the 'Synchronized' is empty.
--
-- Should you just use it as a thread safe mutable variable, mutations typically follow the pattern:
--
-- @
-- val <- takeSynchronized -- obtain the value and leave it empty to block concurrent reads
-- mod <- modifyWithIO val -- modify the value, typically involves IO
-- writeSynchronized mod -- write back the result
-- @
--
-- Another use for this type is as a message channel, where we have a producer and a consumer,
-- the producer tries to write values into the 'Synchronized' ('writeSynchronized') and the consumer
-- waits for the 'Synchronized' to be filled and takes the value ('takeSynchronized') for procesing.
--
-- It works generally best if any 'Synchronized' is only used for one of these two applications at the same time.
--
-- This type is the same as 'MVar', only renamed for readability. If you want a more in depth documentation, see the documentation for 'MVar'.
type Synchronized = MVar
-- | Read the vaue, but don't empty the 'Synchronized'. Blocks if it is empty.
readSynchronized :: MonadIO m => Synchronized a -> m a
readSynchronized = liftIO . readMVar
-- | Non blocking version of 'readSynchronized'. Returns 'Nothing' if it was empty.
tryReadSynchronized :: MonadIO m => Synchronized a -> m (Maybe a)
tryReadSynchronized = liftIO . tryReadMVar
-- | Read the value and empty the 'Synchronized', blocks if already empty.
takeSynchronized :: MonadIO m => Synchronized a -> m a
takeSynchronized = liftIO . takeMVar
-- | Non blocking version of 'takeSynchronized', returns 'Nothing' if it was empty.
tryTakeSynchronized :: MonadIO m => Synchronized a -> m (Maybe a)
tryTakeSynchronized = liftIO . tryTakeMVar
-- | Fills the empty 'Synchronized' with a value, blocks if it is full.
writeSynchronized :: MonadIO m => Synchronized a -> a -> m ()
writeSynchronized mv = liftIO . putMVar mv
-- | Non blocking version of 'writeSynchronized'. Returns 'False' if it was full.
tryWriteSynchronized :: MonadIO m => Synchronized a -> a -> m Bool
tryWriteSynchronized mv = liftIO . tryPutMVar mv
-- | Query if the 'Synchronized' is empty or full.
isEmptySynchronized :: MonadIO m => Synchronized a -> m Bool
isEmptySynchronized = liftIO . isEmptyMVar