Search code examples
haskellimmutabilitystate-monad

In Haskell, how to distinguish mutable references vs regular variables in monadic functions definitions


Let's say you want to write some stateful function in Haskell. You have to use a monadic style like this: (using whatever state monad)

f :: x -> k -> (ST s) r

So this means that essentially the function takes some input x and k, might use and/or modify the world to compute a return value r.

Assume x is a stateful structure, that might be modified by f. Assume k is just a simple key type used for example to access something in x. k itself will be assigned a simple number type later, but we don't want to have to decide now of its type.

So essentially I know that x is a mutable thing, and k is immutable. The problem is just looking at f's signature, we cannot tell that, so if f occurs in the body of some more complex monadic code we can't reason very well about those variables.

Example:

g :: x -> k -> (ST s) r
g a i = do
    ...
    f a i --  I don't know if i :: k depends on state
    ... --- I don't know if i was changed by f

What I mean is that if I was given a variable i of unknown type k, I don't know whether or not it depends on s and whether its value can be affected by a call of f. This problem of course does not exist when writing pure functions, since everything is immutable.

Is there a way to conveniently annotate and, more importantly, statically enforce that k will remain unchanged in the ST monad when calling f?


Solution

  • Inside ST, you can definitely tell what is mutable: an Int is always an immutable integer, while a STRef s Int is an (immutable) reference to a mutable Int.

    Hence,

    f :: STRef s Int -> String -> (ST s) Bool
    

    can (read and) modify the Int pointed to the first argument, but can only read the immutable string passed as second argument.

    On top of that, f might create (and mutate) new STRef s references to newly allocated values. It can also modify other valued if f was defined using a reference to such values. E.g. in

    bar :: forall s . ST s ()
    bar = do
       x_ref <- newSTRef "hello"
       let f :: STRef s String -> String -> ST s ()
           f y_ref str = do
             y <- readSTRef y_ref
             writeSTRef x_ref y
             writeSTRef y_ref (y ++ " change " ++ str)
       ...
    

    calling f will alter both the string which was originally set to "hello" and the string whose reference is passed to f.

    In your own example:

    g :: x -> k -> (ST s) r
    g a i = do
        ...
        f a i --  I don't know if i :: k depends on state
        ... --- I don't know if i was changed by f
    

    If i :: k was not a reference, it still has the same value. If it was a reference, the referred value might have been changed by f a i.