runST vs unsafePerformIO的实际含义 [英] Practical Implications of runST vs unsafePerformIO
问题描述
我想要一些像
f :: [forall m。 (Mutable v)(PrimState m)r - > m()] - > v r - > vr - 非法签名
f gs x = runST $ do
y< - thaw x
foldM_(\_ g - > gy)undefined gs - 获得这个想法
unsafeFreeze y
我基本处于和这个问题,Vitus评论道:
如果你希望在一些结构中保持多态函数,你需要特殊的数据类型(例如newtype I = I(forall a。a - > a))或ImpredicativeTypes。
另请参阅这个问题。问题是,这些都是非常难看的解决方案。所以我想出了第三个替代方案,它通过在 IO <
ST
计算来完全避免多态性。 / code>代替。因此 f
变成:
f :: [(可变v)RealWorld r - > IO()] - > v r - > vr
f gs x = unsafePerformIO $ do
y< - thaw x
foldM_(\_ g - > gy)undefined gs - 获得这个想法
unsafeFreeze y
我对 unsafe
IO
route比较了安全 ST
路由,但是如果我的选择是包装类型或意外类型......显然,我并不孤单。
我有什么理由不应该在这里使用 unsafePerformIO
?在这种情况下,它真的是不安全的吗?是否有性能考虑或我应该知道的其他事情?
--------------编辑---- ------------
下面的答案向我展示了如何完全解决这个问题,这非常棒。但我仍然对原始问题感兴趣(当使用可变向量时, runST
vs unsafePerformIO
的含义)用于教育目的。
我不能说我完全理解了问题陈述,但是下面的文件在GHC 7.6下编译时没有错误。 2。它与你的第一个例子具有相同的主体(特别是根本不调用 unsafePerformIO
);主要区别在于 forall
被移出了所有类型的构造函数。
{ - #LANGUAGE RankNTypes# - }
import Control.Monad
import Control.Monad.Primitive(PrimState)
import Control.Monad.ST
import Data.Vector。通用隐藏(foldM_)
f :: Vector vr => (可变v(PrimState m)r - > m()]) - > v r - > vr
f gs x = runST $ do
y< - thaw x
foldM_(\_ g - > gy)undefined gs
unsafeFreeze y
现在让我们解决 ST
vs IO
问题。它被称为 unsafePerformIO
而不是 unusablePerformIO
的原因是因为它带有一个无法通过编译器:你正在运行的东西 unsafePerformIO
on必须表现得好像它是透明的。由于 ST
操作带有一个(编译器检查)的证明,它们在执行时使用 runST
时是透明的,这意味着在使用 unsafePerformIO
的代码中,对 ST
进行类型检查的代码比使用 runST
。
但从软件工程角度来看存在危险。由于证明不再经过编译器检查,未来的重构违反了可以安全使用 unsafePerformIO
的条件。所以如果可以避免它(因为它似乎在这里),你应该努力做到这一点。 (此外,没有更多的危险并不意味着没有危险:您所做的 unsafeFreeze
调用有其自己的证明责任,您必须满足;但那么你已经必须满足 ST
代码的证明负担是正确的。)
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 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.)
这篇关于runST vs unsafePerformIO的实际含义的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!