Mutable and Ref
Let's go over the high level view of what's going on. Conceptually, the entire
library revolves around the Mutable typeclass and the Ref associated type.
class Mutable s a where
type Ref s a = v | v -> a s
thawRef :: (PrimMonad m, PrimState m ~ s) => a -> m (Ref s a)
freezeRef :: (PrimMonad m, PrimState m ~ s) => Ref s a -> m a
copyRef :: (PrimMonad m, PrimState m ~ s) => Ref s a -> a -> m ()
-- ... plus some more methods that can be implemented using
-- the others in most cases
An instance of Mutable s a is an a that has a "mutable version" that can be
updated/mutated in a "mutable PrimMonad m" (like IO or ST) that has a
state token s.
The (injective) type family Ref associates every type a with its "mutable
version".
(A quick note on PrimMonad --- it comes from the primitive library and
is used across the ecosystem; it's a typeclass that abstracts over all "impure"
monads like IO, ST s, ReaderT r IO, etc. You can think of it as an
expanded version of MonadIO to also include monads that use ST s.
PrimState is what you give to MutVar and MVector to make things "work
properly")
For example, for Vector, the "mutable version" is an MVector:
class Mutable s (Vector a) where
type Ref s (Vector a) = MVector s a
thawRef = V.thaw
freezeRef = V.freeze
copyRef = V.copy
For simple non-composite data types like Int, you can just use a
MutVar (a polymorphic version of IORef/STRef):
class Mutable s Int where
type Ref s Int = MutVar s Int
thawRef = newMutVar
freezeRef = readMutVar
copyRef = writeMutVar
class Mutable s Double where
type Ref s Int = MutVar s Double
thawRef = newMutVar
freezeRef = readMutVar
copyRef = writeMutVar
All we are doing so far is associating a type with its "mutable" version. But, what happens if we had some composite type?
data MyType = MT
{ mtInt :: Int
, mtDouble :: Double
, mtVec :: V.Vector Double
}
deriving (Show, Generic)
We might imagine making a piecewise-mutable version of it, where each field is its own mutable reference:
data MyTypeRef s = MTR
{ mtrInt :: MutVar s Int
, mtrDouble :: MutVar s Double
, mtrVec :: MV.MVector s Double
}
instance Mutable s MyType where
type Ref s MyType = MyTypeRef s
thawRef (MT x y z) = MTR <$> newMutVar x
<*> newMutVar y
<*> V.thaw z
freezeRef (MTR x y z) = MT <$> readMutVar x
<*> readMutVar y
<*> V.freeze z
copyRef (MTR a b c) (MT x y z) = do
writeMutVar a x
writeMutVar b y
V.copy c z
But, this is pretty tedious to write for every single data type we have. What if we could instead automatically derive a reference type?
Well, we're in luck. If MyType is an instance of Generic, then we can just
write:
instance Mutable s MyType where
type Ref MyType = GRef s MyType
We can now leave the rest of the typeclass body blank...and the mutable library will do the rest for us!
GRef s MyTypeis an automatically derived type that is equivalent to theMyTypeRefthat we wrote earlier. It leverages the power of GHC generics and typeclasses. Every field of typeXturns into a field of typeRef s X. This "does the right thing" as long as all your fields are instances ofMutable.- The mechanisms in
DefaultMutablewill automatically fill in the rest of the typeclass for you.