我想要像
这样的东西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
下面的答案告诉我如何彻底解决这个问题,这很好。但是我仍然对最初的问题感兴趣(使用可变向量时runST
与unsafePerformIO
的牵连)用于教育目的。
答案 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
代码的正确负担。)