GADT的详尽检查失败了

时间:2013-04-25 21:46:23

标签: haskell types gadt

请考虑以下代码:

data (:+:) f g a = Inl (f a) | Inr (g a)

data A
data B

data Foo l where
  Foo :: Foo A

data Bar l where
  Bar :: Bar B

type Sig = Foo :+: Bar

fun :: Sig B -> Int
fun (Inr Bar) = 1

尽管 fun 是一个详尽的匹配,但在使用-Wall进行编译时,GHC会抱怨缺少案例。但是,如果我添加另一个构造函数:

data (:+:) f g a = Inl (f a) | Inr (g a)

data A
data B

data Foo l where
  Foo :: Foo A
  Baz :: Foo B

data Bar l where
  Bar :: Bar B

type Sig = Foo :+: Bar

fun :: Sig B -> Int
fun (Inr Bar) = 1
fun (Inl Baz) = 2

然后GHC正确检测到 fun 是完全的。

我在我的工作中使用与此类似的代码,如果我错过了案例,我希望GHC提出警告,如果不这样做,就不会发出警告。为什么GHC对第一个程序抱怨,如何在没有添加虚假构造函数或案例的情况下在没有警告的情况下编译第一个样本?

3 个答案:

答案 0 :(得分:13)

实际报告的问题是:

Warning: Pattern match(es) are non-exhaustive
         In an equation for `fun': Patterns not matched: Inl _

这是真的。您为Inr构造函数提供了案例,但没有为Inl构造函数提供案例。

您希望的是,由于无法提供使用Sig B构造函数的Inl类型的值(它需要类型为Foo B的参数,但是只有Foo的构造函数的类型为Foo A),ghc会注意到您不需要处理Inl构造函数。

麻烦的是,由于底部每种类型都有人居住。 是使用Sig B构造函数的Inl类型的值;甚至有非底价值。它们必须包含底部,但它们本身并不是底部。因此,程序可以评估对fun的调用无法匹配;这就是ghc警告的内容。

因此,要解决此问题,您需要将fun更改为以下内容:

fun :: Sig B -> Int
fun (Inr Bar) = 1
fun (Inl foo) = error "whoops"

但是现在当然如果你以后添加Baz :: Foo B这个功能是等待发生的定时炸弹。 ghc警告 是很好的,但实现这一目标的唯一方法是将匹配foo与当前详尽的模式进行模式匹配。不幸的是 没有有效的模式你可以放在那里! foo已知为Foo B类型,只有底部居住,您无法为底部编写模式。

但是你可以将它传递给一个接受多态类型Foo a的参数的函数。然后,该函数可以匹配所有当前存在的Foo构造函数,以便稍后添加一个构建函数时会收到警告。像这样:

fun :: Sig B -> Int
fun (Inr Bar) = 1
fun (Inl foo) = errorFoo foo
    where 
        errorFoo :: Foo a -> b
        errorFoo Foo = error "whoops"

现在你已经正确处理了:+:fun的所有构造函数,“不可能”的情况只是如果实际发生错误就会出错,如果你添加了Baz :: Foo B关于errorFoo中非详尽模式的警告,至少会引导您查看fun,因为它是在附加的where中定义的。

在缺点方面,当您向Foo添加不相关的构造函数时(更多类型为Foo A),您将不得不向errorFoo添加更多案例,这可能是不成功的(如果你有很多功能应用这种模式,那就容易和机械了。

答案 1 :(得分:7)

很抱歉告诉你这个,但你的第一个例子并不像你想象的那么详尽:

∀x. x ⊢ fun (Inl (undefined :: Foo B))
*** Exception: Test.hs:48:1-17: Non-exhaustive patterns in function fun

烦人,是的,但他们是休息。 ⊥这就是为什么我们不能拥有美好的东西。 :

答案 2 :(得分:1)

如上所述。您未处理的情况是Inl _|_,它本身不是_|_,因此必须处理。

幸运的是,有一种完美的方法来处理此问题:


data (:+:) f g a = Inl (f a) | Inr (g a)

data A
data B

data Foo l where
  Foo :: Foo A
  Baz :: Foo B

data Bar l where
  Bar :: Bar B

type Sig = Foo :+: Bar

fun :: Sig B -> Int
fun (Inr Bar) = 1
fun (Inl x) = case x of {}

现在,如果您确实添加了Baz :: Foo B构造函数,您将获得:

    Pattern match(es) are non-exhaustive
    In a case alternative: Patterns not matched: Baz
   |
21 | fun (Inl x) = case x of {}
   |               ^^^^

因此,您可以适当地将代码更改为类似于第二个示例的代码,以正确处理您创建的新案例。