-- | -- Module : Data.PVar -- Copyright : (c) 2009 Gregory Crosswhite -- License : BSD3 -- -- Maintainer : Gregory Crosswhite -- Stability : experimental -- Portability : ghc -- -- Procrastinating variables ('PVar's) are meant to be used in cases where -- you want to give someone a value that you do not have available yet, -- but will definitely have ready by the time that they need to use it. -- -- 'PVar's have the advantage that you do not make the user of your value -- execute some kind of unwrapping routine in order to get access to the -- value within. For example, this is useful when you are constructing -- closures that you want to go ahead and construct now even though some -- of the values that they contain are not available yet. -- -- 'PVar's are implemented with a lazy thunk that reads from an -- 'IORef'; before the 'IORef' is written to, it contains @_|_@ ( in -- the form of an exception with a descriptive error message) so that -- an error is raised in the user code if the variable is accidently -- accessed before the value is ready. -- -- NOTE: 'PVar's are modeled closely on the 'IVar' implementation in the -- ivar-simple package. The major difference is that if you try -- to read an 'IVar' before it has been given a value, it blocks -- until the value is available, whereas reading from a 'PVar' -- before it is ready raises an exception. The reason behind the -- different symantics for 'PVar' is because if the user accidently -- accesses the value too early, you want there to be a lot of -- noise to let him or her know about it, rather than merely -- blocking the thread indefinitely and causing them to wonder -- what went wrong. {-# LANGUAGE DeriveDataTypeable #-} module Data.PVar ( -- * PVar creation PVar , newPVar , newPVarWithCustomMessage -- * Writing to PVars , writePVar -- * Probing PVars , tryReadPVar , tryWritePVar -- * Errors , AccessedTooEarly , AlreadyHasAValue ) where import Control.Concurrent.MVar import Control.Exception import Control.Monad import Data.IORef import Data.Typeable import System.IO.Unsafe -- |A procrastinating variable ('PVar' for short). data PVar a = PVar !(MVar ()) !(IORef a) -- |The exception raised when a 'PVar' is accessed before it is ready. data AccessedTooEarly = AccessedTooEarly String deriving (Show, Typeable) instance Exception AccessedTooEarly -- |The exception raised by 'writePVar' when one attempts to write to a 'PVar' -- after it has already been given a value. data AlreadyHasAValue = AlreadyHasAValue deriving (Show, Typeable) instance Exception AlreadyHasAValue -- |Creates a new, empty 'PVar', and returns both a reference you can -- use to fill the value later as well as a lazy thunk which you can -- treat as a normal variable; the latter evaluates to the value -- stored in the reference if it is available and to bottom otherwise. newPVar :: IO (PVar a, a) newPVar = newPVarWithCustomMessage "This procrastinating variable was accessed before it was ready." -- |Creates a new, empty 'PVar' that raises an exception with a custom -- message. (Use this if you want to make explicit to the user of this -- variable exactly when they should expect its value to become -- available.) newPVarWithCustomMessage :: String -> IO (PVar a, a) newPVarWithCustomMessage message = do lock <- newMVar () ref <- newIORef . throw . AccessedTooEarly $ message let {-# NOINLINE value #-} value = unsafePerformIO $ readIORef ref return (PVar lock ref,value) -- |Tries to read a 'PVar', but does not throw an exception if -- the value is not ready yet; instead, if the value is ready it -- returns @Just value@ and otherwise it returns @Nothing@. tryReadPVar :: PVar a -> IO (Maybe a) tryReadPVar (PVar lock ref) = block $ do is_empty <- isEmptyMVar lock if is_empty then readIORef ref >>= return . Just else return Nothing -- |Writes a value to a 'PVar'. Raises an 'AlreadyHasAValue' exception -- if the 'PVar' already has a value. writePVar :: PVar a -> a -> IO () writePVar pvar value = do result <- tryWritePVar pvar value unless result $ throwIO AlreadyHasAValue -- |Attempts to a value to a 'PVar', but instead of throwing an exception -- it returns 'True' if it was successful and 'False' otherwise. tryWritePVar :: PVar a -> a -> IO Bool tryWritePVar (PVar lock ref) value = block $ do a <- tryTakeMVar lock case a of Just _ -> writeIORef ref value >> return True Nothing -> return False