2018-03-02

What if runST didn't use existential quantification?

Here’s the ST API:

data ST s a

runST :: (forall s. ST s a) -> a
newSTRef :: a -> ST s (STRef s a)
readSTRef :: STRef s a -> ST s a
writeSTRef :: STRef s a -> a -> ST s ()

Let’s drop forall s and see what happens:

data ST s a

runST :: ST s a -> a
newSTRef :: a -> ST s (STRef s a)
readSTRef :: STRef s a -> ST s a
writeSTRef :: STRef s a -> a -> ST s ()

Well, now ST can be eliminated by calling runST after any other operation (e.g. runST . newSTRef):

newSTRef :: a -> STRef s a
readSTRef :: STRef s a -> a
writeSTRef :: STRef s a -> a -> ()

At the type level, writeSTRef looks useless, but it still has the side effect of writing a value to an STRef.

Now the order of evaluation matters, and purity goes out the window:

let ref = newSTRef "1"
    _ = writeSTRef ref "2"
in readSTRef ref -- Does this evaluate to "1" or "2"?

forall s is the linchpin that enforces sequential reads/writes in the ST monad.