Haskell 实时持久队列总数

Haskell 实时持久队列总数,haskell,queue,dependent-type,gadt,Haskell,Queue,Dependent Type,Gadt,Okasaki描述了可在Haskell中使用 data Queue a = forall x . Queue { front :: [a] , rear :: [a] , schedule :: [x] } 其中增量旋转保持不变 length schedule = length front - length rear 更多细节 如果您熟悉所涉及的队列,可以跳过此部分 旋转函数看起来像 rotate :: [a] -> [a] -> [a] -> [a] ro

Okasaki描述了可在Haskell中使用

data Queue a = forall x . Queue
  { front :: [a]
  , rear :: [a]
  , schedule :: [x]
  }
其中增量旋转保持不变

length schedule = length front - length rear
更多细节 如果您熟悉所涉及的队列,可以跳过此部分

旋转函数看起来像

rotate :: [a] -> [a] -> [a] -> [a]
rotate [] (y : _) a = y : a
rotate (x : xs) (y : ys) a =
  x : rotate xs ys (y : a)
它由一个智能构造函数调用

exec :: [a] -> [a] -> [x] -> Queue a
exec f r (_ : s) = Queue f r s
exec f r [] = Queue f' [] f' where
  f' = rotate f r []
在每次队列操作之后。当
length s=length f-length r+1
时,始终调用智能构造函数,以确保
rotate
中的模式匹配成功

问题 我讨厌部分函数!我想找到一种方法来表达类型中的结构不变量。通常的依赖向量似乎是一种可能的选择:

data Nat = Z | S Nat

