The MArray class provides generic functions for working with mutable arrays of various sorts in both ST and IO contexts. I haven't been able to find a similar class for working with both STRefs and IORefs. Does such a thing exist?
ref-fd package provides it:
class Monad m => MonadRef r m | m -> r where [...]
or with type families,
class Monad m => MonadRef m where type Ref m :: * -> * [...]
Another answer suggested the monad-statevar package which doesn't have the functional dependency. It also has separate
HasPut members and no abstraction over the
Aside from the different methods in each, the functional dependency is a design trade-off. Consider the following two simplified classes:
class MRef1 r m where newRef1 :: a -> m (r a) readRef1 :: r a -> m a class MRef2 r m | m -> r where newRef2 :: a -> m (r a) readRef2 :: r a -> m a
MRef1, both the monad type and the reference type can vary freely, so the following code has a type error:
useMRef1 :: ST s Int useMRef1 = do r <- newRef1 5 readRef1 r No instance for (MRef1 r0 (ST s)) arising from a use of `newRef1' The type variable `r0' is ambiguous
We have to add an extra type signature somewhere to say that we want to use
In contrast, the same code works fine for
MRef2 without any extra signature. The signature on the definition saying that the whole code has type
ST s Int, combined with the functional dependency
m -> r means that there is only one
r type for a given
m type and so the compiler knows that our existing instance is the only possible one and we must want to use
On the flip side, suppose we want to make a new kind of reference, e.g.
STRefHistory that tracks all the values that have ever been stored in it:
newtype STRefHistory s a = STRefHistory (STRef s [a])
MRef1 instance is fine because we are allowed multiple reference types for the same monad type:
instance MRef1 (STRefHistory s) (ST s) where newRef1 a = STRefHistory <$> newSTRef [a] readRef1 (STRefHistory r) = head <$> readSTRef r
but the equivalent
MRef2 instance fails with:
Functional dependencies conflict between instance declarations: instance MRef2 (STRef s) (ST s) -- Defined at mref.hs:28:10 instance MRef2 (STRefHistory s) (ST s) -- Defined at mref.hs:43:10
I also mentioned the type family version, which is quite similar in expressive power to the functional dependency; the reference type is a "type function" of the monad type so there can again only be one per monad. The syntax ends up being a bit different and in particular you can just say
MonadRef m in constraints, without stating what the reference type is within the constraint.
It's also plausible to have the reversed functional dependency:
class MRef2 r m | r -> m where
so that each reference type can live in just one monad, but you can still have several reference types for a monad. Then you'd need type signatures on your references but not on your monadic computations as a whole.
Control.Monad.StateVar has a typeclass which lets you
put IORefs and STRefs identically.