{-# LANGUAGE 
    MultiParamTypeClasses,
    FunctionalDependencies,
    GADTs
  #-}

module Data.StateRef.Types where

-- |A simple reference type, hiding the complexity of all these type classes,
-- since most of the time the only thing you care about is that you want a reference.
-- The full complexity is still there, though, so FFI types or other reference-like
-- things can still be made into 'Ref's.
data Ref m a where
    Ref :: ModifyRef sr m a => !sr -> Ref m a

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

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

class (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.
    atomicModifyReference :: sr -> (a -> (a,b)) -> m b
    
    -- |Same thing, but don't thread out the extra return.  Could perhaps
    -- be implemented slightly more efficiently than 'atomicModifyReference' in many cases.
    -- Note that implementations are expected to be atomic, if at all possible,
    -- but not strictly required to be.
    modifyReference :: sr -> (a -> a) -> m ()

-- |Default implementation of atomicModifyReference in terms of readReference and writeReference
defaultAtomicModifyReference :: sr -> (t -> (a, b)) -> m b
defaultAtomicModifyReference ref :: sr
ref f :: t -> (a, b)
f = do
    t
x <- sr -> m t
forall sr (m :: * -> *) a. ReadRef sr m a => sr -> m a
readReference sr
ref
    let (x' :: a
x', b :: b
b) = t -> (a, b)
f t
x
    sr -> a -> m ()
forall sr (m :: * -> *) a. WriteRef sr m a => sr -> a -> m ()
writeReference sr
ref a
x'
    b -> m b
forall (m :: * -> *) a. Monad m => a -> m a
return b
b

-- |Default implementation of modifyReference in terms of readReference and writeReference
defaultModifyReference :: sr -> (t -> a) -> m ()
defaultModifyReference ref :: sr
ref f :: t -> a
f = do
    t
x <- sr -> m t
forall sr (m :: * -> *) a. ReadRef sr m a => sr -> m a
readReference sr
ref
    let x' :: a
x' = t -> a
f t
x
    sr -> a -> m ()
forall sr (m :: * -> *) a. WriteRef sr m a => sr -> a -> m ()
writeReference sr
ref a
x'
    () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()

class NewRef sr m a | sr -> a where
    -- |Construct a new reference to the provided value.
    newReference :: a -> m sr

class HasRef m where
    -- |Construct a new mutable reference (of an unspecified implementation type) containing the provided value.
    newRef :: a -> m (Ref m a)