Is `otherwise` mandatory in guards?

I was trying to re-implement Data.List.range as an exercise, but it didn’t work out as hoped:

rangeMine :: Int -> Int -> List Int
rangeMine start end
  | start == end = start : Nil
  | start < end = start : rangeMine (start + 1) end
  | start > end = start : rangeMine (start - 1) end

This results in the following error:

A case expression could not be determined to cover all inputs.
The following additional cases are required to cover all inputs:

   _ _

Alternatively, add a Partial constraint to the type of the enclosing value.

while checking that type Partial => t0
    is at least as general as type List Int
while checking that expression

case start end of                                                
  start end | (eq start) end -> (Cons start) Nil                 
            | (lessThan start) end -> (Cons start) ((...) end)   
            | (greaterThan start) end -> (Cons start) ((...) end)

    has type List Int
in binding group rangeMine

where t0 is an unknown type

See https://github.com/purescript/documentation/blob/master/errors/NoInstanceFound.md for more information, or to contribute content related to this error.

The first part of the error message clear: the guard cases are not exhaustive; when I replace start > end with otherwise, it does work. (Unfortunately, I’m unable to decipher the error message right below “add a Partial constraint” yet…)


If start and end are Ints and I covered the “lower than”, “greater than”, and “equal” cases, what case(s) did I miss?

1 Like

You didn’t miss any cases, the problem is the compiler isn’t smart enough to work out that you have covered all the options. That would require reasoning about the properties of Integers that it can’t do. otherwise isn’t always needed but it does make it trivial for the compiler to notice that it covers all remaining options.

The “partial constraint” mentioned in the error message is a way of telling the compiler that you are deliberately not covering all the options (Explained here in the Purescript book), but that’s not what you want in this case.

2 Likes

For this particular case, you can replace your > guard with otherwise since it must be true. But you could also rewrite it to case on Ordering.

rangeMine start end = case compare start end of
  EQ -> start : Nil
  LT -> start : rangeMin (start + 1) end
  GT -> start : rangeMin (start - 1) end

The compiler only refines exhaustivity of explicit case matrices. It does not propagate refinement information through boolean guards, so yeah, guards always need an otherwise unless there is some other case pattern to fallthrough to.

3 Likes

That’s exactly what I needed. My problem with otherwise in cases like this is that it would hide the last case, but your example enumerates all of them without the need to resort to comments. Thanks!