矢量创作安全

时间:2018-04-12 21:06:08

标签: arrays haskell vector higher-rank-types traversable

这个问题实际上是一个非常密切相关问题的小格子;我认为将其分解是非常有意义的。

创建Vector的基本方法之一是使用unsafeFreeze。顾名思义,unsafeFreeze确实不安全。特别是,没有任何东西可以阻止传递给MVector的{​​{1}}在被冻结后被修改。这导致了两个不同的问题:

  1. 它可以使"不可变的"向量变化的价值。这就是Haskell一般避开的那种怪异行为。

  2. 修改冻结的矢量可以(至少可能)混淆垃圾收集器。没有文件证明垃圾收集器将扫描冻结的阵列以确保其内容被撤离。更一般地说,在它们被冻结时变异载体是绝对禁止的,这样做的结果完全没有说明。

  3. vector包[1]为创建不可变向量提供了两个高效,看似安全的原语:createcreateT

    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 sMVector 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;是偏袒的:我们总是会对我们肯定会找到的案例进行匹配。

1 个答案:

答案 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)))

travt (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无法执行任何与他们有关的东西。