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 the MyTypeRef that we wrote earlier. It leverages the power of GHC generics and typeclasses. Every field of type X turns into a field of type Ref s X. This "does the right thing" as long as all your fields are instances of Mutable.
  • The mechanisms in DefaultMutable will automatically fill in the rest of the typeclass for you.