有两个问题。首先,正如
dave4420指出的那样,
runST
需要在状态
s
中使
alter
函数具有多态性。
然而,修正这个问题会导致解决第二个问题变得不可能,即您需要一个MArray
实例来进行thaw
(和freeze
)。您需要一个约束条件。
alterUArray :: (Ix i, Ord e, IArray UArray e, forall s. MArray (STUArray s) e (ST s)) => ...
为了让它正常工作,需要选择
s
,因为
runST
是这个选择。但你不能指定这样的限制。
如果您提供特定的元素类型(
Int
、
Double
等),它就能正常工作,因为有一个...
instance MArray (STUArray s) Int (ST s) where ...
所以无论
runST
选择什么样的
s
,
thaw
和
freeze
的要求都能得到满足(约束条件不必声明)。此外,如果选择装箱数组而非非装箱数组,它也能正常工作,因为也存在一个。
instance MArray (STArray s) e (ST s) where ...
因此,在alterUArray
的签名中没有对需要声明的元素类型进行限制。(列表元素的类型没有限制,且列表元素是装箱的,因此装箱数组是列表的对应项,而不是非装箱数组)。
如果你能忍受手脏,你可以通过将ST s
替换为IO
来规避这个问题。
alterUArray :: (Ix i, Ord e, MArray IOUArray e IO, IArray UArray e) =>
(IOUArray i e -> IO ()) -> UArray i e -> UArray i e
alterUArray alter ua = unsafePerformIO $ do
mua <- thaw ua
alter mua
freeze mua
只需要 FlexibleContexts
。这允许传递一个有害的 alter
参数,执行恶意的 IO
操作,但会将其隐藏在调用者之外。因此,让我们通过强制在 alter
参数上使用更通用的类型来使这里对 unsafePerformIO
的使用安全:
{-# LANGUAGE FlexibleContexts, RankNTypes, ScopedTypeVariables #-}
import Data.Array.Unboxed
import Data.Array.IO
import System.IO.Unsafe
alterUArray :: forall i e. (Ix i, Ord e, IArray UArray e, MArray IOUArray e IO) =>
(forall m u. MArray u e m => u i e -> m ()) -> UArray i e -> UArray i e
alterUArray alter ua = unsafePerformIO $ do
mua <- thaw ua :: IO (IOUArray i e)
alter mua
freeze mua
现在,我们已经给
alter
赋予了一种类型,使得它不可能进行恶意的
IO
操作,而不使用
unsafePerformIO
,因此这里使用
unsafePerformIO
并不会增加额外的不安全性——代价是需要更多所需的扩展。
(注意:虽然使用
thaw
来获取原始数组的副本是必要的,但在冻结时不需要额外的副本,可以毫无问题地使用
unsafeFreeze
。)
thaw ua
中删除类型注释,那么会得到什么错误? - dave4420