Talking with a friend earlier today we decided to make an experiment in declaring generic mutable variables that can be used in IO, ST or State monads. I don’t think this is useful, but it was fun to write. Here is result.
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FlexibleContexts  #-}
{-# LANGUAGE FunctionalDependencies  #-}
import Data.Array.MArray
import Data.Array.IO
import Control.Monad.ST
import Data.STRef
import qualified Data.List.NonEmpty as NE
import Data.List.NonEmpty (NonEmpty)
import qualified Control.Monad.State.Strict as State
import Data.Foldable (forM_)
import Control.Monad (when)
import Test.QuickCheck
import Test.QuickCheck.MonadicLet’s define the abstract interface. v will be the type of variables that can operate on the monad m, holding values of type a. We need three operation: create a variable, read, and write to it.
Notice we had to use FunctionalDependencies to ease type inference. I don’t like this, there is probably a better way.
A utility function to do both read and write passing through a function:
Now we can provide different implementations for variables. First one in IO
We represent the value as an array of a single element. This is obviously overkill, but the goal was also to experiment with the low level array API
instance Var IOVar IO a where
  new = fmap IOVar . newArray ((), ())
  get (IOVar ar) = readArray ar ()
  set (IOVar ar) a = writeArray ar () aThe implementation is straightforward, reading and writing from/to the the array. Creation needs to take care of wrapping the array in the IOVar constructor. Notice that () is a valid index type for arrays, and it makes obvious in the type the fact that the array has a single element.
Providing an implementation in the ST monad is not much harder. Here, we could also use an STArray, but we go directly to STRef for simplicity
newtype STVar s a = STVar (STRef s a)
instance Var (STVar s) (ST s) a where
  new = fmap STVar . newSTRef
  get (STVar ref) = readSTRef ref
  set (STVar ref) a = writeSTRef ref aThe code looks very similar to the IO case.
Finally, let’s try to implement a variable in the State monad. For a variable holding values of type a, it is enough to maintain state a. So we can define
And now to create an instance of Var we can do
instance Var StateVar (State.State a) a where
  new x = State.StateT $ \_ -> return (StateVar x, x)
  get _ = State.get
  set _ = State.putget and set are simple. new requires some care. Initializing the variable means setting the state to a given value, so it can then be read by get. So in new we need it ignore the current state, and set it to x. The types are not enough to ensure correctness, there is a wrong implementation that also compiles:
And that’s it, we have the three types of variables we wanted. Now we can write a stateful looking algorithm, computing the maximum of a list is a good example. The way people do this in non functional languages usually is:
- initialize a variable maxwith the first element of the list
- go through all other elements:
- if the current element is larger than max, updatemaxwith the new value
 
- if the current element is larger than 
- when done iterating the list return max
We can express exactly this algorithm with our variables, even more, we can do it in a way that is generic for every type of Var and every supported Monad
Take a look at the signature: given a non empty list (NonEmpty a), we return its maximum in some monad (Monad m). We can do this as long as a can be ordered (Ord a), and there is some type of variable v which works for the monad m and the type a (Var v m a). The type signature expresses all this pretty well.
myMaximum xs = do
  max <- new (NE.head xs) -- initialize a new var
  forM_ (NE.tail xs) $ \a -> do  -- for each el after the head
    maxSoFar <- get max  -- get the current maximum
    when (a > maxSoFar) $  -- compare with current element
      set max a  -- update if needed
  get maxJust like in the description of the algorithm, we create a variable and update it for every element that is larger than the initial value. When done iterating we return the last value hold by the variable.
Now we need to write some tests:
In IO
testIO :: (NonEmptyList Int) -> Property
testIO (NonEmpty xs) = monadicIO $ do
  mine <- run . myMaximum . NE.fromList $ xs
  assert $ mine == maximum xsIn ST
testST :: (NonEmptyList Int) -> Property
testST (NonEmpty xs) = monadicST $ do
  mine <- run . myMaximum . NE.fromList $ xs
  assert $ mine == maximum xsAnd in State
testState :: (NonEmptyList Int) -> Bool
testState (NonEmpty xs) =
  State.execState (mine xs) whoCares == maximum xs
  where
    mine = myMaximum . NE.fromList
    whoCares = 42Running the QuickCheck tests
main = do
  quickCheckWith opts testIO
  quickCheckWith opts testST
  quickCheckWith opts testState
  where opts = stdArgs {maxSuccess = 5000}Success!
 
            