Why doesn't GHC give a compile time warning for the “No match in record selector” exception?

StackOverflow https://stackoverflow.com/questions/10393764

  •  04-06-2021
  •  | 
  •  

Question

When I run this buggy code...

data Person = Adult { pName :: String}
            | Kid   { pName :: String
                    , pAge  :: Int
                    } deriving Show

getAge :: Person -> Int
getAge p = pAge p

getName :: Person -> String
getName p = pName p

main :: IO ()
main = do

  let p1 = Kid "fred" 5
      p2 = Adult "john"
      ps = [p1, p2]

  names = map getName ps
  ages = map getAge ps

  putStrLn $ "names: " ++ show names
  putStrLn $ "ages: " ++ show ages

... I get this in ghci:

names: ["fred","john"]

ages: [5,* * * Exception: No match in record selector pAge

I know how to avoid this error, but I'm wondering why compiling with "ghc -Wall" didn't warn me about this problem. Is there another tool that can help me to prevent this type of error?

Was it helpful?

Solution

Is there [a] tool that can help me to prevent this type of error?

No, but there could be.

As you know, record syntax automatically generates getters with the same name as the attributes you define. Therefore the code

data Person = Adult { pName :: String}
            | Kid   { pName :: String
                    , pAge  :: Int
                    } deriving Show

creates the functions pName :: Person -> String and pAge :: Person -> Int. Now, suppose Haskell had subtyping. If it did, then Kid could be a subtype of Person, and pAge could have the more appropriate type Kid -> String. However, Haskell does not have subtyping, and there is therefore no Kid type.

Now, given that Person -> String is the most specific type we can give to pAge, why not warn that pAge is a partial function at compile time? Let me divert the question by referring to the List example

data List a = Cons { head :: a, tail :: List a } | Empty

In this example, head and tail are partial functions: the two components of a non-empty list, but (due to Haskell's lack of subtyping) meaningless accessors on the empty list. So, why no warning by default? Well, by default, you know the code you have written. The compiler doesn't provide warnings if you use unsafePerformIO, because you're the programmer here, you're expected to use such things responsibly.

So tl;dr: if you want the warning here:

getAge :: Person -> Int
getAge p = pAge p

then you're out of luck, because the type system does not have enough information to deduce that this is a problem.

If you want the warning here:

data Person = Adult | Kid { pAge :: Int }

then I'm sure it would be trivial to implement: just check that a given field exists in some constructors but not others. But I do not foresee this warning being widely useful for everyone; some might complain that it would be just noise.

OTHER TIPS

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top