Automatic Instance Options
As previously seen, any type with a Generic
instance can be given an instance
automatically. However, this might not always be the behavior you want for
your values. This library offers a few alternative automatic behaviors for
what you want your mutable value to be like. Of course, you can always just
define all your semantics and data types by hand (like what was done in
MyTypeRef
in the previous section).
Picking an automatic derived behavior is as easy as specifying what the Ref
instance is:
instance Mutable s MyType where
type Ref s MyType = ....
If you set the Ref
to a known "auto-derivable" type, then the library will
automatically infer what you want. Here are the options.
Whole-wise Mutation
You don't want any piecewise mutation. Treat your object as an inseparable block, and any mutations are done over the entire data type.
This is the default behavior --- it is mostly useful for "primitive",
non-composite data types like Int
:
data WholeType = WT { wtInt :: Int, wtDouble :: Double }
instance Mutable s WholeType
If you just leave the instance blank, this will be the automatic default behavior. You can also be explicit:
instance Mutable s WholeType where
type Ref s WholeType = MutVar s WholeType
and that would do the same thing.
Generic Instance
This is the main thing the library is useful for. Get an automatic
"piecewise-mutable" form of any ADT with a Generic
instance.
Dispatch this behavior by using GRef s X
as your type's Ref
:
data MyType = MT
{ mtInt :: Int
, mtDouble :: Double
, mtVec :: V.Vector Double
}
deriving Generic
instance Mutable s MyType where
type Ref s MyType = GRef s MyType
The data type GRef s MyType
is essentially equivalent to the same type as
MyType
with all the fields replaced with their mutable versions. That is,
GRef s MyType
is equivalent to MyTypeRef
, if we wanted to define it
manually:
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
The above snippet is the equivalent code to what is generated in the simple line
instance Mutable s MyType where
type Ref s MyType = GRef s MyType
The semantics for mutability is that a record type essentially becomes a record of mutable values, which can all be updated independently.
Updating each part independently
For GRef
, you can update each part independently by using features from
FieldMut
and PosMut
. See Getting Started for a
summary on how to use these.
Sum Types
GRef
also works for sum types, as well. For sum types, an extra layer of
indirection is added: at the top level is a MutVar
containing a reference to
the contents of a constructor. For example:
data IntOrBool = IBInt Int
| IBBool Bool
deriving Generic
instance Mutable s IntOrBool where
type Ref s IntOrBool = GRef s IntOrBool
then we get to "access" each potential branch with constrMB
:
ibInt :: MutBranch s IntOrBool Int
ibInt = constrMB #_IBInt
ibBool :: MutBranch s IntOrBool Bool
ibBool = constrMB #_IBBool
The combinators in the Data.Mutable.Branches module are intended for usage with mutable sum types like this. See the mutable branches module for more information, and an actual useful example --- mutable linked lists.
Newtyped Instances
If you have a newtype, you can give it a Mutable
instance based on the
underlying type by using CoerceRef
newtype VecD = VecD (V.Vector Double)
instance Mutable s VecD where
type Ref s VecD = CoerceRef s VecD (V.Vector Double)
This will appropriately have VecD
be using MVector
as its mutable version.
To get an instance for a newtype X
wrapping underlying type Y
using the
Mutable
instance for Y
, use CoerceRef s X Y
.
You can access the underlying Ref
using coerceRef
or withCoerceRef
:
withCoerceRef
:: Ref s VecD
-> (MV.Vector s Double -> m r)
-> m r
freezePart coerceRef
:: Ref s VecD
-> m (V.Vector Double)
Traversable Instances
Any "fixed-length" Traversable
instance can be used as a mutable reference by
just swapping out all its leaves for Ref
. You can use TraverseRef
:
data V4 a = V4 a a a a
deriving (Functor, Foldable, Traversable)
instance Mutable s a => Mutable s (V4 a) where
type Ref s (V4 a) = TraverseRef s V4 a
Basically, this just uses V4 (Ref s a)
as your mutable reference:
getTraverseRef
:: Ref s (V4 a)
-> V4 (Ref s a)
so you can directly access the parts by just accessing your Traversable
instance normally --- no need for any fancy MutPart
shenanigans.
Note that this still technically works for a non-fixed-length Traversable
instance (like lists and vectors), but copy
semantics can get a bit wonky.
See the documentation for more details.
Higher-Kinded Data
Sandy Maguire's Higher-Kinded Data pattern is seriously one of my
favorite things ever in Haskell, and it works nicely with Mutable
as well.
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)
In this style, MyType'
behaves exactly like MyType
from above:
MTF 3 4.5 (V.fromList [1..100])
:: MyType'
But now, MyTypeF (RefFor s)
literally has mutable references as its fields.
You can pattern match to get rI :: MutVar s Int
, rD :: MutVar s Double
, and
rV :: MVector s Double
MTF rI rD rV :: MyTypeF (RefFor s)
and the accessors work as well:
mtfVec
:: (PrimState m ~ s)
-> MyTypeF (RefFor s)
-> MVector s Double
You can use it like:
doStuff :: MyType' -> MyType'
doStuff x = runST $ do
r@(MTF rI rD rV) <- thawRef x
replicateM_ 1000 $ do
-- rI is just the 'Int' ref
modifyMutVar rI (+ 1)
-- rV is the 'MVector'
MV.modify rV (+1) 0
freezeRef r
doStuff $ MTF 0 19.3 (V.fromList [1..12]) =>
MTF {mtfInt = 1000, mtfDouble = 19.3, mtfVec = [1001.0,2.0,3.0,4.0,5.0,6.0,7.0,8.0,9.0,10.0,11.0,12.0]}
This makes it all really syntactically easy to access the internal parts
directly as Ref
s.