Why does the type of local variables that are values affect the type of input variables in the type signature of a function?

Viewed 73
Prelude> func f = [(show s, f == s) | s <- [0, 1..10]]
Prelude> :type func
func :: (Num a, Enum a, Show a, Eq a) => a -> [(String, Bool)]

I would expect f to just be an instance of Eq a but all the class constraints applied to s are also applied to f for some reason. Replacing s with any constant removes the relevant type constraint for f, and replacing s in the equality removes all class constraints except Eq a for f.

Can someone explain to me why does the type of local variables that are values affect the type of input variables that are values?

1 Answers

Eq doesn't exist in a vacuum. To compare two things for equality, you have to have two things. And, crucially, those two things have to be of the same type. In Haskell, 0 == "A" isn't just false; it's a type error. It literally doesn't make sense.

f == s

When the compiler sees this, even if it knows nothing else about the types of f and s, it knows what (==) is. (==) is a function with the following signature.

(==) :: Eq a => a -> a -> Bool

Both arguments are of the same type. So now and forevermore, for the rest of type-checking this expression, we must have f and s of the same type. Anything required of s is also required of f. And s takes values from [0, 1..10]. Your type constraints come as follows

  • Num is required since s takes values from a list of literal integers.
  • Enum is required by the [..] list enumeration syntax.
  • Show is required by show s.
  • Eq is required by the f == s equality expression.

Now, if we replace s with a constant, we get something like

func f = [(show s, f == 0) | s <- [0, 1..10]]

Now f is being compared with 0. It has no relation to s. f requires Eq (for (==)) and Num (since we're comparing against zero, a number). s, on the other hand, requires Enum, Num, Eq, and Show. In the abstract, this should actually be a type error, since we've given no indication as to which type s should be and there aren't enough clues to figure it out. But type defaulting kicks in and we'll get Integer out of it.

Related