Ошибка совпадения с образцом, когда `function [] _ ​​= ...; Синтаксис функции _ [] = ...`опущен

Хоть disjoint исчерпывает все возможные паттерны в своих условиях охраны, Хаскелл дает мне PatternMatchFail ошибка при запуске.

disjoint :: (Ord a) => [a] -> [a] -> Bool
disjoint l@(x:xs) r@(y:ys)
    | null l || null r   = True
    | x == y             = False
    | x > y              = disjoint l ys -- reduce right list
    | otherwise          = disjoint xs r -- reduce left list
-- | Terminates when either list has been reduced to null, or when their head
-- elements are equal. Since lists are ordered, it only needs to compare head elements.

Тем не менее, это не проблема, если я напишу:

disjoint :: (Ord a) => [a] -> [a] -> Bool
disjoint [] _ = True
disjoint _ [] = True
disjoint l@(x:xs) r@(y:ys)
--  | null l || null r   = True -- now redundant, but included for sake of continuity
    | x == y             = False
    | x > y              = disjoint l ys -- reduce right list
    | otherwise          = disjoint xs r -- reduce left list

Без этих дополнительных строк я получаю PatternMatchFail, Если я хочу сделать вывод, что проблема для Haskell в первом случае, то это то, что если задан нулевой список для входного аргумента, его ожидаемые аргументы l@(x:xs) r@(y:ys) уже вызывают сопоставление с шаблоном, которое не является исчерпывающим в случае нулевого списка, что приводит к PatternMatchFailнесмотря на наличие охранного состояния, которое проверяет точно такое же состояние. Он просто не может достичь состояния охраны, потому что сначала должен соответствовать "условию аргумента".

Тем не менее, эти две дополнительные строки немного отталкивают меня в их повторяемости, и мне просто интересно, есть ли более лаконичный способ исправить это. В более общем смысле: если бы я использовал три или более списков в качестве аргументов, я бы определенно не хотел выписывать несвязанные более 3 раз просто для проверки на нулевые условия, так что я мог бы сделать в подобных случаях? Спасибо за ваше время.

2 ответа

Решение

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

disjoint :: (Ord a) => [a] -> [a] -> Bool
disjoint l@(x:xs) r@(y:ys)
    | x == y             = False
    | x > y              = disjoint l ys -- reduce right list
    | otherwise          = disjoint xs r -- reduce left list
disjoint _ _ = True -- catch all pattern, executed if either l or r is []

Это решение, которое я рекомендую. Существует еще одно решение: сделать сопоставление с шаблоном более ленивым (шаблон проверяется только в том случае, если x/xs или же y/ys действительно требуется):

disjoint :: (Ord a) => [a] -> [a] -> Bool
disjoint l@ ~(x:xs) r@ ~(y:ys) -- the ~ here says that this is an irrefutable pattern, which makes the match more lazy
    | null l || null r   = True -- x/y is not required, so pattern not checked
    | x == y             = False
    | x > y              = disjoint l ys -- reduce right list
    | otherwise          = disjoint xs r -- reduce left list

Я не рекомендую делать это, так как проверка на null явно не похоже на идиоматический Haskell (также, неопровержимые образцы используются редко). Проблема второго подхода заключается в том, что вы должны позаботиться о том, чтобы вы не y/ys / x/xs в нулевых случаях, и компилятор вам не поможет. Первый подход гарантирует, что вы не можете получить к ним доступ в нулевых случаях.

Другой способ избежать дублирования состоит в том, чтобы воспользоваться преимуществом сопоставления с образцом / защиты:

disjoint :: (Ord a) => [a] -> [a] -> Bool
disjoint l r
    | null l || null r   = True
       -- If the guard above fails, then this pattern match is attempted:
disjoint l@(x:xs) r@(y:ys)
    | x == y             = False
    | x > y              = disjoint l ys -- reduce right list
    | otherwise          = disjoint xs r -- reduce left list

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

Другие вопросы по тегам