我正在编写一堆不同的排序,并为列表和数组执行此操作。困扰我的一件事是我可以为像
这样的列表编写多态排序函数bubblesort :: (Ord a) => [a] -> [a]
但是当我尝试为UArray
s做同样的事情时:
alterUArray :: (Ix i, Ord e) =>
(STUArray s i e -> ST s ()) -> UArray i e -> UArray i e
alterUArray alter ua = runST $ do
mua <- thaw ua :: ST s1 (STUArray s1 i e)
alter mua
freeze mua
来自GHC的long error messages失败(UArray Int Int
的版本效果很好)。我尝试指定{-# LANGUAGE ScopedTypeVariables #-}
,但这并未消除i e
调用中thaw
类型的歧义。没有thaw
类型的错误消息:http://hpaste.org/84910。
编写多态UArray
操作需要什么?是否有一些基本限制?是否有允许执行此类操作的编译器扩展?
答案 0 :(得分:3)
有两个问题。首先,作为dave4420 pointed out,runST
需要alter
函数在状态s
中具有多态性。
但是,修复此问题使得无法解决第二个问题,即MArray
(和thaw
)需要freeze
个实例。你需要一个约束
alterUArray :: (Ix i, Ord e, IArray UArray e, forall s. MArray (STUArray s) e (ST s)) => ...
要让它发挥作用,因为runST
是选择s
的人。但是你不能指定这样的约束。
如果您提供特定的元素类型(Int
,Double
,...),则可以使用
instance MArray (STUArray s) Int (ST s) where ...
所以thaw
和freeze
的要求无论s
选择了runST
都得到满足(并且不需要说明约束)。
如果您选择盒装数组而不是未装箱数组,它也有效,因为还有
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
内容,并将其隐藏在调用者之外。因此,让我们通过在unsafePerformIO
参数上强制使用更通用的类型来安全使用alter
:
{-# 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
获取原始数组的副本是必要的,但在冻结时不需要额外的副本,可能{{1}}没有问题。)
答案 1 :(得分:1)
我也被这类问题所困扰。现在我认为编写这样的多态函数是不可能的。
我们可以写
alterArray :: (Ix i, IArray b e, IArray a e, MArray a2 e m) =>
(a2 i e -> m a1) -> a i e -> m (b i e)
alterArray alter ua = do
mua <- thaw ua
alter mua
freeze mua
或者
alterUArrayST :: (Ix i, IArray UArray e, MArray (STUArray s) e (ST s)) =>
(STUArray s i e -> ST s ()) -> UArray i e -> ST s (UArray i e)
alterUArrayST alter ua = do
mua <- thaw ua
alter mua
freeze mua
但是如果我们要摆脱ST
,我们必须编写一些特定类型的版本,例如。
alterUArrayInt :: (forall s. STUArray s Int Int -> ST s ()) -> UArray Int Int -> UArray Int Int
alterUArrayInt alter ua = runST $ do
mua <- thaw ua
alter mua
freeze mua
alterUArrayFloat :: (forall s. STUArray s Int Float -> ST s ()) -> UArray Int Float -> UArray Int Float
alterUArrayFloat alter ua = runST $ do
mua <- thaw ua
alter mua
freeze mua
如果MArray
有一个实例MArray (STUArray s) e (ST s)
,我想我们可以编写这样的多态函数。不幸的是,MArray
没有这样的实例。
yairchu在https://stackoverflow.com/a/2244281/779412中针对此类问题提供了另一种解决方法。
答案 2 :(得分:0)
将类型声明更改为
alterUArray :: (Ix i, Ord e) =>
(forall s. STUArray s i e -> ST s ()) -> UArray i e -> UArray i e
并从thaw ua
中删除类型注释。
您需要启用RankNTypes
扩展程序(或Rank2Types
,虽然已弃用)(但是您不需要这样做才能使用runST
吗?我忘了)
说明:您的原始类型声明等同于
alterUArray :: (Ix i, Ord e) =>
forall s. (STUArray s i e -> ST s ()) -> UArray i e -> UArray i e
这意味着alterUArray
的来电者可以选择s
。我改变的类型坚持认为alterUArray
可以自己选择s
。然后,您的代码会将s
的选择推迟到runST
;它的类型坚持认为它可以做出选择。