IORef is a pointer to value that can be changed. IORef holds pointer to a value (boxed value) because most Haskell values are lazy.
Please look at unboxed vectors for another example: https://hackage.haskell.org/package/vector-0.12.1.2/docs/Dat...
You can create an unboxed mutable vector and read and update its elements. Vectors are stored as Structure Of Arrays (Vector (a,b) is transformed into (Vector a, Vector b)) and are very efficient in transformations.
Next to them you can find Storable vectors which allow you to store and update any values that have Storable class instance defined. They are for cases when you need Array Of Structures.
Continuing Vector example, ST monad allows you to create a computation that uses mutation internally and looks pure from outside - do new/read/write and then return freezed array. Apply ST monad runner and you get pure freezed array. IO monad allows you to pass that array between computations of different kind.