The full type signature of runST
is forall a. (forall s. ST s a) -> a
. In forall s. ST s a
all occurrences of the s
parameter are quantified by forall s
, including the s
in your MPixelMap s
, in the specific example you provided. In fact, all Haskell type parameters must be introduced somewhere by quantification, it's just that most of the time it's left implicit, like the a
in the type of runST
. The scope of the s
param here in restricted to only ST s a
. It doesn't make sense for the a
param returned by runST
to contain an s
parameter, because there is no such s
parameter in scope anymore!
In practice, this means that you cannot extract anything with runST
that depends on the inner state parameter. This is the actually a core safety feature of the ST monad. A function is pure if it's independent from some state. The type quantification trick ensures that runST
appears pure to the outside world.
You can make your example code work if you eliminate the s
from the returned type. In the case of mutable vectors freeze
and unsafeFreeze
does exactly that. You can freeze your bitmaps by freezing their state-dependent fields:
freezeMPixelMap :: MPixelMap s -> ST s PixelMap
freezeMPixelMap (MBitmap width height vec) =
Bitmap width height `liftM` V.freeze vec
Then you can extract a PixelMap
any time using runST
.
Of course, you can use the unsafe versions of freeze
and thaw
to convert between immutable/mutable vectors without copying. It is usually quite easy to ascertain that unsafeFreeze
does nothing nasty; you just have to make sure you don't use the mutable vector anymore in the ST action. unsafeThaw
can be trickier, since you have to make sure that your whole program has no reference to your immutable vector, so it makes sense to only unsafeThaw
vectors that live in a small local scope.