简体   繁体   English

证明自然数的类型级加法的交换性

[英]Proving commutativity of type level addition of natural numbers

I'm playing around with what tools haskell offers for dependently typed programming. 我正在使用haskell为依赖类型编程提供的工具。 I have promoted a GADT representing natural numbers to the kind level and made a type family for addition of natural numbers. 我已经将一个代表自然数的GADT推广到了那个级别,并且为了增加自然数而建立了一个类型族。 I have also made your standard "baby's first dependently typed datatype" vector, parameterized over both its length and the type it contains. 我还制作了你的标准“婴儿的第一个依赖类型数据类型”向量,参数化其长度和它包含的类型。 The code is as follows: 代码如下:

data Nat where
    Z :: Nat
    S :: Nat -> Nat

type family (a :: Nat) + (b :: Nat) :: Nat where
    Z + n = n
    S m + n = S (m + n)

data Vector (n :: Nat) a where
    Nil :: Vector Z a
    Cons :: a -> Vector n a -> Vector (S n) a

Furthermore I have made an append function that takes an m-vector, an n-vetor and return an (m+n)-vector. 此外,我做了一个append函数,它接受一个m向量,一个n-vetor并返回一个(m + n) - 向量。 This works as well as one might hope. 这可以和人们希望的一样有效。 However, just for the heck of it, I tried to flip it around so it returns an (n+m)-vector. 然而,只是为了它,我试图翻转它,所以它返回一个(n + m) - 矢量。 This produces a compiler error though, because GHC can't prove that my addition is commutative. 这会产生编译器错误,因为GHC无法证明我的添加是可交换的。 I'm still relatively new to type families, so I'm not sure how to write this proof myself, or if that's even something you can do in haskell. 我还是比较新的打字家庭,所以我不知道如何自己写这个证明,或者如果你甚至可以用haskell做的话。

My initial thought was to somehow utilize a type equality constraint, but I'm not sure how to move forward. 我最初的想法是以某种方式利用类型相等约束,但我不确定如何前进。

So to be clear: I want to write this function 所以要明确:我想写这个功能

append :: Vector m a -> Vector n a -> Vector (n + m) a
append Nil xs         = xs
append (Cons x xs) ys = x `Cons` append xs ys

but it fails to compile with 但它无法编译

    * Could not deduce: (n + 'Z) ~ n
      from the context: m ~ 'Z
        bound by a pattern with constructor: Nil :: forall a. Vector 'Z a,
                 in an equation for `append'

Here's a full solution. 这是一个完整的解决方案。 Warning: includes some hasochism. 警告:包括一些无主。

We start as in the original code. 我们从原始代码开始。

{-# LANGUAGE TypeFamilies, DataKinds, TypeOperators, GADTs, PolyKinds #-}
{-# OPTIONS -Wall -O2 #-}
module CommutativeSum where

data Nat where
    Z :: Nat
    S :: Nat -> Nat

type family (a :: Nat) + (b :: Nat) :: Nat where
    'Z + n = n
    'S m + n = 'S (m + n)

data Vector (n :: Nat) a where
    Nil :: Vector 'Z a
    Cons :: a -> Vector n a -> Vector ('S n) a

The old append which type checks immediately. 旧的追加类型立即检查。

append :: Vector m a -> Vector n a -> Vector (m + n) a
append Nil xs         = xs
append (Cons x xs) ys = x `Cons` append xs ys

For the other append, we need to prove that addition is commutative. 对于另一个附加,我们需要证明加法是可交换的。 We start by defining equality at the type level, exploiting a GADT. 我们首先在类型级别定义相等性,利用GADT。

-- type equality, also works on Nat because of PolyKinds
data a :~: b where
   Refl :: a :~: a

We introduce a singleton type, so that we can pass Nat s and also pattern match on them. 我们引入了单例类型,这样我们就可以传递Nat s并对它们进行模式匹配。

-- Nat singleton, to reify type level parameters
data NatI (n :: Nat) where
  ZI :: NatI 'Z
  SI :: NatI n -> NatI ('S n)

We can associate to each vector its length as a singleton NatI . 我们可以将每个向量的长度关联为单个NatI

-- length of a vector as a NatI
vecLengthI :: Vector n a -> NatI n
vecLengthI Nil = ZI
vecLengthI (Cons _ xs) = SI (vecLengthI xs)

Now the core part. 现在是核心部分。 We need to prove n + m = m + n by induction. 我们需要通过归纳证明n + m = m + n This requires a few lemmas for some arithmetic laws. 对于某些算术定律,这需要一些引理。

-- inductive proof of: n + Z = n  
sumZeroRight :: NatI n -> (n + 'Z) :~: n
sumZeroRight ZI = Refl
sumZeroRight (SI n') = case sumZeroRight n' of
   Refl -> Refl

-- inductive proof of: n + S m = S (n + m)
sumSuccRight :: NatI n -> NatI m -> (n + 'S m) :~: 'S (n + m)
sumSuccRight ZI _m = Refl
sumSuccRight (SI n') m  = case sumSuccRight n' m of
   Refl -> Refl

-- inductive proof of commutativity: n + m = m + n
sumComm :: NatI n -> NatI m -> (n + m) :~: (m + n)
sumComm ZI m = case sumZeroRight m of Refl -> Refl
sumComm (SI n') m = case (sumComm n' m, sumSuccRight m n') of
   (Refl, Refl) -> Refl

Finally, we can exploit the proof above to convince GHC to type append as we wanted. 最后,我们可以利用上面的证据来说服GHC按我们的意愿键入append Note that we can reuse the implementation with the old type, and then convince GHC that it can also use the new one. 请注意,我们可以使用旧类型重用该实现,然后说服GHC它也可以使用新类型。

-- append, with the wanted type
append2 :: Vector m a -> Vector n a -> Vector (n + m) a
append2 xs ys = case sumComm (vecLengthI xs) (vecLengthI ys) of
   Refl -> append xs ys

Final remarks. 最后的评论。 Compared to a fully dependently typed language (say, like Coq), we had to introduce singletons and spend some more effort to make them work (the "pain" part of Hasochism). 与完全依赖类型的语言(比如Coq)相比,我们不得不引入单身人士并花费更多的努力使它们发挥作用(Hasochism的“痛苦”部分)。 In return, we can simply pattern match with Refl and let GHC figure out how to use the deduced equations, without messing with dependent matching (the "pleasure" part). 作为回报,我们可以简单地与Refl模式匹配,让GHC弄清楚如何使用推导出的方程,而不会弄乱依赖匹配(“愉悦”部分)。

Overall, I think it's still a little easier to work with full dependent types. 总的来说,我认为使用完全依赖类型仍然更容易一些。 If/when GHC gets non-erased type quantifiers ( pi n. ... beyond forall n. ... ), probably Haskell will become more convenient. 如果/当GHC获得非擦除类型量词( pi n. ...超出forall n. ... ),可能Haskell将变得更加方便。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM