2

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?

jam
  • 803
  • 5
  • 14

1 Answers1

2

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.

chi
  • 111,837
  • 3
  • 133
  • 218
  • is there a way to constrain the type ```k``` in the function signature to make it explicit that it cannot depend on ```s``` ? – jam May 03 '20 at 12:17
  • @jam If it does not explicitly depend on `s`, it can not be modified. E.g. `f :: forall a s . Maybe a -> ST s ()` can not alter the first argument, not even if the caller passes `Just ref` where `ref :: STRef s Int`. – chi May 03 '20 at 12:37
  • I disagree. You can plug in something that happens to depend on ```s``` even if there is no explicit ```s``` in the signature, hence my concern. I will try to give an example. @chi – jam May 03 '20 at 12:41
  • @jam That's a tricky property that should be ensured by parametricity. It's by no means a simple one, so I understand your confusion. Feel free to craft an example, so that we can discuss this point more precisely. – chi May 03 '20 at 12:49
  • For me this compiles with no error with the vector package, (im using some language extensions): ``` import Control.Monad.ST import Data.Vector.Mutable as VM class Write s x k v where write :: x -> k -> v -> (ST s) () instance Write s (VM.MVector s Int) Int Int where write x k v = VM.write x k v ``` – jam May 03 '20 at 13:01
  • @jam That's because the type of `write` uses a constraint `Write s x k v`, so these variables are "connected", in a sense. You can observe the connection from the type. Without such a constraint it would be impossible. – chi May 03 '20 at 13:13
  • So what is the right way to design stateful monadic typeclasses in Haskell? – jam May 03 '20 at 13:26
  • My feeling is that the answer might be "don't put an ```s``` in the typeclass variables". But I don't know, haskell best practices have never been clear to me. Sorry for my rambling – jam May 03 '20 at 13:40
  • 1
    @jam I'm unsure about that. It depends on the actual goal. You can sometimes change `class C a b where f :: T a b` to `class C a where f :: forall b. T a b` to make `b` independent from `a`. – chi May 03 '20 at 14:23
  • I'm with chi here. Parametricity often takes care of what seems to be your concern. The general issue of state management can be a bit trickier, and might be handled using lenses or effect systems. It's hard to make concrete recommendations without a concrete problem. – dfeuer May 03 '20 at 15:36