Pattern Match Fail when `function [] _ = ...; function _ [] = ...` syntax is omitted

240 Views Asked by At

Though disjoint exhausts all possible patterns in its guard conditions, Haskell gives me a PatternMatchFail error when running it.

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.

However, it has no problem if I write:

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

Without those additional lines, I get a PatternMatchFail. If I am to infer what the issue for Haskell is in the first case, it is that if given a null list for an input argument, its expected arguments l@(x:xs) r@(y:ys) are already invoking a pattern-match, one that is non-exhaustive in the case of a null list, resulting in a PatternMatchFail, despite having a guard condition that checks for exactly the same condition. It just can't ever reach the guard condition, because it first needs to match on the "argument condition".

However, those additional two lines are a tad off-putting to me in their repetitiveness, and I was just wondering if there was a more succinct way of fixing this. More generally: if I were to be using three or more lists as arguments, I definitely wouldn't want to write out disjoint 3+ more times just to check for null conditions, so what might I do in cases like that? Thank you for your time.

2

There are 2 best solutions below

5
On BEST ANSWER

Your explaination for why this gives a pattern match failure is correct. You can write the code the following way to avoid redundant lines:

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 []

This is the solution I recommend. There is another solution, to make the pattern match more lazy (the pattern is then only checked if x/xs or y/ys is actually required):

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

I don't recommend doing this though, since checking for null explicitly does not feel like idiomatic Haskell (also, irrefutable patterns are rarely used). The problem with the second approach is that you have to take care that you don't access y/ys / x/xs in the null cases, and the compiler won't help you. The first approach guarrantes that you can't access them in the null cases.

0
On

Another way to avoid the duplication is to take advantage of pattern match/guard fall through:

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

This is overkill here and personally I prefer the explicit pattern matching over null (the style of the first code block in bennofs answer is what I would go for), but this general technique can be handy in some situations.