Подтвердить что ты не робот

В Haskell, почему неисчерпывающие шаблоны не являются ошибками во время компиляции?

Это продолжение Почему я получаю "Неисчерпывающие шаблоны в функции..." когда я вызываю свою подстроку Haskell?

Я понимаю, что, используя -Wall, GHC может предупреждать о не исчерпывающих шаблонах. Мне интересно, почему причина не делает ошибку компиляции по умолчанию, учитывая, что всегда можно явно определить частичную функцию:

f :: [a] -> [b] -> Int
f [] _  = error "undefined for empty array"
f _ []  = error "undefined for empty array"
f (_:xs) (_:ys) = length xs + length ys

Вопрос не связан с GHC.

Это потому, что...

  • Никто не хотел принуждать компилятор Haskell выполнять такой анализ?
  • не исчерпывающий поиск по шаблону может найти некоторые, но не все случаи?
  • частично определенные функции считаются законными и используются достаточно часто, чтобы не налагать вид конструкции, показанный выше? Если это так, не могли бы вы объяснить мне, почему неисчерпывающие шаблоны полезны/законны?
4b9b3361

Ответ 1

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

fac 0 = 1
fac n | n > 0 = n * fac (n-1)

Это не исчерпывающее (отрицательные числа не соответствуют ни одному случаю) не имеет большого значения для типичного использования факториальной функции.

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

mod2 :: Integer -> Integer
mod2 n | even n = 0
mod2 n | odd n  = 1

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

Ответ 2

Вы можете использовать -Werror, чтобы превращать предупреждения в ошибки. Я не знаю, можете ли вы включить ошибки, не являющиеся исчерпывающими, в ошибки, извините!

Что касается третьей части вашего вопроса:

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

Пример с игрушкой:

duplicate :: [a] -> [a]
duplicate [] = []
duplicate (x:xs) = x : x : xs

removeDuplicates :: Eq a => [a] -> [a]
removeDuplicates [] = []
removeDuplicates (x:y:xs) | x == y = x : removeDuplicates xs

Теперь довольно легко увидеть, что removeDuplicates (duplicate as) равно as (всякий раз, когда тип элемента находится в Eq), но в целом duplicate (removeDuplicates bs) будет сбой, потому что есть нечетное число элементов или 2 последовательные элементы различаются. Если это не сбой, это потому, что bs было создано (или могло быть произведено) duplicate во-первых!!

Итак, мы имеем следующие законы (недействительный Haskell):

removeDuplicates . duplicate == id
duplicate . removeDuplicates == id (for values in the range of duplicate)

Теперь, если вы хотите предотвратить неисчерпывающие шаблоны здесь, вы можете сделать removeDuplicates return Maybe [a] или добавить сообщения об ошибках для отсутствующих случаев. Вы даже можете что-то сделать в соответствии с

newtype DuplicatedList a = DuplicatedList [a]

duplicate :: [a] -> DuplicatedList a
removeDuplicates :: Eq a => DuplicatedList a -> [a]
-- implementations omitted

Все это необходимо, потому что вы не можете легко выразить "быть списком четной длины с последовательными парами элементов, равными" в системе типа Haskell (если вы не Олег:)

Но если вы не экспортируете removeDuplicates, я думаю, что здесь совершенно нормально использовать не исчерпывающие шаблоны. Как только вы его экспортируете, вы потеряете контроль над входами и будете иметь дело с отсутствующими случаями!