使用TypeLits附加类型级编号列表

时间:2014-07-14 10:41:12

标签: haskell

使用GHC.TypeLits,我们可以编写一个简单的类型级编号列表(或矢量。)

> {-# LANGUAGE TypeOperators, KindSignatures, GADTs, DataKinds, ScopedTypeVariables #-}

> import GHC.TypeLits

> data Vec :: * -> Nat -> * where
>   VNil :: Vec e 0
>   (:-) :: e -> Vec e n -> Vec e (n+1)

这是带有TypeLits的规范向量定义。直观地说,追加操作应如下所示:

> vecAppend :: Vec e n -> Vec e m -> Vec e (n + m)
> vecAppend VNil vec = vec
> vecAppend (a :- as) vec = a :- vecAppend as vec

但GHC的解算器在算术上遇到了麻烦:

Could not deduce (((n1 + m) + 1) ~ (n + m))
from the context (n ~ (n1 + 1))

当然,自n1 + 1 ~ n(n1 + m) + 1 ~ n1 + 1 + m ~ n + m以来,求解器似乎不知道+的交换性和相关性(类型函数一般不可交换)或者联想!)

我知道有可能I define type-level Peano naturals,但我想知道是否有办法在GHC中使用当前类型nats的实现(7.8.0 here。)

所以我试着帮忙:

> vecAppend :: Vec e (n+1) -> Vec e m -> Vec e ((n + 1) + m)
> vecAppend VNil vec = vec
> vecAppend (a :- as) vec = a :- vecAppend as vec

但这只是将问题推迟到类型变量的正确实例化。

Could not deduce (((n1 + 1) + m) ~ ((n + m) + 1))
from the context ((n + 1) ~ (n1 + 1))
  bound by a pattern with constructor
             :- :: forall e (n :: Nat). e -> Vec e n -> Vec e (n + 1),
       in an equation for ‘vecAppend’
NB: ‘+’ is a type function, and may not be injective
Expected type: Vec l ((n + 1) + m)
  Actual type: Vec l ((n + m) + 1)
Relevant bindings include
  l :: Vec l m
  as :: Vec l n1
  vecAppend :: Vec l (n + 1) -> Vec l m -> Vec l ((n + 1) + m)

还有两个更像这个。

所以让我们来看看它们。

> vecAppend ∷ ∀ e n m. Vec e (n+1) → Vec e m → Vec e (n + 1 + m)
> vecAppend VNil l = l
> vecAppend ((a :- (as ∷ Vec e n)) ∷ Vec e (n+1)) (l ∷ Vec e m) = a :- (vecAppend as l ∷ Vec e (n+m))

唉,

Could not deduce (n1 ~ n)
from the context ((n + 1) ~ (n1 + 1))
  bound by a pattern with constructor
             :- :: forall e (n :: Nat). e -> Vec e n -> Vec e (n + 1),
           in an equation for ‘vecAppend’
  ‘n1’ is a rigid type variable bound by
       a pattern with constructor
         :- :: forall e (n :: Nat). e -> Vec e n -> Vec e (n + 1),
       in an equation for ‘vecAppend’
  ‘n’ is a rigid type variable bound by
      the type signature for
        vecAppend :: Vec e (n + 1) -> Vec e m -> Vec e ((n + 1) + m)
Expected type: Vec e n1
  Actual type: Vec e n
Relevant bindings include
  vecAppend :: Vec e (n + 1) -> Vec e m -> Vec e ((n + 1) + m)
In the pattern: as :: Vec e n
In the pattern: a :- (as :: Vec e n)
In the pattern: (a :- (as :: Vec e n)) :: Vec e (n + 1)

有没有办法用当前的解算器做到这一点,而无需定义自己的Peano nats?我更喜欢我的类型签名中3Succ (Succ (Succ Zero)))的外观。

编辑:由于目前似乎无法做到这一点(直到GHC 7.10)我会重新解释我的问题:任何人都可以展示我的为什么没有办法?遗憾的是,我还没有看过SMT求解器,所以我不知道原则上是否可行。

我的想法是,我对类型级别的计算不是很有经验,我想学会辨别我可以重新解释我的问题的情况,以便它能够正常工作,以及我的情况不能(这是(现在)后者的一个实例。)

1 个答案:

答案 0 :(得分:12)

当然有办法。它不是一种好的方式,但有一种方法..将unsafeCoerce放入语言的整个目的是为了知道某些内容被正确输入的情况,但GHC无法计算它本身。所以..有办法。

该故事应该在GHC 7.10中有显着改善。目前的计划是包括一个SMT求解器,用于处理类型级的Nat值。

修改

喔。至于为什么Peano自然很容易,而且类型级文字很难:使用Peano自然,添加一个是应用类型构造函数。 GHC知道应用类型构造函数是一个内射运算。事实上,它是GHC类型系统的关键点之一。因此,当您使用Peano天然产品时,您只需使用GHC已经非常适合处理的结构。

相比之下,GHC并不知道有关算术的愚蠢事情。它不知道(+1)Nat上的内射函数。因此,它无法知道它可以从m ~ n派生(m + 1) ~ (n + 1)。它对Nat算术的基本属性也没有任何想法,比如关联,分配和交换属性。集成SMT求解器背后的想法是SMT求解器非常善于处理这些属性。

使用GHC 7.8,你可以毫不费力地将类型级文字翻译成Peano自然文字,但是:

{-# LANGUAGE DataKinds, TypeFamilies, TypeOperators, UndecidableInstances #-}

import GHC.TypeLits

data Z
data S a

type family P (n :: Nat) where
    P 0 = Z
    P n = S (P (n - 1))

这利用了新的封闭类型族功能来创建类型函数P,用于从文字转换为Peano表示,如下所示:

*Main> :t undefined :: P 5
undefined :: P 5 :: S (S (S (S (S Z))))