简体   繁体   English

GADT的详尽检查失败了

[英]GADT's failed exhaustiveness checking

Consider the following code: 请考虑以下代码:

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

Even though fun is an exhaustive match, when compiling with -Wall, GHC complains about a missing case. 尽管有趣是一场详尽的比赛,但在使用-Wall进行编译时,GHC会抱怨丢失案例。 However, if I add another constructor: 但是,如果我添加另一个构造函数:

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

Then GHC correctly detects that fun is total. 然后GHC正确地检测到乐趣是完全的。

I am using code similar to this in my work, and would like GHC to raise warnings if I have missed cases, and not raise warnings if I don't. 我在我的工作中使用的代码类似于此,如果我错过了案例,我希望GHC提出警告,如果不这样做,就不会发出警告。 Why is GHC complaining on the first program, and how can I get the first sample to compile without warnings without adding spurious constructors or cases? 为什么GHC会对第一个程序抱怨,如何在没有添加虚假构造函数或案例的情况下在没有警告的情况下编译第一个样本?

The problem actually reported is: 实际报告的问题是:

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

Which is true. 这是真的。 You provide a case for the Inr constructor, but not the Inl constructor. 您为Inr构造函数提供了一个案例,但没有为Inl构造函数提供案例。

What you're hoping is that since there's no way to provide a value of type Sig B that uses the Inl constructor (it would need an argument of type Foo B , but the only constructor for Foo is of type Foo A ), that ghc will notice that you don't need to handle the Inl constructor. 什么你希望的是,因为没有办法提供类型的值Sig B使用的Inl的构造函数(它需要类型的参数Foo B ,但唯一的构造Foo的类型为Foo A ),即GHC会注意到你不需要处理Inl构造函数。

The trouble is that due to bottom every type is inhabited. 麻烦的是,由于底部每种类型都有人居住。 There are values of type Sig B that use the Inl constructor; 类型的值Sig B使用该Inl构造; there are even non-bottom values. 甚至有非底价值。 They must contain bottom, but they are not themselves bottom. 它们必须包含底部,但它们本身不是底部。 So it is possible for the program to be evaluating a call to fun that fails to match; 因此,程序可以评估一个无法匹配的fun调用; that's what ghc is warning about. 这就是ghc警告的内容。

So to fix that you need to change fun to something like this: 因此,要解决这个问题,你需要改变fun的东西是这样的:

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

But now of course if you later add Baz :: Foo B this function is a time bomb waiting to happen. 但是现在当然如果你以后添加Baz :: Foo B这个功能就是等待发生的定时炸弹。 It's be nice for ghc to warn about that , but the only way to make that happen is to pattern match foo against a currently-exhaustive set of patterns. ghc对此提出警告是很好的,但实现这一目标的唯一方法是将foo模式与当前详尽的模式进行模式化。 Unfortunately there are no valid patterns you can put there! 不幸的 ,你可以把有没有有效的模式! foo is known to be of type Foo B , which is only inhabited by bottom, and you can't write a pattern for bottom. foo被称为Foo B类型,它只有底部居住,你不能为底部写一个模式。

But you could pass it to a function that accepts an argument of polymorphic type Foo a . 但是你可以将它传递给一个接受多态类型Foo a的参数的函数。 That function could then match against all the currently-existing Foo constructors, so that you'll get a warning if you later add one. 然后,该函数可以匹配所有当前存在的Foo构造函数,这样如果以后添加一个,就会收到警告。 Something like this: 像这样的东西:

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

Now You've properly handled all the constructors of :+: in fun , the "impossible" case simply errors out if it ever actually occurs and if you ever add Baz :: Foo B you get a warning about a non-exhaustive pattern in errorFoo , which is at least directing you to look at fun because it's defined in an attached where . 现在你已经正确处理了所有的构造函数:+:fun的情况下,“不可能”的情况只是错误输出,如果它实际发生过,如果你曾经添加Baz :: Foo B你会得到关于非详尽模式的警告errorFoo ,它至少指导你看看fun因为它是在附加的where定义的。

On the downside, when you add unrelated constructors to Foo (say more of type Foo A ) you'll have to add more cases to errorFoo , and that could be unfun (though easy and mechanical) if you've got lots of functions applying this pattern. 在缺点方面,当你向Foo添加不相关的构造函数时(更多的是类型为Foo A ),你将不得不向errorFoo添加更多的情况,如果你有很多函数应用,这可能是不成功的(虽然简单和机械)这种模式。

I'm sorry to tell you this, but your first example is not quite as exhaustive as you think it is: 很抱歉告诉你这个,但你的第一个例子并不像你想象的那样详尽:

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

Annoying, yes, but them's the breaks. 很烦人,是的,但他们是休息。 ⊥ is why we can't have nice things. ⊥这就是为什么我们不能拥有美好的东西。 :[ :[

As already mentioned. 如前所述。 The case you are not handling is Inl _|_ , which is not itself _|_ , and thus must be handled. 你没有处理的情况是Inl _|_ ,它本身不是_|_ ,因此必须处理。

Luckily there is a perfectly nice way to handle this: 幸运的是,有一个非常好的方法来处理这个:


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 {}

Now if you do add in the Baz :: Foo B constructor, you will appropriately get: 现在,如果您添加了Baz :: Foo B构造函数,您将适当地得到:

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

Thus you can appropriately change the code to something like your second example to properly handle the new case you have created. 因此,您可以适当地将代码更改为类似于第二个示例的内容,以正确处理您创建的新案例。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM