使用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?我更喜欢我的类型签名中3
到Succ (Succ (Succ Zero)))
的外观。
编辑:由于目前似乎无法做到这一点(直到GHC 7.10)我会重新解释我的问题:任何人都可以展示我的为什么没有办法?遗憾的是,我还没有看过SMT求解器,所以我不知道原则上是否可行。
我的想法是,我对类型级别的计算不是很有经验,我想学会辨别我可以重新解释我的问题的情况,以便它能够正常工作,以及我的情况不能(这是(现在)后者的一个实例。)
答案 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))))