这个问题实际上是一个非常密切相关的问题的小格子;我认为现在拆分它没有多大意义。
创建 a 的基本方法之一Vector
是使用unsafeFreeze
. 顾名思义,unsafeFreeze
确实是不安全的。特别是,没有什么可以阻止MVector
传递的 tounsafeFreeze
在冻结后被修改。这会导致两个不同的问题:
它可以使“不可变”向量的值发生变化。这只是 Haskell 通常回避的那种离奇的动作。
修改冻结的向量可能(至少可能)混淆垃圾收集器。没有文档保证垃圾收集器将扫描冻结的数组以确保其内容被清除。更一般地说,绝对禁止在冻结时突变向量,这样做的结果是完全未指定的。
包vector
[1] 提供了两个有效的、看似安全的原语来创建不可变向量:create
和createT
:
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 列表的操作,然后将它们全部冻结。那时的参数化s
似乎足以create
确保不会发生任何不好的事情。请注意,该操作可能会创建一个包含多个相同副本的列表MVector
。这些将被冻结两次,但这不应该有任何伤害。合法的Traversable
实例看起来都非常像装饰列表,所以它们的行为应该相似。现在我终于达到了我的第一个问题:
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
。例如,这允许用户产生 aVector (MVector s a)
但取回 a [Vector a]
。当 时,这显然与非法实例t ~ u
一样安全。更改“容器”类型的额外灵活性是否会降低安全性?编辑:我刚刚意识到我可以回答这个问题:不,没有区别。有关说明,请参见下面的 [2]。createT
Traversable
当我们使用 时createT
,我们可能实际上并不想要一个向量容器;也许我们想遍历那个容器来得到别的东西。我们可以写类似
traverse f <$> createT m
额外的类型灵活性createTOf
意味着我们不一定有 aTraversable
在我们手上,也不一定能做到这一点。但是使用 的组合律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
值似乎可能是无害的,但我们肯定不能让遍历知道它正在运行并且它正在处理值。但是我们可以让它知道其中一个事实吗?修复肯定会有用: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
...只要我们不让它知道它们与哪个状态线程相关联。这足以将它们拆箱,并且(也许不幸的是)获得它们的尺寸。
编辑!如果允许遍历知道它正在产生Vector
s(这似乎是最不可能出现问题的事情),那么createTOfThen
可以按照以下方式实现createTOf
:
createTOfThen trav post m = getConst $
createTOf (\f ->
fmap Const . getCompose . (trav (Compose . fmap post . f))) m
将晶格放在第三方向,让我们继续进行 rank-2 遍历。该rank2classes
包提供了自己的Traversable
类,我将其称为R2.Traversable
:
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)
我们可以用它玩完全相同的游戏来生成s 的异构容器Vector
:
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)
以及允许遍历知道它正在工作的类似版本ST s
。我想rank -2 版本的安全属性与相应的 rank-1 版本相同,但我不知道如何证明这一点。
只是为了好玩,我认为我的格子的顶部是下面的怪物。如果这些想法中的任何一个不安全,那么这个想法可能是:
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)
[1] 我已经链接到 Stackage,因为 Hackage 今天停止了。如果我记得并且有时间,我稍后会修复链接。
[2] 证明来自Data.Functor.Sum
. 给定一个不改变类型的createTOfSame
,我们可以写
createTOf
:: (forall f a b. Applicative f => (a -> f b) -> t a -> f (u b))
-> (forall s. ST s (t (MVector s a)))
-> u (Vector a)
createTOf trav m =
case createTOfSame (\f (InL xs) -> InR <$> trav f xs)
(InL <$> m) of
InR u -> u
这实际上是全部的,尽管“遍历”是部分的:我们总是对我们肯定会找到的内容进行大小写匹配。