Search code examples
haskellnon-exhaustive-patterns

Haskell: Non-exhaustive pattern - Checking if list is ascending


I have no idea why my function doesn't work. I have gone through all the posts about non-exhaustive functions but my functions fulfills all possible options as far as I can see.

ascending :: [Int] -> Bool
ascending []                = error "Empty list given"
ascending [x]               = True
ascending [x,y]     | y>=x  = True
                    | x<y   = False
ascending (x:y:xs)  | y>=x  = (ascending (y:xs))
                    | x<y   = False

Result:

*Main> ascending []
*** Exception: Empty list given
*Main> ascending [1]
True
*Main> ascending [1, 2]
True
*Main> ascending [2, 1]
*** Exception: test01.hs:(51,1)-(56,55): Non-exhaustive patterns in function ascending

It works for one pair, but not if the pair is not ascending. When I follow my code it should be just returning False.


Solution

  • Have a closer look at the guards for your [x,y] pattern:

    ascending [x,y] | y>=x = True
                    | x<y  = False
    

    When applied to [2,1], the first guard is checked and evaluates to False (because 2 >= 1); then, the second guard is checked, but it also evaluates to False (because 1 < 2). Therefore, the next pattern is used (because [2,1] also matched (x:y:ys)), but exactly the same thing happens. Because this was the last pattern, GHC rightfully screams at you.

    The inequalities in your guards are not complementary. Your third pattern should read

    ascending [x,y] | x <= y = True
                    | x >  y = False
    

    or, to leave less room for error,

    ascending [x,y] | x <= y    = True
                    | otherwise = False
    

    However, there is still much room for improvement. In particular:

    • The third pattern overlaps with the fourth one.
    • Because your function returns a Bool, using guards only to explicitly return a Boolean value is redundant.
    • Because, by convention (see dfeuer's comment), the empty list is considered to be ascending, you don't need to throw an error upon encountering it (unless you're following your own whimsical convention).

    Taking all this into account, you could have simply written

    ascending :: [Int] -> Bool
    ascending (x:y:xs) = x <= y && ascending (y:xs)
    ascending _        = True
    

    Finally, you can condense the code some more with a combination of and and zipWith:

    ascending :: [Int] -> Bool
    ascending xs = and $ zipWith (<=) xs (tail xs)