Haskell: Why is the pattern matching not exhaustive even if it compiles and executes?

Viewed 109

Learning Haskell, bit confused about the code hint here:

getMax :: [Int] -> Int
getMax [a] = a
getMax (a:b:as) = getMax ((if a >= b then a else b):as)
Pattern match(es) are non-exhaustive
In an equation for ‘getMax’: Patterns not matched: []

It works and finds the max of eg [5, 2, 6]. Does it need a pattern for the empty list? What's a reasonable return value then? Zero? Can I return something like undefined?

2 Answers

Does it need a pattern for the empty list?

Not per se, since it is just a warning. As long as you never pass it an empty list, it will here work fine. But it is up to you to check that, and you can never be sure that somehow an empty list will be passed.

What's a reasonable return value then? Zero? Can I return something like undefined?

That is of course up to you to decide. You probably might want to make a distinction between the result of an empty list an a non-empty list. In Haskell usually therefore a Maybe is used. For example:

getMax :: [Int] -> Maybe Int
getMax [] = Nothing
getMax [a] = Just a
getMax (a:b:as) = getMax ((if a >= b then a else b):as)

or you can change the input, such that it is for example impossible to pass a non-empty list. We can here work with two parameters, so the signature is then getMax :: Int -> [Int] -> Int. Here the first parameter is the "head" of the list, and the second is the tail of the list:

getMax :: Int -> [Int] -> Int
getMax a [] = a
getMax a (b:as) = getMax (if a >= b then a else b) as

Using undefined is usually not a good idea. Yes than we no longer have a non-exhaustive pattern, but evaluating the undefined will raise an error. So it is only trading one problem for another one.

If your function can only work on non-empty lists, then you should indicate that in its type:

import Data.List.NonEmpty (NonEmpty(..))

getMax :: NonEmpty Int -> Int
getMax (a:|[]) = a
getMax (a:|b:as) = getMax ((if a >= b then a else b):|as)

This is strictly more useful than using Maybe directly, as then you don't have to handle a Nothing that can never happen if you're sure the list isn't empty. (And if you're not sure, then you can call fmap getMax . nonEmpty instead, which is exactly the same as getMax would be had it used Maybe.)

Related