Ошибка проверки 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

Несмотря на то, что удовольствие - исчерпывающее совпадение, при компиляции с -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 правильно определяет, что забава является полной.

Я использую код, подобный этому в своей работе, и хотел бы, чтобы GHC поднимал предупреждения, если я пропустил дела, и не поднимаю предупреждения, если я этого не делаю. Почему GHC жалуется на первую программу и как я могу скомпилировать первый образец без предупреждений без добавления ложных конструкторов или случаев?

Ответы

Ответ 1

Фактическая информация о проблеме:

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, и это может быть несвоевременным (хотя и легким и механическим), если у вас есть много функций, применяющих этот шаблон.

Ответ 2

Прошу прощения, чтобы сказать вам об этом, но ваш первый пример не настолько исчерпывающий, как вы думаете:

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

Раздражающий, да, но это перерывы. ⊥ почему мы не можем иметь приятные вещи.: [