3

Before you dismiss this as a duplicate

I see that at least as of September 2018, GHCI does not allow you to disable a warning locally (although you can in a whole file).

But maybe there's some other way to let GHCI know that every case is in fact being handled?

The Question

An idiom I use sometimes is to write a function where the first definition(s) tests for some predicate(s) and returns Left, and the other definitions consider arguments where the operation actually makes sense. Whenever I do that, I get a "Pattern match(es) are non-exhaustive" error, but I really am checking for every condition.

Example

(For the real-world code motivating this toy example, see e.g. the definition of pExprToHExpr here.)

This code:

{-# LANGUAGE ViewPatterns #-}

data Cowbell = Cowbell
  deriving Show
data Instrument = Rattle
                | Drums (Maybe Cowbell)
                | Violin
                | Oboe
  deriving Show

pitched :: Instrument -> Bool
pitched Rattle                 = False
pitched (Drums Nothing)        = False
pitched (Drums (Just Cowbell)) = True
pitched Violin                 = True
pitched Oboe                   = True

highestPitch :: Instrument -> Either String Float
highestPitch i@(pitched -> False) =
  Left $ "Instrument " ++ show i ++ " has no pitch."
highestPitch (Drums (Just Cowbell)) = Right 800
highestPitch Violin                 = Right 5000
highestPitch Oboe                   = Right 2000

generates this error:

example.hs:19:1: warning: [-Wincomplete-patterns]
    Pattern match(es) are non-exhaustive
    In an equation for ‘highestPitch’:
        Patterns not matched:
            Rattle
            (Drums Nothing)

Under other conditions, I would just subdivide the Instrument type:

data Percussive = Rattle | Drums
data Pitched    = Violin | Oboe
data Instrument = Percussive Percussive
                | Pitched Pitched

But (in this imaginary physics) a set of Drums might have a highest pitch, if it includes a Cowbell, so it doesn't fit into either the Percussive or the Pitched type.

Jeffrey Benjamin Brown
  • 3,427
  • 2
  • 28
  • 40
  • 1
    This sounds like you are asking ghc to solve the halting problem. – pat Feb 16 '19 at 18:17
  • 2
    Pattern synonyms and the `COMPLETE` pragma? – Carl Feb 16 '19 at 18:29
  • 2
    This seems a little backwards anyway; define `highestPitch` first explicitly, then `pitched = either False True . highestPitch`. – chepner Feb 16 '19 at 19:06
  • 2
    @chepner, `either` takes two functions, not two constants. Better to do `pitched = isRight . highestPitch` – pat Feb 17 '19 at 18:07
  • @pat Yeah, that was a dumb mistake on my part. (I see @chi fixed my answer for me.) I like suggesting `either` in this case, though, because it doesn't require importing `Data.Either` like `isRight` does. – chepner Feb 17 '19 at 18:10

2 Answers2

4

I would reverse which function is "definitive". Even if an instrument is pitched, you still have to assign some highest pitch, while highestPitch provides everything you need to know if an instrument is pitched or not. Thus

-- An instrument with a highest pitch is pitched; the others aren't.
pitched :: Instrument -> Bool
pitched = either (const False) (const True) . highestPitch


highestPitch :: Instrument -> Either String Float
highestPitch (Drums (Just Cowbell)) = Right 800
highestPitch Violin                 = Right 5000
highestPitch Oboe                   = Right 2000
highestPitch Rattle = Left "Instrument Rattle has no pitch"
highestPitch (Drums Nothing) = Left "Instrument Drums Nothing has no pitch"
chi
  • 111,837
  • 3
  • 133
  • 218
chepner
  • 497,756
  • 71
  • 530
  • 681
3

GHC does not check exhaustiveness taking into account the definition of pitched. Consequently, the first equation is essentially ignored by the checker, causing the warning to be raised.

Indeed, from a computing science perspective, GHC can not decide that in the general case, since exhaustiveness in the presence of view patterns is undecidable. At best, GHC might use some sophisticated static analysis, but instead it simply chooses to ignore pitched completely.

To silence the warning, I can see two main options. The first is to add a catch all case at the end.

highestPitch :: Instrument -> Either String Float
highestPitch i@(pitched -> False) =
  Left $ "Instrument " ++ show i ++ " has no pitch."
highestPitch (Drums (Just Cowbell)) = Right 800
highestPitch Violin                 = Right 5000
highestPitch Oboe                   = Right 2000
highestPitch i                      =
  Left $ "Instrument " ++ show i ++ " has no pitch."
  -- we could even use "error", in certain cases

If we follow this route, in this specific case we can remove the first equation.

highestPitch :: Instrument -> Either String Float
highestPitch (Drums (Just Cowbell)) = Right 800
highestPitch Violin                 = Right 5000
highestPitch Oboe                   = Right 2000
highestPitch i                      =
  Left $ "Instrument " ++ show i ++ " has no pitch."

Alternatively, we could make the last case Oboe a catch-all one:

highestPitch :: Instrument -> Either String Float
highestPitch i@(pitched -> False) =
  Left $ "Instrument " ++ show i ++ " has no pitch."
highestPitch (Drums (Just Cowbell)) = Right 800
highestPitch Violin                 = Right 5000
highestPitch _oboe                  = Right 2000   -- must be an Oboe

I am not a huge fan of this approach, though, since if pitched contains a bug, this will silently produce pitches which should have been not existent.

Actually, as pointed out above in the comments, there is a third way: using PatternSynonyms and the COMPLETE pragma to convince GHC to silence the warning. This is however more advanced. While it definitely has its uses in designing libraries, for this specific case it is probably a bit overkill.

chi
  • 111,837
  • 3
  • 133
  • 218