{-
 -      ``Data/StateRef/Classes''
 -      (c) 2008 Cook, J. MR  SSD, Inc.
 -}
{-# LANGUAGE 
    MultiParamTypeClasses,
    FunctionalDependencies
  #-}

module Data.StateRef.Classes where

class Monad m => NewRef sr m a | sr -> a where
        -- |Construct a new mutable reference containing the provided value.
        newRef :: a -> m sr

class Monad m => WriteRef sr m a | sr -> a where
        -- |Replace the existing value of the given reference with the provided value.
        writeRef :: sr -> a -> m ()

class Monad m => ReadRef sr m a | sr -> a where
        -- |Get the current value referenced by the given state reference.
        readRef :: sr -> m a

-- consider whether there needs to be something along the lines of
-- 'atomicModifyIORef' and/or 'modifyMVar' (specifically, what is the
-- motivation for the signature of atomicModifyIORef?  and is there a
-- downside to providing a signature like that to modifyRef?)

class (Monad m, ReadRef sr m a, WriteRef sr m a) => ModifyRef sr m a | sr -> a where
        -- |Atomically modify the contents of a reference.  This is
        -- implemented in a separate class (rather than a function with
        -- context (ReadRef sr m a, WriteRef sr m a)) because in most
        -- cases the default implementation cannot act atomically.
        atomicModifyRef :: sr -> (a -> (a,b)) -> m b
        atomicModifyRef ref f = do
                x <- readRef ref
                let (x', b) = f x
                writeRef ref x'
                return b
        
        -- |Same thing, but don't thread out the extra return.  Could perhaps
        -- be implemented slightly more efficiently than 'atomicModifyRef' in many cases.
        -- Note that implementations are expected to be atomic, if at all possible,
        -- but not strictly required to be.
        modifyRef :: sr -> (a -> a) -> m ()
        modifyRef ref f = do
                x <- readRef ref
                let x' = f x
                writeRef ref x'
                return ()

-- |The 'DefaultStateRef' and 'Data.MRef.Classes.DefaultMRef' are used to 
-- internally constrain types that do not escape an expression, so that the 
-- compiler may choose an instance for the reference type (which it otherwise
-- would not, and maybe not even tell you until you tried to use your
-- function).  For an example, see the source for 'Data.StateRef.newCounter'.
-- See also 'Data.MRef.Classes.DefaultMRef'.
-- 
-- The sole purpose for these classes' existence is as a carrier for an
-- altered set of functional dependencies, which constrain the reference
-- type to be uniquely determined by the monad and the contained type.
class Monad m => DefaultStateRef sr m a | sr -> a, m a -> sr

--
-- in the absence of type families, it'd be nice to be able to say 
-- something like:
--
-- type StateRef m a = 
--         ( DefaultStateRef sr m a
--         , ReadRef sr m a
--         , WriteRef sr m a
--         ) => sr
--
-- this would ease the transition to type families later, assuming
-- they catch on.
--