Instance Wrappers

The following newtype wrappers can imbue types with an automatic Mutable instance, with different behaviors. They are useful in one of these two situations:

One, if your type comes from an external library, and you still want to use it as a Mutable, these newtype wrappers can be used to treat an external data type as if it had a Mutable instance without actually giving them an orphan Mutable instance.

For example, if an external library offered

newtype VecD = VecD (V.Vector Double)

and you don't want to give VecD an orphan instance, then you can use CoerceMut VecD (V.Vector Double), instead. This has a proper Mutable instance utilizes MVector, as it should.

Two, if you are leveraging GRef to build an automatic mutable version of your data type but want to override the "default" behavior of a component.

For example, let's say you had a composite type:

data MyType = MT
    { mtInt    :: Int
    , mtDoube  :: Double
    , mtString :: String
    }
  deriving Generic

instance Mutable s MyType where
    type Ref s MyType = GRef s MyType

This leverages the Mutable instances of Int, Double, and String. However, the normal Mutable instance for String isn't too great: it uses a mutable linked list (since it's a type alias for [Char]), which is a bit over-kill. We can use the VarMut newtype wrapper to instead treat String as a single object to be modified whole-wise instead of piecewise:

data MyType = MT
    { mtInt    :: Int
    , mtDoube  :: Double
    , mtString :: VarMut String
    }
  deriving Generic

instance Mutable s MyType where
    type Ref s MyType = GRef s MyType

Now Ref s MyType is a composite data type of a MutVar s Int, a MutVar s Double, and MutVar s String. VarMut overrides with a "whole-wise mutation" instance.

VarMut

Overrides with (or provides) "whole-wise" mutation, eliminating any piecewise granularity.

The type VarMut String is a whole-wise mutating reference, and is essentially MutVar s String. The example above shows a situation where this might be useful.

CoerceMut

Overrides with (or provides) a mutation in terms of some equivalent type (usually, a newtype unwrapped version). Usually useful for providing an instance for external types. To repeat the example above:

newtype VecD = VecD (V.Vector Double)

Then CoerceMut VecD (V.Vector Double) has a Mutable instance that uses MVector underneath.

TraverseMut

Overrides with (or provides) a mutation in terms of a type's Traversable instance. See the information on TraverseRef in the previous section for more information on the details for how this instance works.

For example, the Mutable instance for Vector a is an MVector s a, where each item is included in its "pure" form. But wouldn't it be nice if we instead had a mutable Vector a instead be Vector (Ref s a), where every slot contains a mutable value?

You can get that behavior with TraverseMut Vector a.

Immutable

This wrapper is typically used to override the mutation of a specific field when using generic derivation.

For example, looking at the type from above:

data MyType = MT
    { mtInt    :: Int
    , mtDoube  :: Double
    , mtString :: String
    }
  deriving Generic

Let's say you reaaaalllly don't want that mtString field to be mutable. Like, at all. You don't want to allocate anything, and you want all copies into it to be ignored and all freezes to return the original String.

In that case, you can use Immutable:

data MyType = MT
    { mtInt    :: Int
    , mtDoube  :: Double
    , mtString :: Immutable String
    }
  deriving Generic

instance Mutable s MyType where
    type Ref s MyType = GRef s MyType

And now Ref s MyType will basically be a tupling of MutVar s Int, MutVar s Double, and an immutable String. If you try to modify it, modifications will be ignored. Freezing a Ref s MyType will get the original string back.

This does break a lot of the expectations of mutability, but sometimes this can be useful for low-level optimizations or hacks.