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 MyType
is an automatically derived type that is equivalent to theMyTypeRef
that we wrote earlier. It leverages the power of GHC generics and typeclasses. Every field of typeX
turns into a field of typeRef s X
. This "does the right thing" as long as all your fields are instances ofMutable
.- The mechanisms in
DefaultMutable
will automatically fill in the rest of the typeclass for you.