runST vs unsafePerformIO的实际含义 [英] Practical Implications of runST vs unsafePerformIO

查看:144
本文介绍了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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