我有一个函数,它从树中递归地创建一个扁平的矩阵列表,这些矩阵必须是可变的,因为它们的元素在创建过程中经常更新。到目前为止,我已经提出了一个具有签名的递归解决方案:
doAll :: .. -> [ST s (STArray s (Int, Int) Int)]
我不退货的原因 [UArray (Int,Int) Int]
直接是因为 doAll
被称为递归调用,修改列表中矩阵的元素并附加新的矩阵。我不想不必要地冻结和解冻基质。
到现在为止还挺好。我可以检查一下 n
-th矩阵(类型 Array (Int, Int) Int
) ghci
runSTArray (matrices !! 0)
runSTArray (matrices !! 1)
事实上,我的算法得到了正确的结果。但是,我没有找到一种方法来映射 runSTUArray
在返回的列表上 doAll
:
map (runSTArray) matrices
Couldn't match expected type `forall s. ST s (STArray s i0 e0)'
with actual type `ST s0 (STArray s0 (Int, Int) Int)'
如果我尝试在列表上递归计算或尝试评估函数中包含的单个元素,则会出现同样的问题
有人可以解释一下发生了什么(我真的不明白这个问题的含义 forall
关键字)以及如何评估列表中的数组?
这是类型技巧的一个不幸结果 ST
安全。首先,您需要了解ST的工作原理。唯一的方法是从 ST
monad到纯代码就是用了 runST
功能,或建立在它上面的其他功能 runSTArray
。这些都是形式 forall s.
。这意味着,为了从STArray构造一个Array,编译器必须能够确定它可以替换它喜欢的任何类型 s
里面输入变量 runST
。
现在考虑这个功能 map :: (a -> b) -> [a] -> [b]
。这表明列表中的每个元素必须具有完全相同的类型(a
),因此也是如此 s
。但是这个额外的约束违反了类型 runSTArray
,声明编译器必须能够自由替换其他值 s
。
你可以通过定义一个新函数来解决这个问题,首先冻结ST monad中的数组,然后运行生成的ST动作:
runSTArrays :: Ix ix => (forall s. [ST s (STArray s ix a)]) -> [Array ix a]
runSTArrays arrayList = runST $ (sequence arrayList >>= mapM freeze)
请注意 forall
要求 RankNTypes
延期。
你刚刚反对类型系统的限制。
runSTArray具有更高排名的类型。您必须将状态类型变量唯一的ST动作传递给它。然而,在Haskell中,通常不可能在列表中包含这样的值。
整个事情是一个聪明的计划,以确保您在ST行动中产生的价值无法从那里逃脱。这意味着,您的设计看起来有点破碎。
一个建议:你不能处理另一个ST动作中的值,比如
sequence [ ... your ST s (STArray s x) ...] >>= processing
where
processing :: [STArray s x] -> ST s (your results)