在Haskell中使用类型的冒险:GADT:为什么以下的类型检查?

时间:2016-10-19 16:23:25

标签: haskell functional-programming type-inference gadt

我正在关注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的第二种签名有效?

2 个答案:

答案 0 :(得分:6)

f的定义中有两种情况,两者都匹配您的第二种类型签名:

T0 _ -> True

此处您的参数类型为T Bool,结果为Bool。因此,这符合a ~ Bool的类型签名。

T1 ->  y

此处您的参数类型为T a,结果为y,其类型为a。因此,这与任何a的签名相匹配。

要理解为什么这是类型安全的,请问自己以下问题:有没有办法可以调用f,结果与类型签名不匹配?如果你传递了aT a,你还可以获得除a以外的任何其他内容吗?

答案是:不,没有。如果您传入T0(意为aBool),则会返回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的重点 - 它们允许对模式匹配进行类型检查,其分支显然涉及不同类型。