Reputation: 3936
When I try to compile this:
module Main where
import qualified Data.Vector.Unboxed.Mutable as MV
import Control.Monad.ST
myRead mv = runST $ MV.read mv 0
I get the following error message:
Could not deduce (t ~ U.MVector s a)
from the context (U.Unbox a)
bound by the inferred type of myRead :: U.Unbox a => t -> a
at src/Main.hs:53:1-32
`t' is a rigid type variable bound by
the inferred type of myRead :: U.Unbox a => t -> a
at src/Main.hs:53:1
Expected type: U.MVector (PrimState (ST s)) a
Actual type: t
In the first argument of `MV.read', namely `mv'
In the second argument of `($)', namely `MV.read mv 0'
In the expression: runST $ MV.read mv 0
Can I make a read from a mutable vector pure with runST? If so, how? I assume it entails a type signature for myRead
, but everything I've tried has just lead to more and more incomprehensible error messages.
EDIT: Highlighting some context I just put in a comment below: The context here is that I have a function that takes in a mutable vector, does some computations using the mutable vector as a temporary scratch space, then needs to return a float value. Because I don't care about the changes to the mutable vector, I was wondering if there was a way to ignore its "state change" and simply return one of the values from inside it.
Upvotes: 7
Views: 398
Reputation: 36
The other answers are good, but I think there is one basic thing about ST that you are missing. Each call to runST is effectively making a new "ST universe" in which some imperative code runs. So if you have one call to runST to make the array and a separate call to runST to get a value out of that array, things can't possibly work. The two runST calls want their own unique universes, whereas you want them to share one.
What the answers explained in some detail is how these unique universes are created by some type-system trickery.
Upvotes: 2
Reputation: 8199
The compiler defaults to seeing the argument mv
on the the left hand side as of some specific type, but requires something of polymorphic type on the right. A type signature can fix things.
{-#LANGUAGE Rank2Types#-}
module Main where
import qualified Data.Vector.Unboxed.Mutable as MV
import Control.Monad.ST
myRead :: MV.Unbox a => (forall s . MV.MVector s a) -> a
myRead mv = runST $ MV.read mv 0
The would-be signature of your function would be something like this, if I understand:
-- myBadRead :: forall s a . MV.Unbox a => MV.MVector s a -> a
-- myBadRead mv = runST $ MV.read mv 0
but here runST :: (forall s. ST s a) -> a
wouldn't have an s
-independent thing to work on, since s
is fixed when you write mv
on the LHS.
Edit: However, as Joachim B and Daniel F. emphasize, though the above definition is coherent, it will be of no use in practice, since you will not be able to construct a vector mv
to give to it. To put it crudely, any way of generating an mv
will already have a deteminate s
assigned to it under the hood by the compiler. One standard method is to make such a function act on a 'pure' vector from Data.Vector.Unboxed
then, on the right hand side, thaw it before applying operations from the .Mutable
module
import qualified Data.Vector.Unboxed as UV
import qualified Data.Vector.Unboxed.Mutable as MV
import Control.Monad.ST
myRead :: MV.Unbox a => UV.Vector a -> a
myRead v = runST $ do mv <- UV.unsafeThaw v
MV.read mv 0
Of course, this particular definition is equivalent to myRead = (UV.! 0)
Similarly, something like this would make sense, striking the runST
in the definition of myRead
mhead :: MV.Unbox a => MV.MVector s a -> ST s a
mhead mv0 = MV.read mv0 0
mrx = runST $ do mv <- UV.unsafeThaw $ UV.enumFromStepN 0 1 20
-- ^^^ or however the mv is generated.
x <- MV.unsafeRead mv 17 -- arbitrary 'scratch pad'
MV.unsafeWrite mv 17 (2*x) -- computations
mhead mv
-- ^^^ we return just the first element, after all the mutation
Here, rather than closing off myRead
or mhead
with runST
we keep it polymorphic in s
and can then use it inside the same ST
block in which the mutable vector mv
appears. The compiler will thus be able to use the 'secret' s
it is using for the do-block as a whole to interpret the result of applying mhead
to mv
, since it is one of the possibilities left open by our polymorphic definition of mhead
Upvotes: 3
Reputation: 25763
The answer by applicative tells you how to get your code to compile. But the code will not be usable: The point of runST
is that the imperative computation cannot escape it, due to the existentially bound type variable there.
Now any mutable array that you create somewhere will have type MVector s a
for a fixed s, while your myRead
expects a value that provides a vector for any s.
It seems that there is a problem earlier that made you want to have that (impossible) function.
Upvotes: 3