data Vec n a where
  Nil :: Vec 'Z a
  Cons :: a -> Vec n a -> Vec ('S n) a
然后(也许)

问题是我还没有弄清楚如何处理这些类型。似乎极有可能需要一定量的
不安全力量来提高效率。然而,我还没能想出一个甚至可以模糊管理的方法。在Haskell中是否可以很好地做到这一点?

以下是我得到的:

open import Function
open import Data.Nat.Base
open import Data.Vec

grotate : ∀ {n m} {A : Set}
        -> (B : ℕ -> Set)
        -> (∀ {n} -> A -> B n -> B (suc n))
        -> Vec A n
        -> Vec A (suc n + m)
        -> B m
        -> B (suc n + m)
grotate B cons  []      (y ∷ ys) a = cons y a
grotate B cons (x ∷ xs) (y ∷ ys) a = grotate (B ∘ suc) cons xs ys (cons y a)

rotate : ∀ {n m} {A : Set} -> Vec A n -> Vec A (suc n + m) -> Vec A m -> Vec A (suc n + m)
rotate = grotate (Vec _) _∷_

record Queue (A : Set) : Set₁ where
  constructor queue
  field
    {X}      : Set
    {n m}    : ℕ
    front    : Vec A (n + m)
    rear     : Vec A m
    schedule : Vec X n

open import Relation.Binary.PropositionalEquality
open import Data.Nat.Properties.Simple

exec : ∀ {m n A} -> Vec A (n + m) -> Vec A (suc m) -> Vec A n -> Queue A
exec {m} {suc n} f r (_ ∷ s) = queue (subst (Vec _) (sym (+-suc n m)) f) r s
exec {m}         f r  []     = queue (with-zero f') [] f' where
 with-zero    = subst (Vec _ ∘ suc) (sym (+-right-identity m))
 without-zero = subst (Vec _ ∘ suc) (+-right-identity m)

 f' = without-zero (rotate f (with-zero r) [])
rotate
是根据
grotate
定义的,原因相同(或):因为
Vec A(suc n+m)
在定义上不是
Vec A(n+suc m)
,而
(B∘ suc)m
在定义上是
B(suc m)

exec
具有与您提供的相同的实现(对那些
subst
s进行模块化),但我不确定其类型:
r
必须为非空可以吗?

该函数非常聪明(请花点时间对其进行表决),但作为不熟悉Agda的人,这将如何在Haskell中实现对我来说并不明显。这是哈斯克尔的完整版本。我们需要大量的扩展,以及
Data.Type.Equality
(因为我们需要做一些有限的类型证明)

定义
Nat
Vec
Queue
接下来,我们定义了常用的类型级自然数(这看起来只是一个常规的
数据
定义,但是因为我们启用了
TypeInType
,当我们在一个类型中使用它时,它会自动升级)和一个用于添加的类型函数(一个
类型族
)。请注意,尽管有多种定义
+
的方法,但我们在这里的选择将影响下面的内容。我们还将定义通常的
Vec
,它非常类似于一个列表,只是它将其长度编码为幻影类型
n
。这样,我们就可以继续定义队列的类型了

data Nat = Z | S Nat

type family n + m where
    Z   + m = m
    S n + m = S (n + m)

data Vec a n where
    Nil   :: Vec a Z
    (:::) :: a -> Vec a n -> Vec a (S n)

data Queue a where
    Queue :: { front :: Vec a (n + m)
             , rear :: Vec a m
             , schedule :: Vec x n } -> Queue a
定义
rotate
现在,事情开始变得更棘手了。我们想定义一个函数
rotate
,它的类型为
rotate::vecan->vecam(sn+m)->vecam->vecam(sn+m)
,但是只要递归地定义它,就会很快遇到各种与证明相关的问题。相反,解决方案是定义一个更一般的
grotate
,它可以递归定义,并且
rotate
是它的特例

Bump
Bump
的目的是避免Haskell中没有类型级组合这一事实。像
(∘)使
(S∘ S) x
S(sx)
。解决方法是使用
凹凸
/
降低
连续包裹/展开

newtype Bump p n = Bump { lower :: p (S n) }

grotate :: forall p n m a.
           (forall n. a -> p n -> p (S n)) ->
           Vec a n ->
           Vec a (S n + m) ->
           p m ->
           p (S n + m)
grotate cons Nil        (y ::: _)  zs = cons y zs
grotate cons (x ::: xs) (y ::: ys) zs = lower (grotate consS xs ys (Bump (cons y zs))) 
  where
    consS :: forall n. a -> Bump p n -> Bump p (S n)
    consS = \a -> Bump . cons a . lower 

rotate :: Vec a n -> Vec a (S n + m) -> Vec a m -> Vec a (S n + m)
rotate = grotate (:::)
在这里,我们需要为所有的
s提供明确的
,以明确哪些类型变量被捕获,哪些没有被捕获,以及表示更高级别的类型

单一自然数
SNat
在我们继续进行
exec
之前,我们设置了一些机制,允许我们证明一些类型级别的算术声明(我们需要让
exec
进行类型检查)。我们首先制作一个
SNat
类型(这是与
Nat
相对应的单态类型).
SNat
在幻影类型变量中反映其值

data SNat n where
  SZero :: SNat Z
  SSucc :: SNat n -> SNat (S n)
然后,我们可以制作一些有用的函数来处理
SNat

sub1 :: SNat (S n) -> SNat n
sub1 (SSucc x) = x

size :: Vec a n -> SNat n
size Nil = SZero
size (_ ::: xs) = SSucc (size xs)
最后,我们准备证明一些算法,即
n+sm~S(n+m)
n+Z~n

plusSucc :: (SNat n) -> (SNat m) -> (n + S m) :~: S (n + m)
plusSucc SZero _ = Refl
plusSucc (SSucc n) m = gcastWith (plusSucc n m) Refl

plusZero :: SNat n -> (n + Z) :~: n
plusZero SZero = Refl
plusZero (SSucc n) = gcastWith (plusZero n) Refl 
定义
exec
现在我们有了
rotate
,我们可以定义
exec
。这个定义看起来与问题中的定义(带列表)几乎相同,只是用
gcastWith
注释了


可能值得注意的是,我们可以通过使用免费获得一些东西

import Data.Singletons.TH 

singletons [d|
    data Nat = Z | S Nat

    (+) :: Nat -> Nat -> Nat
    Z   + n = n
    S m + n = S (m + n)
  |]

定义,
Nat
、类型族
:+
(相当于my
+
)和单态类型
SNat
(构造函数
SZ
SS
相当于my
SZero
SSucc
)综合在一起。

我认为我不了解这种队列类型-向量实现中的
exec
rotate
类型是什么?对于所有x.{…;schedule::Vec sl x}
给你?在我看来,
时间表
本质上是一个自然数,因为你唯一知道的是它的长度,因为它的内容是存在的量化。所以
时间表
的类型可能应该是
Sing sl
@user3237465,
时间表
确实代表一个自然数(我特别引入了
x
,以确保它仅用作自然数),但它实际上是前一个列表的一部分,因此它上的模式匹配驱动了该列表的增量计算。@user2407038,
exec
的类型可以用不同的方式表示,具体取决于最有效的方式,但我相信有一个有效的表达式是
Vec(sl1:+rl)a->Vec rl a->Vec('s sl1)a->Queue a
那么你工作正常了吗?顺便说一句,你可能应该接受,因为它的信息量明显更大
plusSucc :: (SNat n) -> (SNat m) -> (n + S m) :~: S (n + m)
plusSucc SZero _ = Refl
plusSucc (SSucc n) m = gcastWith (plusSucc n m) Refl

plusZero :: SNat n -> (n + Z) :~: n
plusZero SZero = Refl
plusZero (SSucc n) = gcastWith (plusZero n) Refl 
exec :: Vec a (n + m) -> Vec a (S m) -> Vec a n -> Queue a
exec f r (_ ::: s) = gcastWith (plusSucc (size s) (sub1 (size r))) $ Queue f r s
exec f r Nil       = gcastWith (plusZero (sub1 (size r))) $
  let f' = rotate f r Nil in (Queue f' Nil f')
import Data.Singletons.TH 

singletons [d|
    data Nat = Z | S Nat

    (+) :: Nat -> Nat -> Nat
    Z   + n = n
    S m + n = S (m + n)
  |]