Почему неисчерпывающие защитники вызывают неопровержимое совпадение шаблонов?

У меня есть эта функция в Haskell:

test :: (Eq a) => a -> a -> Maybe a
test a b
  | a == b = Just a
test _ _ = Nothing

Это то, что я получил, когда пробовал функцию с разными входами:

ghci>test 3 4
Nothing
ghci>test 3 3
Just 3

Согласно Real World Haskell, первый образец неопровержимый. Но похоже, что test 3 4 не выдает первый шаблон и не соответствует второму. Я ожидал какой-то ошибки - может быть, "неисчерпывающих стражей". Итак, что действительно происходит здесь, и есть ли способ включить предупреждения компилятора в случае, если это произойдет случайно?

Ответ 1

Первый шаблон действительно является "неопровержимым шаблоном", однако это не означает, что он всегда будет выбирать соответствующую правую часть вашей функции. Он по-прежнему подвергается защите, который может потерпеть неудачу, как в вашем примере.

Чтобы обеспечить защиту всех случаев, обычно используется otherwise для окончательной защиты, которая всегда будет успешной.

test :: (Eq a) => a -> a -> Maybe a
test a b
  | a == b    = Just a
  | otherwise = Nothing

Обратите внимание, что в otherwise нет ничего волшебного. Он определен в прелюдии как otherwise = True. Тем не менее, идиоматично использовать otherwise для окончательного случая.

Предоставление компилятору предупреждений о неустранимых защитниках было бы невозможно в общем случае, так как оно включало бы решение проблемы с остановкой, однако существуют такие инструменты, как Catch, которые пытаются выполнить лучшую работу, чем компилятор при определении того, охвачены ли все случаи или нет в общем случае.

Ответ 2

Компилятор должен предупредить вас, если вы оставите второе предложение, т.е. если в вашем последнем совпадении есть набор охранников, где последний не является тривиально истинным.

Как правило, проверка защит для полноты, очевидно, невозможна, так как это будет так же сложно, как решить проблему остановки.

Ответа на комментарий Matt:

Посмотрите на пример:

foo a b 
   | a <= b = True
   | a >  b = False

Человек может видеть, что один из обоих охранников должен быть правдой. Но компилятор не знает, что либо a <= b, либо a > b.

Теперь ищите другой пример:

fermat a b c n 
    | a^n + b^n /= c^n = ....
    | n < 0 = undefined
    | n < 3 = ....

Чтобы доказать, что множество охранников завершено, компилятор должен был доказать последнюю теорему Ферма. Это невозможно сделать в компиляторе. Помните, что количество и сложность охранников не ограничены. Компилятор должен быть общим решателем математических задач, проблем, которые указаны в самом Haskell.

Более формально, в самом простом случае:

 f x | p x = y

компилятор должен доказать, что если p x не является дном, то p x есть True для всех возможных x. Другими словами, он должен доказать, что либо p x является нижней (не останавливается), независимо от того, что x соответствует или оценивается True.

Ответ 3

Охранники не являются неопровержимыми. Но очень распространенная (и хорошая) практика заключается в том, чтобы добавить одного последнего защитника, который поймает другие случаи, поэтому ваша функция будет выглядеть следующим образом:

test :: (Eq a) => a -> a -> Maybe a
test a b
  | a == b = Just a
  | True = Nothing