我有以下GADT
data Vec n a where
T :: Vec VZero a
(:.) :: (VNat n) => a -> (Vec n a) -> (Vec (VSucc n) a)
使用类
对固定长度向量进行建模class VNat n
data VZero
instance VNat VZero
data VSucc n
instance (VNat n) => VNat (VSucc n)
我尝试在vector上编写一个append-function:
vAppend :: Vec n b -> Vec m b -> Vec nm b
vAppend T T = T -- nonsense, -- its just a minimal def for testing purposes
类型检查器不喜欢它:
Could not deduce (nm ~ VZero)
from the context (n ~ VZero)
bound by a pattern with constructor
T :: forall a. Vec VZero a,
in an equation for `vAppend'
at VArrow.hs:20:9
or from (m ~ VZero)
bound by a pattern with constructor
T :: forall a. Vec VZero a,
in an equation for `vAppend'
at VArrow.hs:20:11
`nm' is a rigid type variable bound by
the type signature for vAppend :: Vec n b -> Vec m b -> Vec nm b
at VArrow.hs:20:1
Expected type: Vec nm b
Actual type: Vec VZero b
In the expression: T
In an equation for `vAppend': vAppend T T = T
Failed, modules loaded: Vectors.
任何人都可以用类型变量nm
解释GHC问题吗?究竟什么意思是错误消息中的~
?
答案 0 :(得分:10)
就目前而言,你说你可以通过附加任何两个向量来获得任何长度的向量。如果你废弃签名ghc
推断vAppend
应该在任意两个向量的情况下产生长度为VZero的向量 - 这是有道理的,但不是你想要的。您需要与Plus
关联的VNat
类型来约束向量上vAppend
的结果类型。自然的方式是某种类型的家庭,但我无法在VNat
类下得到它。在任何情况下,使用DataKinds
扩展的推广类型(在ghc-7.4及更高版本中)可以更清楚地实现这一整个想法 - 尽管您可能是故意避免扩展?这消除了VSucc
的令人讨厌的未闭合字符,它承认VSucc Char
等。如果您不想避免DataKinds
,那么您的模块可能看起来像这样:
{-#LANGUAGE GADTs, TypeFamilies, DataKinds, TypeOperators#-}
data Vec n a where -- or: data Vec :: VNat -> * -> * where
T :: Vec VZero a
(:.) :: a -> Vec n a -> Vec (VSucc n) a
-- no class constraint
data VNat = VZero | VSucc VNat -- standard naturals
type family n :+ m :: VNat -- note the kind of a ":+" is a promoted VNat
type instance VZero :+ n = n
type instance VSucc n :+ m = VSucc (n :+ m)
vAppend :: Vec n b -> Vec m b -> Vec (n :+ m) b
vAppend T v = v
vAppend (a :. u) v = a :. (vAppend u v)
编辑:我看到这一点,Plus
- 或:+
- 类型系列的行应该明确约束参数的类型
type family (n::VNat) :+ (m::VNat) :: VNat
保留像Char :+ VZero
之类的垃圾类型等等 - 也就是说,使用与DataKinds
之类的data VSucc n
类型相同的原则。此外,我们可以看到两个实例完全指定它,但我不知道编译器可以使用多少这个。
答案 1 :(得分:9)
类型签名中的所有类型变量都是普遍量化的。这意味着,如果您说该函数具有类型
Vec n b -> Vec m b -> Vec nm b
然后,对于n
,m
,nm
和b
的任何选择,此类型必须有效。特别是,例如,
Vec VZero Int -> Vec VZero Int -> Vec (VSucc VZero) Int
也必须是您的函数的有效类型。但是,附加两个向量不具有这种通用类型。 nm
存在约束,即nm
是类型级数n
和m
的总和。您必须在函数类型中表达这些约束,否则您将获得类型错误。
在您的情况下,GHC抱怨说,在您的定义中,nm
实际上是VZero
,因此您假设您的类型表明您不允许nm
~
。 {{1}}只是GHC的类型相等符号。
答案 2 :(得分:6)
当通过模式匹配在GADT的值上编写函数时,GHC在类型检查每个子句时使用有关函数运行时的预期行为的信息。您的vAppend
函数只有一个子句,该模式匹配类型Vec n b
的值和另一个类型Vec m b
的值。 GHC的原因是,如果在运行时vAppend
应用于与模式T
匹配的实际参数,那么实际参数的实际类型必须是Vec VZero b
形式,这是一个比Vec n b
或Vec m b
更具信息性的类型。在GHC中实现这种推理的方式是,当类型检查vAppend
的唯一子句的RHS时,它会记录n
必须确实是VZero
的假设,写{{1}同样n ~ VZero
。
您为函数编写的类型列出了必须履行的合同。您获得的错误消息是因为m ~ VZero
的实现必须履行的合同过于笼统。你是说给定两个长度为vAppend
和n
的向量,m
必须产生一个可以被认为是任何大小的向量。实际上,GHC注意到您的实施不符合此合同,因为您的RHS类型vAppend
与RHS的预期类型Vec VZero b
不匹配,并且没有假设{ {1}}。实际上,GHC告诉我们,唯一可用的假设是前一段中的假设。
您履行此合同的唯一可能方法是将Vec nm b
写为您的条款的RHS。这显然不是你想要的。为nm ~ VZero
获取正确类型的技巧是以某种方式将输出向量的大小与两个输入向量的相应大小相关联。这可能是这样的:
undefined
我们在这里所做的是说,长度由vAppend
的输入长度决定,通过一些名为type family Plus n m
type instance Plus VZero m = m
type instance Plus (VSucc n) m = VSucc (Plus n m)
vAppend :: Vec n b -> Vec m b -> Vec (Plus n m) b
vAppend T T = T
的类型的函数来确定。在两个输入长度均为vAppend
的情况下,我们知道Plus
与VZero
和Plus n m
之后的Plus VZero VZero
相同。由于n ~ VZero
具有第一类型族实例的形状,因此GHC知道它与m ~ VZero
相同。因此,在这个分支中,GHC期望Plus VZero VZero
类型的RHS,我们可以很容易地构建它!