The State monad is just an abstraction for a function that takes a state and returns an intermediate value and some new state value:

newtype State s a = State { runState :: s -> (a, s) }

It is commonly used when needing state in a single thread of control. It doesn’t actually use mutable state and so does not necessary operate in IO.


The ST[1] monad lets you use update-in-place, but unlike IO it is escapable. This means it uses system trickery to ensure that mutable data can’t escape the monad; that is, when you run an ST computation you get a pure result.

ST actions have the form:

-- an ST action returning a value of type a in state t
newtype ST s a = ST (Store s -> (a, Store s))
 -- a mutable variable in thread s
data STRef s a = STRef (MutVar# s a)

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

The reason ST is interesting is that it’s a primitive monad like IO, allowing computations to perform low-level manipulations on bytearrays and pointers. This means that ST can provide a pure interface while using low-level operations on mutable data, meaning it’s very fast. From the perspective of the program, it’s as if the ST computation runs in a separate thread with thread-local storage.

1. state monad transformer.