Mutable Parts

The Data.Mutable.Parts module has some mechanisms for accessing specific parts of mutable references, to take full advantage of piecewise-mutability.

The main data type is MutPart:

data MutPart s b a = MutPart { getMutPart :: Ref s b -> Ref s a }

You can sort of imagine MutPart s b a as spiritually similar to a Lens' b a: it's a way to access and modify an a "inside" some b. It allows you to access or modify an a part of the b, without touching the rest of the b.

Usage

Once you have a MutPart, you can use it with some simple utilities:

-- | With a 'MutPart', read out a specific part of a 'Ref'.
freezePart :: Mutable s a => MutPart s b a -> Ref s b -> m a

-- | With a 'MutPart', overwrite into a specific part of a 'Ref'.
copyPart :: Mutable s a => MutPart s b a -> Ref s b -> a -> m ()

-- | With a 'MutPart', modify a specific part of a 'Ref' with a pure
-- function.
modifyPart :: Mutable s a => MutPart s b a -> Ref s b -> (a -> a) -> m ()

freezePart, copyPart, and modifyPart act like "focused" versions of freezeRef, copyRef, and modifyRef. There's also a continuation-like combinator to work directly with the smaller sub-reference:

-- | Using a 'MutPart', perform a function on a `Ref s b` as if you had
-- a `Ref s a`.
withPart
    :: MutPart s b a        -- ^ How to zoom into an `a` from an `s`
    -> Ref s b              -- ^ The larger reference of `s`
    -> (Ref s a -> m r)     -- ^ What do do with the smaller sub-reference of `a`
    -> m r

MutParts also have a Category instance, so you can compose them with . from Control.Category.

Examples

The rest of the module offers different useful MutParts to be used in different situations.

For example, with our favorite example type:

data MyType = MT
    { mtInt    :: Int
    , mtDouble :: Double
    , mtVec    :: V.Vector Double
    }
  deriving (Show, Generic)

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

We are able to access each field:

fieldMut #mtInt    :: MutPart s MyType Int
fieldMut #mtDouble :: MutPart s MyType Double
fieldMut #mtVec    :: MutPart s MyType (V.Vector Double)

and also each position:

posMut @1 :: MutPart s MyType Int
posMut @2 :: MutPart s MyType Double
posMut @3 :: MutPart s MyType (V.Vector Double)

We can also get a MutPart into a view of your data type as a tuple:

tupleMut :: MutPart s MyType (Int, Double, V.Vector Double)

Because the instance of Ref for tuples, this just turns a Ref s MyType into a (MutVar s Int, MutVar s Double, MVector s Double). This is arguably easier to use continuation-style, so there is a nice helper withTuple = withPart tupleMut

withTuple
    :: (PrimMonad m, PrimState m ~ s)
    => MutPart s MyType
    -> ((MutVar s Int, MutVar s Double, MVector s Double) -> m r)
    -> m r

Another way of generating MutParts for your record types is if you are using Sandy Maguire's Higher-Kinded Data pattern (like mentioned in Automatic Instance Options), you can use hkdMutParts:

data MyTypeF f = MTF
    { mtfInt    :: HKD f Int
    , mtfDouble :: HKD f Double
    , mtfVec    :: HKD f (V.Vector Double)
    }
  deriving Generic

type MyType' = MyTypeF Identity

instance Mutable s MyType' where
    type Ref s MyType' = MyTypeF (RefFor s)
MTF mpInt mpDouble mpVec = hkdMutParts @MyTypeF

That will give you mpInt :: MutPart s MyType Int, mpDouble :: MutPart s MyType Double, and mpVec :: MutPart s MyType (V.Vector Double), in a way that is nice to pattern match out of. You can also access the MutParts:

mpInt :: MutPart s MyType Int
mpInt = mtfInt (hkdMutParts @MyTypeF)