Haskell 我可以向类型检查器提供GHC 7.6中有关归纳自然的证明吗?

Haskell 我可以向类型检查器提供GHC 7.6中有关归纳自然的证明吗?,haskell,ghc,data-kinds,Haskell,Ghc,Data Kinds,GHC 7.6.1提供了用于在类型级别编程的新功能,包括。以类型级自然和向量为例,我希望能够在向量上编写函数,这些函数依赖于算术的基本定律 不幸的是,尽管我想要的定律通常很容易通过案例分析和归纳法在归纳自然法上证明,但我怀疑我能否说服类型检查器。作为一个简单的例子,下面的简单反转函数的类型检查需要证明n+suze~sun 我有没有办法提供证据,或者我现在真的处于完全依赖类型的领域 {-# LANGUAGE DataKinds, KindSignatures, GADTs, TypeFamilie

GHC 7.6.1提供了用于在类型级别编程的新功能,包括。以类型级自然和向量为例,我希望能够在向量上编写函数,这些函数依赖于算术的基本定律

不幸的是,尽管我想要的定律通常很容易通过案例分析和归纳法在归纳自然法上证明,但我怀疑我能否说服类型检查器。作为一个简单的例子,下面的简单反转函数的类型检查需要证明
n+suze~sun

我有没有办法提供证据,或者我现在真的处于完全依赖类型的领域

{-# LANGUAGE DataKinds, KindSignatures, GADTs, TypeFamilies, TypeOperators #-}

data Nat = Ze | Su Nat

data Vec :: * -> Nat -> * where
  Nil  :: Vec a Ze
  Cons :: a -> Vec a n -> Vec a (Su n)

type family (m :: Nat) + (n :: Nat) :: Nat

type instance Ze + n = n
type instance (Su m + n) = Su (m + n)

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

rev :: Vec a n -> Vec a n
rev Nil = Nil
rev (Cons x xs) = rev xs `append` Cons x Nil
(注意:我只对这些代码进行了类型检查(并没有实际运行)

方法1

实际上,您可以通过将证据存储在GADT中来操作它们。您需要启用
ScopedTypeVariables
才能使这种方法起作用

data Proof n where
    NilProof  :: Proof Ze
    ConsProof :: (n + Su Ze) ~ Su n => Proof n -> Proof (Su n)

class PlusOneIsSucc n where proof :: Proof n
instance PlusOneIsSucc Ze where proof = NilProof
instance PlusOneIsSucc n => PlusOneIsSucc (Su n) where
    proof = case proof :: Proof n of
        NilProof    -> ConsProof proof
        ConsProof _ -> ConsProof proof

rev :: PlusOneIsSucc n => Vec a n -> Vec a n
rev = go proof where
    go :: Proof n -> Vec a n -> Vec a n
    go NilProof Nil = Nil
    go (ConsProof p) (Cons x xs) = go p xs `append` Cons x Nil
事实上,也许是上面的
证明类型的有趣动机,我最初只是

data Proof n where Proof :: (n + Su Ze) ~ Su n => Proof n
但是,这不起作用:GHC正确地抱怨说,仅仅因为我们知道
(sun)+1=Su(sun)
,并不意味着我们知道
n+1=sun
,这是我们在
Cons
案例中递归调用
rev
所需要知道的。因此,我必须扩展
证明的含义,以包括自然所有等式的证明,包括
n
——本质上类似于从归纳到强归纳的强化过程

方法2

经过一番思考,我意识到这门课有点多余;这使得这种方法特别好,因为它不需要任何额外的扩展(甚至
ScopedTypeVariables
),也不会对
Vec
的类型引入任何额外的约束

data Proof n where
    NilProof  :: Proof Ze
    ConsProof :: (n + Su Ze) ~ Su n => Proof n -> Proof (Su n)

proofFor :: Vec a n -> Proof n
proofFor Nil = NilProof
proofFor (Cons x xs) = let rec = proofFor xs in case rec of
    NilProof    -> ConsProof rec
    ConsProof _ -> ConsProof rec

rev :: Vec a n -> Vec a n
rev xs = go (proofFor xs) xs where
    go :: Proof n -> Vec a n -> Vec a n
    go NilProof Nil = Nil
    go (ConsProof p) (Cons x xs) = go p xs `append` Cons x Nil
方法3

或者,如果将
rev
的实现切换一点,将最后一个元素置于列表的反向初始段上,那么代码看起来会更简单。(这种方法也不需要额外的扩展。)

方法4

就像方法3一样,但是再次注意到类型类不是必需的

initLast :: Vec a (Su n) -> (a, Vec a n)
initLast (Cons x xs) = case xs of
    Nil     -> (x, Nil)
    Cons {} -> case initLast xs of
        (x', xs') -> (x', Cons x xs')

rev :: Vec a n -> Vec a n
rev Nil = Nil
rev xs@(Cons {}) = case initLast xs of
    (x, xs') -> Cons x (rev xs')

为了防止有人无法访问7.6,如果
+
被前缀
Add
替换,并且只需要
{-#语言GADT、数据类型、类型族#-}
谢谢,请使用7.4.1进行4次类型检查!我还没有意识到我可以使用现有的值对Nat是什么进行案例分析。唯一的缺点是,每个证明都必须在运行时构造,并且需要线性时间,这可能会将整个线性算法转变为二次算法(在这种情况下,朴素的反面当然已经是二次的)。但是我认为应该可以一次列出一个完整的证明列表,然后将其传递给底层递归。PS我认为需要在运行时检查证明,这也意味着很遗憾需要运行程序,而不仅仅是对它们进行类型检查。否则,在更复杂的例子中,很容易意外地写出一个最基本的证明:-(在本文讨论的这一点上,引用Randy Pollack的格言是传统的,即“使用强规范化语言的伟大之处在于不必规范化事物”是的,要信任Haskell证明,必须检查它的计算结果是否为规范形式。这就是为什么我们没有无可辩驳的GADT模式。Coq或Agda中的证明组件不需要保留,更不用说在运行时执行,因为每个类型良好的闭式表达式都保证有规范值,但可以选择h值(在证明的情况下)并不重要。@pigworker:是的,我只是希望在类型级别上发生一些事情。不过有点乐观:-)
initLast :: Vec a (Su n) -> (a, Vec a n)
initLast (Cons x xs) = case xs of
    Nil     -> (x, Nil)
    Cons {} -> case initLast xs of
        (x', xs') -> (x', Cons x xs')

rev :: Vec a n -> Vec a n
rev Nil = Nil
rev xs@(Cons {}) = case initLast xs of
    (x, xs') -> Cons x (rev xs')