runST与unsafePerformIO的实际意义

时间:2013-11-14 15:56:11

标签: haskell monads impredicativetypes

我想要像

这样的东西
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

我基本上处于this question所处的位置,Vitus评论说:

  

[我]你想在某些结构中保留多态函数,你需要专门的数据类型(例如newtype I = I(forall a。a - &gt; a))或ImpredicativeTypes。

另请参阅this question。问题是,这些都是非常难看的解决方案。所以我想出了第三个替代方案,即通过在ST中运行“应该”成为IO计算来完全避免多态性。因此f成为:

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

我认为将unsafe IO路线与“安全”ST路线相比有点脏,但如果我的替代方案是包装或不可预测的类型......显然, I'm not alone.

不应该在这里使用unsafePerformIO有什么理由吗?在这种情况下,它真的不安全吗?是否存在性能因素或其他我应该注意的事项?

-------------- ---------------- EDIT

下面的答案告诉我如何彻底解决这个问题,这很好。但是我仍然对最初的问题感兴趣(使用可变向量时runSTunsafePerformIO的牵连)用于教育目的。

1 个答案:

答案 0 :(得分:5)

我不能说我完全理解了问题陈述,但是以下文件在GHC 7.6.2下编译时没有错误。它与你的第一个例子具有相同的主体(特别是根本不调用unsafePerformIO);主要区别在于forall被移出所有类型构造函数之外。

{-# 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

现在让我们解决ST vs IO问题。它被称为unsafePerformIO而不是unusablePerformIO的原因是因为它带有一个无法由编译器检查的证明负担:您运行unsafePerformIO的东西必须表现得像它一样是参考透明的。由于ST操作附带(编译器检查)证明它们在使用runST执行时透明行为,这意味着在unsafePerformIO上使用ST代码时会遇到更严重的危险{ {1}}比使用runST

但是:从软件工程的角度来看存在危险。由于证明不再经过编译器检查,因此未来的重构更容易违反使用unsafePerformIO安全的条件。因此,如果可以避免它(因为它似乎在这里),你应该努力这样做。 (此外,“没有更多的危险”并不意味着“没有危险”:你正在进行的unsafeFreeze电话有你必须满足的证明负担;但是你必须满足那个证据ST代码的正确负担。)