    {-# 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 sr
    ref t -> (a, b)
    f = do
        x <- sr -> m t
    forall sr (m :: * -> *) a. ReadRef sr m a => sr -> m a
    readReference sr
    ref
        let (x', b) = f x
        writeReference ref x'
        return b

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