mutable-0.1.0.0: src/Data/Mutable/Class.hs
{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE DeriveFoldable #-}
{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DeriveTraversable #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilyDependencies #-}
{-# LANGUAGE TypeInType #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE UndecidableInstances #-}
-- |
-- Module : Data.Mutable.Class
-- Copyright : (c) Justin Le 2020
-- License : BSD3
--
-- Maintainer : justin@jle.im
-- Stability : experimental
-- Portability : non-portable
--
-- Provides the 'Mutable' typeclass and various helpers. See
-- 'Data.Mutable' for the main "entrypoint".
module Data.Mutable.Class (
Mutable(..)
, copyRefWhole, moveRefWhole, cloneRefWhole
, modifyRef, modifyRef'
, updateRef, updateRef'
, modifyRefM, modifyRefM'
, updateRefM, updateRefM'
, RefFor(..)
, DefaultMutable(..)
-- * Providing and overwriting instances
, VarMut(..)
, CoerceMut(..)
, TraverseMut(..)
, Immutable(..)
-- * Changing underlying monad
, reMutable, reMutableConstraint
-- * Util
, MapRef
) where
import Control.Monad
import Control.Monad.Primitive
import Data.Coerce
import Data.Constraint
import Data.Constraint.Unsafe
import Data.Kind
import Data.Mutable.Instances
import Data.Mutable.Internal
import Data.Primitive.MutVar
import Data.Proxy
import Data.Reflection
import GHC.Generics
import qualified Data.Vinyl.XRec as X
-- | Apply a pure function on an immutable value onto a value stored in
-- a mutable reference.
modifyRef :: Mutable m a => Ref m a -> (a -> a) -> m ()
modifyRef v f = copyRef v . f =<< freezeRef v
{-# INLINE modifyRef #-}
-- | 'modifyRef', but forces the result before storing it back in the
-- reference.
modifyRef' :: Mutable m a => Ref m a -> (a -> a) -> m ()
modifyRef' v f = (copyRef v $!) . f =<< freezeRef v
{-# INLINE modifyRef' #-}
-- | Apply a monadic function on an immutable value onto a value stored in
-- a mutable reference. Uses 'copyRef' into the reference after the
-- action is completed.
modifyRefM :: Mutable m a => Ref m a -> (a -> m a) -> m ()
modifyRefM v f = copyRef v =<< f =<< freezeRef v
{-# INLINE modifyRefM #-}
-- | 'modifyRefM', but forces the result before storing it back in the
-- reference.
modifyRefM' :: Mutable m a => Ref m a -> (a -> m a) -> m ()
modifyRefM' v f = (copyRef v $!) =<< f =<< freezeRef v
{-# INLINE modifyRefM' #-}
-- | Apply a pure function on an immutable value onto a value stored in
-- a mutable reference, returning a result value from that function.
updateRef :: Mutable m a => Ref m a -> (a -> (a, b)) -> m b
updateRef v f = do
(x, y) <- f <$> freezeRef v
copyRef v x
return y
-- | 'updateRef', but forces the updated value before storing it back in the
-- reference.
updateRef' :: Mutable m a => Ref m a -> (a -> (a, b)) -> m b
updateRef' v f = do
(x, y) <- f <$> freezeRef v
x `seq` copyRef v x
return y
-- | Apply a monadic function on an immutable value onto a value stored in
-- a mutable reference, returning a result value from that function. Uses
-- 'copyRef' into the reference after the action is completed.
updateRefM :: Mutable m a => Ref m a -> (a -> m (a, b)) -> m b
updateRefM v f = do
(x, y) <- f =<< freezeRef v
copyRef v x
return y
-- | 'updateRefM', but forces the updated value before storing it back in the
-- reference.
updateRefM' :: Mutable m a => Ref m a -> (a -> m (a, b)) -> m b
updateRefM' v f = do
(x, y) <- f =<< freezeRef v
x `seq` copyRef v x
return y
-- | A default implementation of 'copyRef' using 'thawRef' and 'moveRef'.
copyRefWhole
:: Mutable m a
=> Ref m a -- ^ destination to overwrite
-> a -- ^ pure value
-> m ()
copyRefWhole r v = moveRef r =<< thawRef v
{-# INLINE copyRefWhole #-}
-- | A default implementation of 'moveRef' that round-trips through the
-- pure type, using 'freezeRef' and 'copyRef'. It freezes the entire source
-- and then re-copies it into the destination.
moveRefWhole
:: Mutable m a
=> Ref m a -- ^ destination
-> Ref m a -- ^ source
-> m ()
moveRefWhole r v = copyRef r =<< freezeRef v
{-# INLINE moveRefWhole #-}
-- | A default implementation of 'moveRef' that round-trips through the
-- pure type, using 'freezeRef' and 'thawRef'. It freezes the entire
-- source and then re-copies it into the destination.
cloneRefWhole
:: Mutable m a
=> Ref m a
-> m (Ref m a)
cloneRefWhole = thawRef <=< freezeRef
{-# INLINE cloneRefWhole #-}
-- | Newtype wrapper that can provide any type with a 'Mutable' instance,
-- giving it a "non-piecewise" instance. Can be useful for avoiding orphan
-- instances yet still utilizing auto-deriving features, or for overwriting
-- the 'Mutable' instance of other instances.
--
-- For example, let's say you want to auto-derive an instance for your data
-- type:
--
-- @
-- data MyType = MT Int Double OtherType
-- deriving Generic
-- @
--
-- This is possible if all of @MyType@s fields have 'Mutable' instances.
-- However, let's say @OtherType@ comes from an external library that you
-- don't have control over, and so you cannot give it a 'Mutable' instance
-- without incurring an orphan instance.
--
-- One solution is to wrap it in 'VarMut':
--
-- @
-- data MyType = MT Int Double ('VarMut' OtherType)
-- deriving Generic
-- @
--
-- This can then be auto-derived:
--
-- @
-- instance Mutable m MyType where
-- type Ref m MyType = GRef m MyType
-- @
--
-- It can also be used to /override/ a 'Mutable' instance. For example,
-- even if the 'Mutable' instance of @SomeType@ is piecewise-mutable, the
-- 'Mutable' instance of @'VarMut' SomeType@ will be not be piecewise.
--
-- For example, the 'Mutable' instance for 'String' is a mutable linked
-- list, but it might be more efficient to treat it as an atomic value to
-- update all at once. You can use @'VarMut' 'String'@ to get that
-- 'Mutable' instance.
newtype VarMut a = VarMut { getVarMut :: a }
-- | Use a @'VarMut' a@ as if it were an @a@.
instance X.IsoHKD VarMut a where
type HKD VarMut a = a
unHKD = VarMut
toHKD = getVarMut
instance PrimMonad m => Mutable m (VarMut a) where
type Ref m (VarMut a) = MutVar (PrimState m) (VarMut a)
-- | Similar to 'VarMut', this allows you to overwrite the normal 'Mutable'
-- instance for a type to utilize its 'Traversable' instance instead of its
-- normal instance. It's also useful to provide an instance for an
-- externally defined type without incurring orphan instances.
--
-- For example, the instance of @'Mutable' ('TraverseMut' [] a)@ is
-- a normal list of mutable references, instead of a full-on mutable linked
-- list.
newtype TraverseMut f a = TraverseMut { getTraverseMut :: f a }
deriving (Show, Eq, Ord, Generic, Functor, Foldable, Traversable)
-- | Use a @'TraverseMut' f a@ as if it were an @f a@
instance X.IsoHKD (TraverseMut f) a where
type HKD (TraverseMut f) a = f a
unHKD = TraverseMut
toHKD = getTraverseMut
instance (Traversable f, Mutable m a) => Mutable m (TraverseMut f a) where
type Ref m (TraverseMut f a) = TraverseRef m (TraverseMut f) a
-- | Similar to 'VarMut', this allows you to overwrite the normal 'Mutable'
-- instance of a type to utilize a coercible type's 'Mutable' instance
-- instead of its normal instance. It's also useful to provide an instance for
-- an externally defined type without incurring orphan instances.
--
-- For example, if an external library provides
--
-- @
-- newtype DoubleVec = DV (Vector Double)
-- @
--
-- and you want to use it following 'V.Vector's 'Mutable' instance (via
-- 'MV.MVector'), but you don't want to write an orphan instance like
--
-- @
-- instance Mutable m DoubleVec where
-- type 'Ref' m DoubleVec = 'CoerceRef' m DoubleVec (Vector Double)
-- @
--
-- then you can instead use @'CoerceMut' DoubleVec (Vector Double)@ as the
-- data type. This wrapped type /does/ use the inderlying 'Mutable'
-- insatnce for 'V.Vector'.
newtype CoerceMut s a = CoerceMut { getCoerceMut :: s }
-- | Use a @'CoerceMut' s a@ as if it were an @s@
instance X.IsoHKD (CoerceMut s) a where
type HKD (CoerceMut s) a = s
unHKD = CoerceMut
toHKD = getCoerceMut
instance (Mutable m a, Coercible s a) => Mutable m (CoerceMut s a) where
type Ref m (CoerceMut s a) = CoerceRef m (CoerceMut s a) a
-- | Similar to 'VarMut', this allows you to overwrite the normal 'Mutable'
-- instance of a type to make it /immutable/.
--
-- For example, let's say you have a type, with the automatically derived
-- generic instance of 'Mutable':
--
-- @
-- data MyType = MT
-- { mtX :: Int
-- , mtY :: Vector Double
-- , mtZ :: String
-- }
-- deriving Generic
--
-- instance Mutable m MyType where
-- type Ref m MyType = GRef m MyType
-- @
--
-- This basically uses three mutable references: the 'Int', the @'V.Vector'
-- Double@, and the 'String'. However, you might want the 'Mutable'
-- instance of @MyType@ to be /immutable/ 'String' field, and so it cannot
-- be updated at all even when thawed. To do that, you can instead have:
--
-- @
-- data MyType = MT
-- { mtX :: Int
-- , mtY :: Vector Double
-- , mtZ :: 'Immutable' String
-- }
-- deriving Generic
--
-- instance Mutable m MyType where
-- type Ref m MyType = GRef m MyType
-- @
--
-- which has that behavior. The 'Int' and the 'V.Vector' will be mutable
-- within @'Ref' m MyType@, but not the 'String'.
newtype Immutable a = Immutable { getImmutable :: a }
-- | Use an @'Immutable' a@ as if it were an @a@
instance X.IsoHKD Immutable a where
type HKD Immutable a = a
unHKD = Immutable
toHKD = getImmutable
instance Monad m => Mutable m (Immutable a) where
type Ref m (Immutable a) = ImmutableRef (Immutable a)
newtype ReMutable (s :: Type) m a = ReMutable a
newtype ReMutableTrans m n = RMT { runRMT :: forall x. m x -> n x }
instance (Monad n, Mutable m a, Reifies s (ReMutableTrans m n)) => Mutable n (ReMutable s m a) where
type Ref n (ReMutable s m a) = ReMutable s m (Ref m a)
thawRef (ReMutable x) = runRMT rmt $ ReMutable <$> thawRef @m @a x
where
rmt = reflect (Proxy @s)
freezeRef (ReMutable v) = runRMT rmt $ ReMutable <$> freezeRef @m @a v
where
rmt = reflect (Proxy @s)
copyRef (ReMutable x) (ReMutable v) = runRMT rmt $ copyRef @m @a x v
where
rmt = reflect (Proxy @s)
moveRef (ReMutable x) (ReMutable v) = runRMT rmt $ moveRef @m @a x v
where
rmt = reflect (Proxy @s)
cloneRef (ReMutable x) = runRMT rmt $ ReMutable <$> cloneRef @m @a x
where
rmt = reflect (Proxy @s)
unsafeThawRef (ReMutable x) = runRMT rmt $ ReMutable <$> unsafeThawRef @m @a x
where
rmt = reflect (Proxy @s)
unsafeFreezeRef (ReMutable v) = runRMT rmt $ ReMutable <$> unsafeFreezeRef @m @a v
where
rmt = reflect (Proxy @s)
unsafeReMutable :: forall s m n a. Mutable n (ReMutable s m a) :- Mutable n a
unsafeReMutable = unsafeCoerceConstraint
-- | If you can provice a natural transformation from @m@ to @n@, you
-- should be able to use a value as if it had @'Mutable' n a@ if you have
-- @'Mutable' m a@.
reMutable
:: forall m n a r. (Mutable m a, Monad n)
=> (forall x. m x -> n x)
-> (Mutable n a => r)
-> r
reMutable f x = x \\ reMutableConstraint @m @n @a f
-- | If you can provice a natural transformation from @m@ to @n@, then
-- @'Mutable' m a@ should also imply @'Mutable' n a@.
reMutableConstraint
:: forall m n a. (Mutable m a, Monad n)
=> (forall x. m x -> n x)
-> Mutable m a :- Mutable n a
reMutableConstraint f = reify (RMT f) $ \(Proxy :: Proxy s) ->
case unsafeReMutable @s @m @n @a of
Sub Data.Constraint.Dict -> Sub Data.Constraint.Dict