Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a typeclass for references similar to the MArray class for mutable arrays?

The MArray class provides generic functions for working with mutable arrays of various sorts in both ST and IO contexts. I haven't been able to find a similar class for working with both STRefs and IORefs. Does such a thing exist?

like image 889
dfeuer Avatar asked Dec 20 '22 19:12

dfeuer


2 Answers

The ref-fd package provides it:

class Monad m => MonadRef r m | m -> r where
    [...]

or with type families, ref-tf:

class Monad m => MonadRef m where
    type Ref m :: * -> *
    [...]

Another answer suggested the monad-statevar package which doesn't have the functional dependency. It also has separate HasGet and HasPut members and no abstraction over the newRef functionality.

Aside from the different methods in each, the functional dependency is a design trade-off. Consider the following two simplified classes:

class MRef1 r m where
   newRef1 :: a -> m (r a)
   readRef1 :: r a -> m a

class MRef2 r m | m -> r where
   newRef2 :: a -> m (r a)
   readRef2 :: r a -> m a

With MRef1, both the monad type and the reference type can vary freely, so the following code has a type error:

useMRef1 :: ST s Int
useMRef1 = do
   r <- newRef1 5
   readRef1 r

No instance for (MRef1 r0 (ST s)) arising from a use of `newRef1'
The type variable `r0' is ambiguous

We have to add an extra type signature somewhere to say that we want to use STRef.

In contrast, the same code works fine for MRef2 without any extra signature. The signature on the definition saying that the whole code has type ST s Int, combined with the functional dependency m -> r means that there is only one r type for a given m type and so the compiler knows that our existing instance is the only possible one and we must want to use STRef.

On the flip side, suppose we want to make a new kind of reference, e.g. STRefHistory that tracks all the values that have ever been stored in it:

newtype STRefHistory s a = STRefHistory (STRef s [a])

The MRef1 instance is fine because we are allowed multiple reference types for the same monad type:

instance MRef1 (STRefHistory s) (ST s) where
   newRef1 a = STRefHistory <$> newSTRef [a]
   readRef1 (STRefHistory r) = head <$> readSTRef r

but the equivalent MRef2 instance fails with:

Functional dependencies conflict between instance declarations:
  instance MRef2 (STRef s) (ST s) -- Defined at mref.hs:28:10
  instance MRef2 (STRefHistory s) (ST s) -- Defined at mref.hs:43:10

I also mentioned the type family version, which is quite similar in expressive power to the functional dependency; the reference type is a "type function" of the monad type so there can again only be one per monad. The syntax ends up being a bit different and in particular you can just say MonadRef m in constraints, without stating what the reference type is within the constraint.

It's also plausible to have the reversed functional dependency:

class MRef2 r m | r -> m where

so that each reference type can live in just one monad, but you can still have several reference types for a monad. Then you'd need type signatures on your references but not on your monadic computations as a whole.

like image 132
GS - Apologise to Monica Avatar answered May 10 '23 22:05

GS - Apologise to Monica


Control.Monad.StateVar has a typeclass which lets you get and put IORefs and STRefs identically.

like image 29
r3m0t Avatar answered May 10 '23 22:05

r3m0t