Closely related to my most recent question on handling large data blocks, I have reached the point in which I need to take a large immutable data block, make it mutable for some operations, and then make it immutable again when I am done.
Since I want to retain at least the appearance of purity, the mutable data will be a mutable copy of the original immutable data. For reference, I'm looking at the Bloom Filter example in Real World Haskell, but finding that I cannot actually make my code run in runST.
My data structures, first the pure one, then the impure:
import Data.Vector.Unboxed (Vector)
import Data.Vector.Unboxed.Mutable (MVector)
data PixelMap = Bitmap Int Int (Vector Bool)
data MPixelMap s = MBitmap Int Int (MVector s Bool)
And then I create just a basic newBitmapM function:
newBitmapM :: (Int, Int) -> ST s (MPixelMap s)
newBitmapM (width, height) = MBitmap width height `liftM` MV.replicate (width * height) False
This loads into GHCI just fine, but then I try to run it:
> runST $ newBitmapM (15, 15)
<interactive>:78:9:
Couldn't match type `a' with `PixelMapMutable s'
`a' is a rigid type variable bound by
the inferred type of it :: a at <interactive>:78:1
Expected type: ST s a
Actual type: ST s (PixelMapMutable s)
In the return type of a call of `newBitmapM'
In the second argument of `($)', namely `newBitmapM (15, 15)'
In the expression: runST $ newBitmapM (15, 15)
This error message makes no sense to me at all. a, defined in the type for runST should be polymorphic, and thus not at all "fixed". Can anyone decode this enough to tell me what is really wrong with the code?
The full type signature of
runSTisforall a. (forall s. ST s a) -> a. Inforall s. ST s aall occurrences of thesparameter are quantified byforall s, including thesin yourMPixelMap 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 theain the type ofrunST. The scope of thesparam here in restricted to onlyST s a. It doesn't make sense for theaparam returned byrunSTto contain ansparameter, because there is no suchsparameter in scope anymore!In practice, this means that you cannot extract anything with
runSTthat 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 thatrunSTappears pure to the outside world.You can make your example code work if you eliminate the
sfrom the returned type. In the case of mutable vectorsfreezeandunsafeFreezedoes exactly that. You can freeze your bitmaps by freezing their state-dependent fields:Then you can extract a
PixelMapany time usingrunST.Of course, you can use the unsafe versions of
freezeandthawto convert between immutable/mutable vectors without copying. It is usually quite easy to ascertain thatunsafeFreezedoes nothing nasty; you just have to make sure you don't use the mutable vector anymore in the ST action.unsafeThawcan be trickier, since you have to make sure that your whole program has no reference to your immutable vector, so it makes sense to onlyunsafeThawvectors that live in a small local scope.