这个问题实际上是一个非常密切相关问题的小格子;我认为将其分解是非常有意义的。
创建Vector
的基本方法之一是使用unsafeFreeze
。顾名思义,unsafeFreeze
确实不安全。特别是,没有任何东西可以阻止传递给MVector
的{{1}}在被冻结后被修改。这导致了两个不同的问题:
它可以使"不可变的"向量变化的价值。这就是Haskell一般避开的那种怪异行为。
修改冻结的矢量可以(至少可能)混淆垃圾收集器。没有文件证明垃圾收集器将扫描冻结的阵列以确保其内容被撤离。更一般地说,在它们被冻结时变异载体是绝对禁止的,这样做的结果完全没有说明。
vector
包[1]为创建不可变向量提供了两个高效,看似安全的原语:create
和createT
:
unsafeFreeze
忽略向量融合业务,基本实现看起来像
create :: (forall s. ST s (MVector s a)) -> Vector a
createT :: Traversable t => (forall s. ST s (t (MVector s a))) -> t (Vector a)
create m = runST $ m >>= unsafeFreeze
createT m = runST $ m >>= traverse unsafeFreeze
非常安全。它会运行给定的create
操作,该操作必须创建一个新的ST s
(MVector s
的类型确保它不能使用现有的runST
,并确保fixST
不能玩任何有趣的技巧),冻结它,然后返回冻结的矢量。
createT
实例合法时, Traversable
非常安全。例如,使用列表,createT m
会运行一个生成MVector
列表的操作,然后将它们全部冻结。 s
中的参数化似乎就像create
一样充足,以确保不会发生任何不良事件。请注意,操作可能会创建一个包含相同MVector
的多个副本的列表。这些将被冻结两次,但不应该有任何伤害。合法的Traversable
个实例all look pretty much like decorated lists,因此它们的行为应该相似。现在我终于到达了我的第一个问题:
与非法createT
实例一起使用时,Traversable
是否安全?
非法丢弃,复制或重新安排某些元素或更改装饰(违反身份法)并不构成任何明显的困难。参数化可以防止任何有趣的违反自然法的行为,从而导致其失效。我没有能够通过违反组成法或整体来找到造成麻烦的方法,但这并不能保证不存在。
概括createT
的一个显而易见的方法是允许用户传递他们自己的遍历函数:
createTOf
:: (forall f x y. Applicative f => (x -> f y) -> t x -> f (u y))
-> (forall s. ST s (t (MVector s a))) -> u (Vector a)
createTOf trav m = runST $ m >>= trav unsafeFreeze
请注意,我已允许遍历将容器类型从t
更改为u
。例如,这允许用户生成Vector (MVector s a)
但返回[Vector a]
。在t ~ u
时,这显然与createT
具有非法Traversable
实例一样安全。改变容器的额外灵活性"型减少安全? 编辑:我刚才意识到我可以回答这个问题:不,它没有任何区别。请参阅下面的[2]以获得解释。
当我们使用createT
时,我们可能实际上并不想要一个载体容器;也许我们想要遍历那个容器以获得别的东西。我们可以写类似
traverse f <$> createT m
createTOf
的额外类型灵活性意味着我们手上不一定有Traversable
,并且无法做到这一点。但是使用Traversable
的组合法则,我们可以将此遍历集成到创建函数中:
createTOfThen
:: Applicative g
=> (forall f x y. Applicative f => (x -> f y) -> t x -> f (u y))
-> (Vector a -> g b)
-> (forall s. ST s (t (MVector s a)))
-> g (u b)
createTOfThen trav f m =
runST $ m >>= getCompose . trav (Compose . fmap f . unsafeFreeze)
如果createTOfThen
不是合法的遍历,trav
是否安全?
我确实说过我会谈论一个格子,对吧?接下来的问题是我们可以在多大程度上(如果有的话)削弱遍历的多态性而不会造成麻烦。即使遍历仅需要在s
中进行多态化,所有内容都会进行类型检查,但这显然是不安全的,因为它可以将冻结与修改交错,但它喜欢。
显示最终结果保持Vector
值似乎可能是无害的,但我们肯定不能让遍历知道两者它在{{1}中运行} 和它处理ST s
值。但我们可以让它知道其中一个事实吗?修复MVector s a
肯定会有用:
Applicative
这样可以更有效地创建向量向量之类的东西,因为向量可以在createTOf'
:: (forall s x y. (x -> ST s y) -> t x -> ST s (u y))
-> (forall s. ST s (t (MVector s a))) -> u (Vector a)
createTOfThen'
:: Applicative g
=> (forall s x y. (x -> Compose (ST s) g y) -> t x -> Compose (ST s) g (u y))
-> (Vector a -> g b)
-> (forall s. ST s (t (MVector s a)))
-> g (u b)
中比在任意ST
仿函数中更有效地遍历。它还可以减少对内联的依赖,因为我们避免处理Applicative
字典。
另一方面,我怀疑我们可以让遍历知道它处理Applicative
...只要我们不让它知道他们的状态线程是什么?与...相关联。这足以打开它们,并且(也许不幸的是)获得它们的大小。
编辑!如果允许遍历知道它产生MVector
(这似乎是最不可能出现问题的事情),那么{{1 }}可以用Vector
:
createTOfThen
将格子向第三个方向移动,让我们继续进行秩-2遍历。 createTOf
包提供了自己的Traversable
课程,我将其称为createTOfThen trav post m = getConst $
createTOf (\f ->
fmap Const . getCompose . (trav (Compose . fmap post . f))) m
:
rank2classes
我们可以用完全使用它来制作异构容器R2.Traversable
s:
class (R2.Functor g, R2.Foldable g) => R2.Traversable g where
R2.traverse :: Applicative m
=> (forall a. p a -> m (q a))
-> g p -> m (g q)
以及类似的版本,其中允许遍历知道它在Vector
中工作。我想想象等级-2版本的安全属性与相应的等级1版本的安全属性相同,但我不知道如何证明这一点。
只是为了好玩,我认为我的格子顶部是下面的怪物。如果这些想法中的任何一个都不安全,那么这个想法可能就是:
createTHet
:: R2.Traversable t
=> (forall s. ST s (t (MVector s)))
-> t Vector
createTHet m = runST $ m >>= R2.traverse unsafeFreeze
createTHetOf
:: (forall h f g.
(Applicative h => (forall x. f x -> h (g x)) -> t f -> h (u g)))
-> (forall s. ST s (t (MVector s)))
-> u Vector
createTHetOf trav m = runST $ m >>= trav unsafeFreeze
createTHetOfThen
:: Applicative q
=> (forall h f g.
(Applicative h => (forall x. f x -> h (g x)) -> t f -> h (u g)))
-> (forall x. Vector x -> q (r x))
-> (forall s. ST s (t (MVector s)))
-> q (u r)
createTHetOfThen trav post m =
runST $ m >>= getCompose . trav (Compose . fmap post . unsafeFreeze)
[1]我已经和Stackage联系了,因为今天Hackage已经关闭了。如果我记得并有时间,我会稍后修复这些链接。
[2]证据来自ST s
。鉴于非类型更改createTHetOfThen'
:: (forall s1 s2.
((forall x. MVector s2 x -> Compose (ST s1) q (r x)) -> t (MVector s2) -> Compose (ST s1) q (u r)))
-> (forall x. Vector x -> q (r x))
-> (forall s. ST s (t (MVector s)))
-> q (u r)
createTHetOfThen' trav post m =
runST $ m >>= getCompose . trav (Compose . fmap post . unsafeFreeze)
,我们可以写
Data.Functor.Sum
这实际上是完全的,尽管&#34;遍历&#34;是偏袒的:我们总是会对我们肯定会找到的案例进行匹配。
答案 0 :(得分:0)
将这个想法推向极限实际上帮助我更好地理解它,而且我现在相当确信这些函数的所有都是安全的。考虑
createTOf
:: (forall s1 s2.
(MVector s1 a -> ST s2 (Vector a))
-> t (MVector s1 a) -> ST s2 (u (Vector a)))
-> (forall s. ST s (t (MVector s a)))
-> u (Vector a)
createTOf trav m = runST $ m >>= trav unsafeFreeze
这肯定是一种类型的签名!让我们密切关注我们关心的安全属性:冻结后MVector
没有变异。我们要做的第一件事是运行m
来生成t (MVector s a)
类型的内容。
t
现在非常神秘。它是一个容器吗?这是产生载体的某种行为吗?我们无法真正说出是的内容,但我们可以说出trav unsafeFreeze
无法做什么的一些事情它。让我们首先打破trav
的类型签名:
trav :: forall s1 s2.
(MVector s1 a -> ST s2 (Vector a))
-> t (MVector s1 a) -> ST s2 (u (Vector a)))
trav
将t (MVector s1 a)
变为ST s2 (u (Vector a))
。如果t
中有向量,那么这些向量将存在于状态线程s1
中。但是,结果是状态线程s2
中的操作。因此trav
无法修改使用常规操作给出的MVector
。它只能应用它所需的功能(将是unsafeFreeze
),并使用t
上可能携带的任何机器。什么样的机器可以乘坐t
?嗯,这是一个愚蠢的例子:
data T :: Type -> Type where
T :: [ST s (MVector s a)] -> t (MVector s a)
trav
可以将这些ST
行为与冻结交错吗?没有!这些ST
操作与MVector
匹配,但不匹配状态线程trav
操作。因此trav
无法执行任何与他们有关的东西。