我正在关注GADT的Simon Peyton-Jones this lecture。在那里,声明了以下数据类型:
data T a where
T0 :: Bool -> T Bool
T1 :: T a
然后问的问题是以下函数的类型:
f x y = case x of
T0 _ -> True
T1 -> y
对我而言,似乎唯一可能的类型是:
f :: T a -> Bool -> Bool
但是,以下类型:
f :: T a -> a -> a
也有效!事实上,您可以使用f
,如下所示:
f (T1) "hello"
我的问题是为什么f
的第二种签名有效?
答案 0 :(得分:6)
f
的定义中有两种情况,两者都匹配您的第二种类型签名:
T0 _ -> True
此处您的参数类型为T Bool
,结果为Bool
。因此,这符合a ~ Bool
的类型签名。
T1 -> y
此处您的参数类型为T a
,结果为y
,其类型为a
。因此,这与任何a
的签名相匹配。
要理解为什么这是类型安全的,请问自己以下问题:有没有办法可以调用f
,结果与类型签名不匹配?如果你传递了a
和T a
,你还可以获得除a
以外的任何其他内容吗?
答案是:不,没有。如果您传入T0
(意为a
为Bool
),则会返回Bool
。如果您传入T1
,您将获得第二个参数,该参数也保证为a
类型。如果您尝试将其称为f (T1 :: T Int) "notAnInt"
,则无法编译,因为类型不匹配。换句话说:与类型签名匹配的函数的任何应用程序将根据签名生成正确的结果类型。因此f
是类型安全的。
答案 1 :(得分:3)
通常,输入检查
case e of
K1 ... -> e1
K2 ... -> e2
...
要求所有表达式ei
共享一个共同类型。
使用GADT时仍然如此,除了在每个分支中构造函数提供了一些已知在该分支中保存的类型相等方程T ~ T'
。因此,当检查所有ei
共享一个共同类型时,我们不再要求它们的类型相同,但只有在类型方程成立时才相等。
特别是:
f :: T a -> a -> a
f x y = -- we know x :: T a , y :: a
case x of
T0 _ -> -- provides a ~ Bool
True -- has type Bool
T1 -> -- provides a ~ a (useless)
y -- has type a
这里我们需要检查Bool ~ a
这一般是错误的,但是这里变成了现实,因为我们只需要在提供的等式a ~ Bool
下检查它。而且,在这种情况下,它变为现实!
(老实说,类型系统做的事略有不同, 检查两个分支是否等于在...中声明的类型 签名(在他们已知的平等之下) - 但让我保持简单。对于GADT模式匹配,总是需要某种形式的签名。)
请注意,这是GADT的重点 - 它们允许对模式匹配进行类型检查,其分支显然涉及不同类型。