Practical Implications of runST vs unsafePerformIO
haskell, impredicativetypes, monads
Solution
I can't say I understand the problem statement completely yet, but the following file compiles without error under GHC 7.6.2. It has the same body as your first example (and in particular doesn't call `unsafePerformIO` at all); the primary difference is that the `forall` is moved outside of all type constructors.
{-# LANGUAGE RankNTypes #-}
import Control.Monad
import Control.Monad.Primitive (PrimState)
import Control.Monad.ST
import Data.Vector.Generic hiding (foldM_)
f :: Vector v r => (forall m. [Mutable v (PrimState m) r -> m ()]) -> v r -> v r
f gs x = runST $ do
y <- thaw x
foldM_ (\_ g -> g y) undefined gs
unsafeFreeze y
Now let's tackle the the `ST` vs `IO` question. The reason it's called `unsafePerformIO` and not `unusablePerformIO` is because it comes with a proof burden that can't be checked by the compiler: the thing you are running `unsafePerformIO` on must behave as if it is referentially transparent. Since `ST` actions come with a (compiler-checked) proof that they behave transparently when executed with `runST`, this means there is no more danger in using `unsafePerformIO` on code that would typecheck in `ST` than there is in using `runST`.
BUT: there is danger from a software engineering standpoint. Since the proof is no longer compiler-checked, it's much easier for future refactoring to violate the conditions under which it's safe to use `unsafePerformIO`. So if it is possible to avoid it (as it seems to be here), you should take efforts to do so. (Additionally, "there is no more danger" doesn't mean "there is no danger": the `unsafeFreeze` call you are making has its own proof burden that you must satisfy; but then you already had to satisfy that proof burden for the `ST` code to be correct.)
Problem
I want something like ``` f :: [forall m. (Mutable v) (PrimState m) r -> m ()] -> v r -> v r -- illegal signature f gs x = runST $ do y <- thaw x foldM_ (\_ g -> g y) undefined gs -- you get the idea unsafeFreeze y ``` I'm essentially in the same position I was in this question where Vitus commented: [I]f you want keep polymorphic functions inside some structure, you need either specialized data type (e.g. newtype I = I (forall a. a -> a)) or ImpredicativeTypes. Also, see this question. The problem is, these are both really ugly solutions. So I've come up with a third alternative, which is to avoid the polymorphism altogether by running what "should" be a `ST` computation in `IO` instead. Thus `f` becomes: ``` f :: [(Mutable v) RealWorld r -> IO ()] -> v r -> v r f gs x = unsafePerformIO $ do y <- thaw x foldM_ (\_ g -> g y) undefined gs -- you get the idea unsafeFreeze y ``` I feel slightly dirty for going the `unsafe` `IO` route compared the to the "safe" `ST` route, but if my alternative is a wrapper or impredicative types... Apparently, I'm not alone. Is there any reason I shouldn't use `unsafePerformIO` here? In this case, is it really unsafe at all? Are there performance considerations or anything else I should be aware of? --------------EDIT---------------- An answer below shows me how to get around this problem altogether, which is great. But I'm still interested in the original question (implicaitons of `runST` vs `unsafePerformIO` when using mutable vectors) for educational purposes.