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.
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 theforall
is moved outside of all type constructors.Now let's tackle the the
ST
vsIO
question. The reason it's calledunsafePerformIO
and notunusablePerformIO
is because it comes with a proof burden that can't be checked by the compiler: the thing you are runningunsafePerformIO
on must behave as if it is referentially transparent. SinceST
actions come with a (compiler-checked) proof that they behave transparently when executed withrunST
, this means there is no more danger in usingunsafePerformIO
on code that would typecheck inST
than there is in usingrunST
.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": theunsafeFreeze
call you are making has its own proof burden that you must satisfy; but then you already had to satisfy that proof burden for theST
code to be correct.)